<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Patrick Tingen</title>
    <description>The latest articles on Forem by Patrick Tingen (@patricktingen).</description>
    <link>https://forem.com/patricktingen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1715%2FPatrick.jpg</url>
      <title>Forem: Patrick Tingen</title>
      <link>https://forem.com/patricktingen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/patricktingen"/>
    <language>en</language>
    <item>
      <title>Help me choose my next book</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Sun, 23 Feb 2020 12:46:57 +0000</pubDate>
      <link>https://forem.com/patricktingen/help-me-choose-my-next-book-5h7i</link>
      <guid>https://forem.com/patricktingen/help-me-choose-my-next-book-5h7i</guid>
      <description>&lt;p&gt;As an experienced developer, I always want to sharpen my saw and thus I try to keep up with blogs, articles and books. &lt;/p&gt;

&lt;p&gt;I planned on reading the new version of &lt;a href="https://pragprog.com/book/tpp20/the-pragmatic-programmer-20th-anniversary-edition"&gt;The Pragmatic Programmer&lt;/a&gt; but then came across Martin Fowler's book &lt;a href="https://martinfowler.com/books/refactoring.html"&gt;Refactoring, Improving the Design of Existing Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now I am in doubt. I can only read one book at a time, so which one should I pick? Pragmatic Programmer is interesting, but as a developer with 26 years of experience (or 35 if you count my child years as well) I might profit more from the Refactoring book.&lt;/p&gt;

&lt;p&gt;What would you suggest?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Don’t analyze your code</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Sun, 01 Dec 2019 20:04:52 +0000</pubDate>
      <link>https://forem.com/patricktingen/don-t-analyze-your-code-3o0n</link>
      <guid>https://forem.com/patricktingen/don-t-analyze-your-code-3o0n</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and has over 40,000 lines of code. Inside are some real treasures, so I will dissect the DataDigger, to reveal them. Today: bugs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My advice may sound strange, but there is a twist. You should not analyze your code YOURSELF. Have it done by someone who can do it much better and faster than you. And that is – obviously – a computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ_BeZy5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2019/12/spgthga1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ_BeZy5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2019/12/spgthga1.png" alt="spgTHgA[1]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am the main developer for the DataDigger and since it is a hobby project, I have some pride in keeping the code clean, so when Gilles Querret of Riverside software asked me if he could use the DataDigger as a test case for his OE plugin for Sonar, I did not need to think twice. Having it analyzed by Sonar would prove that my code was super clean and that there were only a few minor things to improve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boy, was I wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even though the code /was/ quite clean, Sonar still found a lot of issues with the code. A lot of these issues were not very important things. Think of things like variables that are defined and assigned, but never used. Think of LEAVE or NEXT statements without a block label. Think of statements that are combined on one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minor things&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sure, minor things. But they added up. There was a total of over 400 issues that were reported. A lot of them were indeed minor issues, but a few were not. One of the reports of a parameter that was used but never assigned made me frown. Why did I put a parameter in when I did not use it. So I was busy removing it from both the program and the callers, when I suddenly saw that it was not the parameter that was obsolete, but it was the logic that was lacking. A part of the functionality was missing and I had not noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But there were more and more small issues that – on a second glance – were perhaps not as small as I thought. Missing labels for NEXT or LEAVE are no problem, as long as you are leaving the right block. When placing labels I found that I left the inner block where I should have left the outer block, resulting in poor performance. Another issue was a ‘dot commented line’. When debugging, I sometimes add a dot before the first statement on a line when debugging. It results in the line being treated as a commented line. That’s fine, but you should remove it when your done. I didn’t.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MXiUNVaE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2019/12/ox2ywsk1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MXiUNVaE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2019/12/ox2ywsk1.png" alt="ox2ywSK[1]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So there were quite a few issues with the DataDigger. Gilles attached the development branch to Sonar and I started to fix the issues. Now, there is only a handful left that are either hard to fix or wrongly reported.&lt;/p&gt;

&lt;p&gt;So, see what Sonar reports on the development branch on the &lt;a href="http://sonar.riverside-software.fr/dashboard?branch=develop&amp;amp;id=patrickTingen%3ADataDigger"&gt;riverside&lt;/a&gt; site.&lt;/p&gt;

&lt;p&gt;Since the DataDigger development branch is quite clean now, you can compare it to the &lt;a href="http://sonar.riverside-software.fr/dashboard?branch=master&amp;amp;id=patrickTingen%3ADataDigger"&gt;master branch&lt;/a&gt;, which still contains quite a number of issues.&lt;/p&gt;

&lt;p&gt;So don’t analyze your code, have it analyzed by Sonar.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was previously posted on the &lt;a href="https://datadigger.wordpress.com/2019/12/01/dont-analyze-your-code"&gt;DataDigger blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>coding</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Settings, settings everywhere</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Mon, 15 Jul 2019 18:36:15 +0000</pubDate>
      <link>https://forem.com/patricktingen/settings-settings-everywhere-al2</link>
      <guid>https://forem.com/patricktingen/settings-settings-everywhere-al2</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and has over 40,000 lines of code. Inside are some real treasures, so I will dissect the DataDigger, to reveal them.     Today: caching.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The DataDigger remembers a lot. That may be something that goes by largely unnoticed, but things like the window size, its position, what table you selected, which fields you hid or what filters you used, all of it is remembered in the settings file. But there is more, because the settings you use in the settings screen are saved in the ini file as well. These settings are saved and restored when you start DataDigger again. But what is going on behind the scenes?&lt;/p&gt;

&lt;p&gt;Settings can be saved on disk in a number of ways. For DataDigger I decided that I wanted to use a common format for the settings and I chose the &lt;a href="https://en.wikipedia.org/wiki/INI_file" rel="noopener noreferrer"&gt;ini file format&lt;/a&gt; as used by Windows. This is a fairly readable format and allows for external tools to edit the file, should it be necessary. Other possible formats would have been an xml file, a json file or a proprietary format, but I settled on a simple structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEFINE TEMP-TABLE ttConfig NO-UNDO 
  FIELD cSection AS CHARACTER 
  FIELD cSetting AS CHARACTER
  FIELD cValue AS CHARACTER 
  INDEX idxPrim IS PRIMARY cSection cSetting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Populating it is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE readConfigFile : 
  DEFINE INPUT PARAMETER pcConfigFile AS CHARACTER NO-UNDO. 

  DEFINE VARIABLE cSection AS CHARACTER NO-UNDO. 
  DEFINE VARIABLE cLine    AS CHARACTER NO-UNDO. 

  INPUT FROM VALUE(pcConfigFile). 
  REPEAT: 
    IMPORT UNFORMATTED cLine. 
    IF cLine MATCHES "[\*]" THEN cSection = TRIM(cLine,"[]"). 
    IF NUM-ENTRIES(cLine,'=') = 2 THEN 
    DO: 
      FIND ttConfig 
        WHERE ttConfig.cSection = cSection 
          AND ttConfig.cSetting = ENTRY(1,cLine,"=") NO-ERROR. 
      IF NOT AVAILABLE ttConfig THEN 
      DO: 
        CREATE ttConfig. 
        ASSIGN ttConfig.cSection = cSection 
               ttConfig.cSetting = ENTRY(1,cLine,"="). 
      END. 
      ttConfig.cValue = ENTRY(2,cLine,"="). 
    END. 
  END. 
  INPUT CLOSE.
END PROCEDURE. /\* readConfigFile \*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that this is a simplified version of what is used in DataDigger. No buffers are used (you really should use buffers, like: always) and no edge cases are handled here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DataDigger’s INI files
&lt;/h2&gt;

&lt;p&gt;DataDigger uses 3 different .ini files. One is for DataDigger itself; its primary task is to save time stamps of the source files. On startup, the time stamps of the current files in the DataDigger folder are compared to those in the .ini file and based on that, DataDigger decides whether or not to recompile itself.&lt;/p&gt;

&lt;p&gt;The second file is for the help messages. In hindsight, these could have been put in the primary .ini file, but in the early days of DataDigger I thought it would be handy to have them in a separate file.&lt;/p&gt;

&lt;p&gt;The last one is the user-specific .ini file for the settings of the user. The .ini file is appended with the login name of the user so each user will have his own settings file. In this file all settings are saved that are a result of the user’s actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrKsNrXk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrKsNrXk.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This last one is the one that gets most read and write actions. When I introduced the settings file, this was a nice feature to save and restore user settings, but as DataDigger developed, more and more ended up in the settings file and eventually, reading and writing became noticeable (read: slow).&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings, version 1
&lt;/h3&gt;

&lt;p&gt;The very first version  was one that read its settings straight from the INI file itself, using GET-KEY-VALUE and PUT-KEY-VALUE. The temp-table as shown above was not yet used. Although straightforward, it was slow, so I quickly moved on to plan B.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings, version 2
&lt;/h3&gt;

&lt;p&gt;Plan B was called “Hello Caching”. At the beginning of the session, I read the .ini file into ttConfig and served all settings from there. Saving was done at the end of the session. This worked way better than the previous solution, but a problem arose when your session crashed prematurely, because your settings would not be saved. This was not the only problem, because when you had two windows active at the same moment, the settings would get out of sync very easy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings, version 3
&lt;/h3&gt;

&lt;p&gt;Enter version 3. The settings needed to be saved when changed, so I read them on startup, but saved them to disk whenever they changed, so data was saved when the session would crash. The temp-table was moved to the persistent library, so when running multiple windows, the settings would remain in sync.&lt;/p&gt;

&lt;p&gt;At this point, the code to get/set the config basically boils down to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FUNCTION getRegistry RETURNS CHARACTER
    ( pcSection AS CHARACTER
    , pcKey     AS CHARACTER ) :

  FIND ttConfig 
    WHERE ttConfig.cSection = pcSection 
      AND ttConfig.cSetting = pcKey NO-ERROR.

  RETURN ( IF AVAILABLE ttConfig THEN ttConfig.cValue ELSE ? ).
END FUNCTION. /* getRegistry */

FUNCTION setRegistry RETURNS CHARACTER
  ( pcSection AS CHARACTER
  , pcKey     AS CHARACTER
  , pcValue   AS CHARACTER ) :

  FIND ttConfig
    WHERE ttConfig.cSection = pcSection
      AND ttConfig.cSetting = pcKey NO-ERROR.

  IF NOT AVAILABLE ttConfig THEN DO:
    CREATE ttConfig.
    ASSIGN ttConfig.cSection = pcSection
           ttConfig.cSetting = pcKey.
  END.

  IF pcValue = ? OR pcValue = '' 
    THEN DELETE ttConfig.
    ELSE ttConfig.cValue = pcValue.

  RETURN "". 
END FUNCTION. /* setRegistry */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Again: stripped of buffers and edge cases&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This solution has been used pretty long in DataDigger, but since more and more got saved into the settings file, the writing process became a problem, so I needed to fix that. The answer to this was delayed writing to disk. Writing to disk involves some serious overhead where it does not really matter if you are writing one setting to disk or hundred. The extra time involved is a matter of milliseconds; saving 100 settings to disk one-by-one takes approximately 80 msec. Saving 100 settings in one pass takes 3 msec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings, version 4
&lt;/h3&gt;

&lt;p&gt;First, we add a new field to ttConfig to indicate the value has changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEFINE TEMP-TABLE ttConfig NO-UNDO
  FIELD cSection AS CHARACTER 
  FIELD cSetting AS CHARACTER 
  FIELD cValue AS CHARACTER 
  FIELD lDirty AS LOGICAL 
  INDEX idxPrim IS PRIMARY cSection cSetting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This field – lDirty – will be set to TRUE whenever we change a value in the table. So, the function setRegistry is changed to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FUNCTION setRegistry RETURNS CHARACTER
  ( pcSection AS CHARACTER
  , pcKey     AS CHARACTER
  , pcValue   AS CHARACTER ) :

  FIND ttConfig
    WHERE ttConfig.cSection = pcSection
      AND ttConfig.cSetting = pcKey NO-ERROR.

  IF NOT AVAILABLE ttConfig THEN DO:
    CREATE ttConfig.
    ASSIGN ttConfig.cSection = pcSection
           ttConfig.cSetting = pcKey.
  END.

  IF pcValue = ? OR pcValue = '' 
    THEN DELETE ttConfig.
    ELSE ASSIGN ttConfig.cValue = pcValue
                ttConfig.lDirty = TRUE.

  RETURN "". 
END FUNCTION. /* setRegistry */ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, only one extra line of code. Now, we add a timer (check my post ‘&lt;a href="https://wordpress.com/post/datadigger.wordpress.com/1000" rel="noopener noreferrer"&gt;Turn timers into a scheduler&lt;/a&gt;‘ on how to do that) and let it periodically checks whether there is anything to save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF CAN-FIND(FIRST ttConfig WHERE ttConfig.lDirty = TRUE) THEN
DO:
  OUTPUT TO VALUE(cConfigFile).

  FOR EACH ttConfig WHERE ttConfig.lDirty = TRUE
    BREAK BY ttConfig.cSection:

    ttConfig.lDirty = FALSE.

    IF FIRST-OF(ttConfig.cSection) THEN 
      PUT UNFORMATTED 
        SUBSTITUTE("[&amp;amp;1]",ttConfig.cSection) SKIP.

    PUT UNFORMATTED 
      SUBSTITUTE("&amp;amp;1=&amp;amp;2",ttConfig.cSetting, ttConfig.cValue) SKIP.
  END.

  OUTPUT CLOSE.
END.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This timer is executed every 5 seconds and on window-close, to make sure that even the last few settings are saved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveat
&lt;/h3&gt;

&lt;p&gt;One last warning: the code above is not literally from the DataDigger. If you explore the code on &lt;a href="https://github.com/patrickTingen/DataDigger" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (go ahead, it’s open) you will see that the code there is much longer, uses buffers and handles a lot of edge cases. I left a lot of that code out to make the code more readable. If you decide to implement settings in your application similar to what is described above, you should probably check the real code as well.&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>coding</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>DataDigger 24</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Fri, 13 Jul 2018 14:02:21 +0000</pubDate>
      <link>https://forem.com/patricktingen/datadigger-24-243j</link>
      <guid>https://forem.com/patricktingen/datadigger-24-243j</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in Progress OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and is free as in free beer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In DataDigger 24, besides a HUGE list of small fixes and improvements, some new features were introduced. My special thanks go to the DataDigger testing team, that consists of over 50 people that test beta versions of the DataDigger. Interested in beta versions? Set the update check in the settings screen to check for beta versions and DataDigger will warn you. Want to join the team and receive notification email? Send me a &lt;a href="https://datadigger.wordpress.com/contact/"&gt;note&lt;/a&gt; and I’ll add you to the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster startup
&lt;/h3&gt;

&lt;p&gt;The first startup for sessions with large database (in terms of nr of tables) could take quite some time. This was due to the fact that DataDigger has to examine the metaschema of the database. If that is large, it takes some time. In this version, this is about 12 times as fast, bringing startup times from over 2 minutes back to a little over 10 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  New toolbar
&lt;/h3&gt;

&lt;p&gt;If you start DataDigger, you will notice the toolbar at the left. Previously, this was hidden in the hamburger menu, but you can now choose to have them in view permanently. Hide or view the toolbar with the hamburger button  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MmRavc5W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/sliNTEn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MmRavc5W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/sliNTEn.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Expand / collapse it with the button at the bottom:  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iLSo_dNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/UFInsVx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iLSo_dNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/UFInsVx.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Generate code
&lt;/h3&gt;

&lt;p&gt;If you right-click in the table browse and select “Generate Code”. You will see a list of possible code fragments that can be generated, based on the table definitions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a7gVyJXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/vwR3UoB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a7gVyJXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/vwR3UoB.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f3Ii9aC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/Ms7eAzC.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f3Ii9aC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/Ms7eAzC.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can tweak the settings for how to generate the code to your likings.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Groups of favourites&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In DD24 you can have multiple groups of favourites. Imaging working on something related to invoicing. Then you only want to see the tables that are involved in invoicing. And the next ticket you pick up is about the new feature you are about to implement which involves a totally different set of tables. I bet you get the picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uX3d5R5u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/08/yraowc9.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uX3d5R5u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/08/yraowc9.png%3Fw%3D840" alt="yraowc9"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press the new ‘+’ button to create a new group.&lt;/p&gt;

&lt;p&gt;To maintain a group press the edit-button and you will end up in a screen where you can add/remove tables from the group or rename or delete the group&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h3mhPZ_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/08/yj0vp8n.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h3mhPZ_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/08/yj0vp8n.png%3Fw%3D840" alt="yj0vp8n"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Suppress yellow tips&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A feature that might be welcomed by some of you is the option to suppress the yellow tips. Just head into the settings, and uncheck ‘Show hints on new features’&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yKQ-NtQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/lFwJZVc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yKQ-NtQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/lFwJZVc.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will now not see the yellow frame again.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Set Working folder&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You can now set the working folder for DataDigger. By default, the working folder is the one where you installed it, but if you set the value in DataDigger.ini like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[DataDigger]WorkFolder=c:\DataDigger\%username%\
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then DataDigger will use the folder you specify at ‘WorkFolder’ for the cache folder, the user ini-files and the backup folder. No write actions will be done on the program folder of DataDigger anymore.&lt;/p&gt;

&lt;p&gt;Note that if you do not have write access to the program folder, you might want to set the following as well in the same section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[DataDigger]AutoCompile=no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beware that if you do that, DataDigger will not compile itself. So if you want to run on compiled sources, you should provide them yourself or else run uncompiled.&lt;/p&gt;

&lt;p&gt;As you may have guessed by the example above, it is possible to use OS-vars in the name of the folder. If it does not exist, it will be created. If it cannot be created, DataDigger will fall back to the DataDigger program folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Factory reset
&lt;/h3&gt;

&lt;p&gt;Often, DataDigger asks a question. A lot of these can be set to hush by ticking ‘Do not ask again’. If you want to reset your answers, you can reset these. Head into the settings screen, select the ‘Behavior’ tab and choose one of these options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--99-l_ao7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/6Y2lW7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--99-l_ao7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/6Y2lW7i.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both actions will ask you to confirm your choice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rxOeqqqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/6hlO4ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rxOeqqqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/6hlO4ho.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jKiC46lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/0tErgrV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jKiC46lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/0tErgrV.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was previously posted on the &lt;a href="https://datadigger.wordpress.com/2018/10/03/20181003/"&gt;DataDigger blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>coding</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Hard things in computer science</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Thu, 10 May 2018 18:33:32 +0000</pubDate>
      <link>https://forem.com/patricktingen/hard-things-in-computer-science-4h2i</link>
      <guid>https://forem.com/patricktingen/hard-things-in-computer-science-4h2i</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and has over 40,000 lines of code. Inside are some real treasures, so I will dissect the DataDigger, to reveal them.     Today: caching.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Years ago, at Netscape, Phil Karlton stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are only two hard things in Computer Science:&lt;br&gt;&lt;br&gt;
Naming things, cache invalidation and off-by-1 errors&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And he was right.&lt;/p&gt;

&lt;p&gt;The first thing I already tackled in 2008, with the first version of DataDigger. It was a fork of Richard Tardivon’s tool DataHack. The second problem manifested itself not until 2013, when in DataDigger 18 I introduced caching to speed up some things. Because, you know, caching *can* speed up things. Big time.&lt;/p&gt;

&lt;p&gt;I remember from that version that I was amazed at the improved startup time of DataDigger. The exact figures escape me at this point, but it was in the leage of 10 times as fast. And boy I was glad that I introduced it. But man, did I regret it later, because time after time old stuff kept creeping up from the cache. It took me a lot of time to get it right. Right now, there are basically two caching systems in DataDigger: one for the table definitions and one for the settings. I’ll walk you through these and explain how I managed to fill them and to invalidate them at the right moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File definitions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The file definitions take quite some time to gather. For a small database, this goes unnoticed, but if you have – like some of my users – a set of 8 databases with a couple of thousand (yes, thousand) tables, then analyzing them takes a considerable amount of time. On startup, DataDigger first checks the date and time of the last modification to the  the database. This file – if it exists – should reside in the cache folder. Lets look at the contents of my cache folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fleyndii.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fleyndii.png%3Fw%3D840" alt="leyndii"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first two files are database cache files. We can recognize them by their name that starts with ‘db’, followed by the name and the date and time of last modification. The date of last modification can be found this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FIND FIRST \_DbStatus NO-LOCK. 
MESSAGE \_DbStatus.\_dbstatus-cachestamp VIEW-AS ALERT-BOX.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see two files for my sports database. That is because I changed the schema by adding a field. That changed the date of last modification to today. When I started DataDigger again, it looked at the date in _DbStatus and found a value of 10 may 2018, 19:45:42. It then looked for a file with the name ‘db.sports.Thu_May_10_194542_2018.xml’ but since it wasn’t there, it recreated the file and analyzed the CRC of all tables. This mechanism makes sure that the cache for database definitions is invalidated at the right time.&lt;/p&gt;

&lt;p&gt;The database cache file contains a list of all tables in the database with their crc number. This value is important when we want to read the cache for the individual tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fdv2xpzo.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fdv2xpzo.png%3Fw%3D840" alt="dv2xpzo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the current crc for my customer table is 6269. If you look in the file list above, you will spot a file called ‘sports.Customer.6269.xml’. This is the file with the settings for this version of the customer table. There is another file for customer with the number 48132. Apparently, this was the CRC before my change.&lt;/p&gt;

&lt;p&gt;Because the CRC changed and the CRC is part of the file name, this also makes sure the cache is invalidated. If DataDigger cannot find a cache file, it will read the definitions from the _file table and create a cache file, so at the next start it can read it in.&lt;/p&gt;

&lt;p&gt;Since caching gives startup of DataDigger such a boost, it is handy to have them. But if DataDigger should wait until you select the table, you would still have to wait. In order to avoid this, at startup DataDigger does some pre-caching in the background. It uses the list of most recently used tables and checks if the cache file for each of those tables is present. In the settings you can find this in the tab for ‘behavior’:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fcakvqic.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F05%2Fcakvqic.png%3Fw%3D840" alt="cakvqic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can untick the caching settings, but normally this would not be needed. Building the cache is done in the background, by using a scheduling mechanism, as I described in my previous post &lt;a href="https://wordpress.com/post/datadigger.wordpress.com/1000" rel="noopener noreferrer"&gt;Turn timers into a scheduler&lt;/a&gt;. To make sure the DataDigger remains responsive, it checks only one table per 2 seconds.&lt;/p&gt;

&lt;p&gt;That’s it. In a next post I will elaborate more on the caching of settings, since that is totally other beast. Let me know if you have used caching in your own project or if my technique can be improved. For now: have fun!&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>coding</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Turn timers into a scheduler in OpenEdge</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Fri, 27 Apr 2018 07:13:25 +0000</pubDate>
      <link>https://forem.com/patricktingen/turn-timers-into-a-scheduler-13j0</link>
      <guid>https://forem.com/patricktingen/turn-timers-into-a-scheduler-13j0</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and has over 40,000 lines of code. Inside are some real treasures, so I will dissect the DataDigger, to reveal them. Today: controlling timers with a scheduler.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/patricktingen/time-to-change-2k8e"&gt;previous post&lt;/a&gt; I explained how I used a timer ocx to improve the user experience on an OpenEdge window. In short: I used it to delay the VALUE-CHANGED event on a browser to avoid lengthy screen updates.&lt;/p&gt;

&lt;p&gt;Since this worked as a charm, I really got the taste of timers and just like when your only tool is a hammer and every problem looks like a nail, a lot of my problems begged for a solution in the form of a timer and so I ended up with lots of timers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to delay the value changed event on the table browse&lt;/li&gt;
&lt;li&gt;to close the popup menu after 2 seconds&lt;/li&gt;
&lt;li&gt;to keep track of scrolling the browser horizontally (there is no event for that)&lt;/li&gt;
&lt;li&gt;to pre-load the cached definitions of the database&lt;/li&gt;
&lt;li&gt;to keep the connections alive to the various databases&lt;/li&gt;
&lt;li&gt;to resize the resizable data dictionary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although it all worked well, it didn’t feel good:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F04%2F9xkdeni.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatadigger.files.wordpress.com%2F2018%2F04%2F9xkdeni.png%3Fw%3D840" alt="9xkdeni"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Noticed it? It’s especially this what nags me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2w3zC8K.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2w3zC8K.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Six timers in a row and although the end user couldn’t really care less how many of those reside on my design canvas, it bothered me. We – developers – can surely do better than that.&lt;/p&gt;

&lt;p&gt;By the time I found a use for a seventh timer I decided that enough is enough; I stripped all timers except for one. I wanted one timer to rule them all. But in order to rule them, it needed to have them all in one place. In a temp-table. Of course. The plan is to save all timer events in this temp-table and let our single timer keep track of what procedure to start and when.&lt;/p&gt;

&lt;p&gt;The table consists of a field for the procedure (cProc), a field for the interval (iTime) and a field to store when the procedure should run (tNext).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* TT for the generic timer OCX */
DEFINE TEMP-TABLE ttTimer NO-UNDO 
  FIELD cProc AS CHARACTER 
  FIELD iTime AS INTEGER 
  FIELD tNext AS DATETIME 
  INDEX idxNext IS PRIMARY tNext 
  INDEX idxProc cProc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that tNext is a datetime field. This has a granularity of a thousandth of a second and that should be more than precise enough for our purposes. Also notice that our primary index is exactly on that field. I’ll come back to that later on.&lt;/p&gt;

&lt;p&gt;Ok, now lets get a timer running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* KeepAlive timer every minute */
RUN setTimer("KeepAlive", 60000).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was easy, wasn’t it? Ok, that’s a bit lame, let’s see what happens inside setTimer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE setTimer: 
  /* Enable or disable a named timer. */
  DEFINE INPUT PARAMETER pcTimerProc AS CHARACTER NO-UNDO. 
  DEFINE INPUT PARAMETER piInterval AS INTEGER NO-UNDO. 

  FIND ttTimer WHERE ttTimer.cProc = pcTimerProc NO-ERROR. 
  IF NOT AVAILABLE ttTimer THEN CREATE ttTimer. 

  ASSIGN 
    ttTimer.cProc = pcTimerProc 
    ttTimer.iTime = piInterval 
    ttTimer.tNext = ADD-INTERVAL(NOW, piInterval,"milliseconds"). 
  RUN SetTimerInterval.
END PROCEDURE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, what we do is find the timer in the temp-table and create it if it does not exist. We fill the fields and finally we run the procedure SetTimerInterval. This procedure doe a neat trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE setTimerInterval:  
  /* Set the interval of the timer so that it will
   * tick exactly when the next timed event is due.
   */
  FOR FIRST ttTimer BY ttTimer.tNext:    
    chCtrlFrame:pstimer:INTERVAL = MAXIMUM(1,MTIME(ttTimer.tNext) - MTIME(NOW)).    
    chCtrlFrame:pstimer:ENABLED = TRUE.  
  END.
END PROCEDURE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It simply finds the first record by time of execution (that’s why we had the index on it), we subtract ‘NOW’ from that time and then we have the number of milliseconds until the next event. We set that as the timer interval, enable it and then we simply wait until the timer ticks. And then …&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE pstimer.ocx.tick: 
  chCtrlFrame:pstimer:ENABLED = FALSE. 
  FIND FIRST ttTimer NO-ERROR. 
  IF AVAILABLE ttTimer THEN DO: 
    RUN VALUE(ttTimer.cProc). 
    IF AVAILABLE ttTimer THEN ttTimer.tNext = ADD-INTERVAL(NOW, ttTimer.iTime,"milliseconds"). 
  END. 
  RUN setTimerInterval.
END PROCEDURE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We find the first event (primary index on time). We run the code and after that we reschedule the timer. That is, if the ttTimer record is still available. One could decide that once the task is run, there is no need for another run. If I close the menu, I only need to close it again when the user opens it, so inside the procedure to close the menu I run setTimer again, but this time with parameter ‘0’, which is a sign to delete it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveats&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are a few limitations of course. You need to define a procedure with the name of the timer and it cannot handle parameters. I thought of introducing that but it would make things overcomplicated so I left it out. The timing is not always 100% accurate and if you have two procedures that should run at the same time, then they will run sequentially. But if exact timing is not a problem and you can do without parameters, then this is a perfect way to improve the UI on your application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code that is used in the DataDigger is a bit more complicated than shown above. For starters, it uses buffers instead of operating on ttTimer directly, has some more comments and some code for edge cases and debugging. Leaving all that code in would make it less readable so I simply left it out. I put together a small demo that has no unnessecary code and can be used as a proof of concept. You can find the code &lt;a href="https://github.com/patrickTingen/BlogSrc/tree/master/2018-04-26" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FU27Ofsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FU27Ofsq.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Move the sliders to start the timer, move them back to zero to disable them. The first one shows a clock, the second a spinning rotor and the third one will hide the text after a few seconds.&lt;/p&gt;

&lt;p&gt;That’s it. Let me know if you have used a technique like this in your own project or if it can be improved. For now: have fun!&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>coding</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Time to change; using a timer OCX in OpenEdge</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Wed, 17 Jan 2018 11:39:16 +0000</pubDate>
      <link>https://forem.com/patricktingen/time-to-change-2k8e</link>
      <guid>https://forem.com/patricktingen/time-to-change-2k8e</guid>
      <description>&lt;p&gt;&lt;em&gt;The DataDigger is an open source database browser for developers in OpenEdge. It enables them to view, update, delete, im- and export the data inside the database. The DataDigger is written fully in OpenEdge 4GL and has over 40,000 lines of code. Inside are some real treasures, so I will dissect the DataDigger, to reveal them. Today: using a timer to improve the UI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Have you ever looked closely to what DataDigger does when you change files? If not, I would like to invite you to do so. Right now. Go ahead, I’ll wait. Startup DataDigger, set focus on the list of tables and press cursor-down once. At the right you will see the fields of the selected table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3xGWmNNy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/01/ablvtco.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3xGWmNNy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/01/ablvtco.png%3Fw%3D840" alt="ablvtco"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No news here. Press cursor-down again and immediately again. And again. Note what happens with the field browse. Did you notice? If you were fast enough, the field browse did not update. It skipped, because it knew that you did not intend to look at it anyway.&lt;/p&gt;

&lt;h4&gt;
  
  
  How did it know that?
&lt;/h4&gt;

&lt;p&gt;It’s elementary my dear, because you didn’t stay long enough on the table name to show the fields. There is a small delay in showing the fields. I’ll show you how it is done.&lt;/p&gt;

&lt;p&gt;I created a small demo that can be downloaded from &lt;a href="https://github.com/patrickTingen/BlogSrc/tree/master/2018-01-17"&gt;GitHub&lt;/a&gt; to show how it’s done. If you download it and run wCustPerSalesrep1.w against the classic sports database you will see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_oGo1mtp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/01/snvh001.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_oGo1mtp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2018/01/snvh001.png%3Fw%3D840" alt="snvh001"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a very minimal implementation, but browse through the sales reps and you will see the related customers at once. Now imagine that in order to collect the child records (in this case the customers) might take some time. What would happen to the browse? Suppose you wanted to move from sales rep BBB to GPE using the cursor keys. That would mean 3 key presses. But on each sales rep that is in between, we would need to collect the data, which would be a waste of time. To illustrate this, just uncomment some code in the VALUE-CHANGED trigger of the left browse to make it look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ON VALUE-CHANGED OF brSalesrep IN FRAME DEFAULT-FRAME
DO:
  ETIME(YES).
  DO WHILE ETIME &amp;lt; 500: END.
  {&amp;amp;OPEN-QUERY-brCustomer}
END.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will simulate that the code that runs to collect the customers takes haf a second to complete. Who knows? It might be running on an appserver over a relative slow connection. Now run it again. Notice the annoying delay? Time to fix it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Introducing the timer
&lt;/h4&gt;

&lt;p&gt;The idea is as follows: if the user chooses another sales rep, then wait a few milliseconds to see if the user changes again (like we did when we pressed cursor-down 3 times). If it looks like he is not going to do that, perform the value-changed. The time to wait is delicate: too short has no effect while waiting too long is just as bad as the cause. In practice, a time of approximately 250-350ms is good. For the DataDigger I started with 350 but later brought it back to 300ms because it felt a bit snappier.&lt;/p&gt;

&lt;p&gt;Run wCustPerSalesrep2.w and use the cursor keys to navigate through the sales rep records. If you are fast enough, you will see that the customer browse will not change as long as you keep changing tables. Release the keys and a after a short while the customer browse will refresh.&lt;/p&gt;

&lt;p&gt;Let’s look at the code. The value-changed of the browse has changed a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ON VALUE-CHANGED OF brSalesrep IN FRAME DEFAULT-FRAME
DO:
  chCtrlFrame:PSTimer:INTERVAL = 300.
  chCtrlFrame:PSTimer:ENABLED = TRUE.
END.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code to actually refresh the customer browse can now be found in the pstimer.tick procedure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE CtrlFrame.PSTimer.Tick.
  {&amp;amp;OPEN-QUERY-brCustomer}
  chCtrlFrame:PSTimer:ENABLED = FALSE.
END PROCEDURE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice that I turn the timer off and on. This is because we want the timer to start again every time we have a new record. In the timer.tick we turn the timer off. After all: we only need to wait until the user chooses another record.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tweaking leads to tweaking
&lt;/h4&gt;

&lt;p&gt;You will notice that once you start tweaking around, you often end up tweaking ad infinitum. Run the program and point to one of the sales reps with your mouse. Why is the program waiting now? You – as a user – pointed EXACTLY at the sales rep you had in mind. No need to wait for the program. So you might want to uncomment the code in the MOUSE-SELECT-CLICK event to make it look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ON MOUSE-SELECT-CLICK OF brSalesrep IN FRAME DEFAULT-FRAME
DO:
  {&amp;amp;OPEN-QUERY-brCustomer}
  RETURN NO-APPLY.
END.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take care of the small delay when selecting with the mouse. Run it again and you will notice that once you select with the mouse, the customer browse is refreshed instantly&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;That’s it. Let me know if you have used a technique like this in your own project. In a next post I will show you what to do if you have multiple delayed events that you want to take care of. For now: have fun!&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>progress</category>
      <category>datadigger</category>
      <category>coding</category>
    </item>
    <item>
      <title>Mention at PUG Challenge</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Thu, 16 Nov 2017 19:23:43 +0000</pubDate>
      <link>https://forem.com/patricktingen/mention-at-pug-challenge-1ohg</link>
      <guid>https://forem.com/patricktingen/mention-at-pug-challenge-1ohg</guid>
      <description>&lt;p&gt;Today, in the opening session of the PUG Challenge in Prague, DataDigger was mentioned as a good example of a nice open source project in OpenEdge. Well, of course, who am I to disagree? So thanks Mark!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://datadigger.files.wordpress.com/2017/11/pug_mention2.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UepB69Ee--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/11/pug_mention2.jpg%3Fw%3D840" alt="pug_mention2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And also thanks to &lt;a href="https://www.linkedin.com/in/arnovanderende/"&gt;Arno van der Ende&lt;/a&gt; for the photo&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

</description>
      <category>openedge</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Too Strict Compile Option</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Wed, 01 Nov 2017 11:15:06 +0000</pubDate>
      <link>https://forem.com/patricktingen/too-strict-compile-option-3hf7</link>
      <guid>https://forem.com/patricktingen/too-strict-compile-option-3hf7</guid>
      <description>&lt;p&gt;Lately I received a few reports that DataDigger would not compile using the new strict compile settings in 11.7. That did not surprise me at all, because I never focussed on that so there might have been a few abbreviated keywords lingering around.&lt;/p&gt;

&lt;p&gt;And indeed there were quite a few. Around 100 to be more specific. Of that number only a handful were due to my coding, but the majority came from the UIB that generates code with abbreviated keywords.&lt;/p&gt;

&lt;p&gt;Since I expected that this would be fixed in 11.7 in the UIB, I opened up the DataDigger main window in the UIB and saved it again, but nothing changed. So I created a new blank window and inspected it afterwards. To my surprise I found this :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3nhOQ_vm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/11/adrqyhk.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3nhOQ_vm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/11/adrqyhk.png%3Fw%3D840" alt="adrqyhk"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the red marks in the gutter. The error that is reported is:&lt;/p&gt;

&lt;p&gt;Cannot reference “VARIABLE” as “VAR” due to the&lt;br&gt;&lt;br&gt;
“require-full-keywords” compiler option. (18494)&lt;/p&gt;

&lt;p&gt;Sounds like a bug to me, but bottom line is that you cannot use the strict compiler options in combination with DataDigger. Until it is fixed, that is.&lt;/p&gt;

&lt;p&gt;Update: I logged a &lt;a href="https://community.progress.com/community_groups/openedge_development/f/19/t/35904"&gt;question&lt;/a&gt; on the PSDN community page which got picked up. It appears to be a bug indeed.&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>DataDigger in Notepad++</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Thu, 26 Oct 2017 08:37:17 +0000</pubDate>
      <link>https://forem.com/patricktingen/datadigger-in-notepad-43om</link>
      <guid>https://forem.com/patricktingen/datadigger-in-notepad-43om</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7ziQ0FZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/jggruit.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7ziQ0FZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/jggruit.png%3Fw%3D840" alt="jggruit"&gt;&lt;/a&gt;If you are using Notepad++ as an editor, you should really check out the plugin 3P from Julien Caillon. His plugin 3P – short for Progress Programmers Pal – is a fan-tas-tic addition to Notepad++. Its list of features is almost endless, his support is great and he provides extra tooling from within Notepad++. It is possible to call DataDigger from Notepad++ with ALT-D. The latest version will be downloaded from GitHub and installed. Same story for ProParse; just press F12 and the latest version will auto-install.&lt;/p&gt;

&lt;p&gt;You can install 3P via the plugin manager of Notepad++. Click ‘Plugins &amp;gt; Plugin Manager &amp;gt; Show Plugin Manager’&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zGr3CBtT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/r7pxc3g.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zGr3CBtT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/r7pxc3g.png%3Fw%3D840" alt="r7pxc3g"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And select 3P from the list of available plugins. It should be at the top&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KoRhCX3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/6uaikyu.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KoRhCX3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/6uaikyu.png%3Fw%3D840" alt="6uaikyu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installing, Notepad++ will restart to complete the installation. 3P will give some additional notifications about syntax highlighting and update checks. Just follow the instructions and then, when the plugin is installed, press ALT-D.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HeWX4yei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/hhwg8ik.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HeWX4yei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/hhwg8ik.png%3Fw%3D840" alt="hhwg8ik"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will tell you that you need to provide the path to the Progress executable. Set it and try again. When DataDigger is installed, you should be welcomed with&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cTm683UA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/e9w4rrc.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cTm683UA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/e9w4rrc.png%3Fw%3D840" alt="e9w4rrc"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Digging in Notepad++&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

</description>
      <category>openedge</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>DataDigger on SonarQube</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Tue, 24 Oct 2017 09:53:51 +0000</pubDate>
      <link>https://forem.com/patricktingen/datadigger-on-sonarqube-5cbc</link>
      <guid>https://forem.com/patricktingen/datadigger-on-sonarqube-5cbc</guid>
      <description>&lt;p&gt;Yesterday, Gilles Querret of Riverside Software analyzed the DataDigger code from GitHub with &lt;a href="http://sonar.riverside-software.fr/about"&gt;SonarQube&lt;/a&gt;. And while I always thought that my code was pretty clean, it turned out that there was still a lot left to be improved&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sonar.riverside-software.fr/projects?search=DataDigger"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gnKuw7Gx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/10/1wirkmu.png%3Fw%3D840" alt="1wirkmu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of the issues SonarQube reported will be fixed in the upcoming DataDigger 24, but I invite you to check on the tooling yourself for your own projects.&lt;/p&gt;

&lt;p&gt;My guess is that it will be an eye opener &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zCyXRrdx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f642.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zCyXRrdx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f642.png" alt="🙂"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>openedge</category>
      <category>datadigger</category>
    </item>
    <item>
      <title>Minimum Progress Version</title>
      <dc:creator>Patrick Tingen</dc:creator>
      <pubDate>Wed, 21 Jun 2017 10:04:13 +0000</pubDate>
      <link>https://forem.com/patricktingen/minimum-progress-version-3p1h</link>
      <guid>https://forem.com/patricktingen/minimum-progress-version-3p1h</guid>
      <description>&lt;p&gt;Up to the current version 23 the front page happily stated that you need at least 10.1B to run the DataDigger.&lt;br&gt;&lt;br&gt;
Today, it turned out that that is a lie &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UJDcIXuh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f626.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UJDcIXuh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f626.png" alt="😦"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main program uses the FINALLY statement and that was introduced in version 10.1C so that is the minimum version you can use to run DataDigger.&lt;/p&gt;

&lt;p&gt;If you are stuck on 10.1B and still want to use the DataDigger, just open up DataDiggerLib.p &lt;strong&gt; &lt;/strong&gt; and search for “FINALLY”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YBhPttyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/06/101b_upgrade.png%3Fw%3D840" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YBhPttyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datadigger.files.wordpress.com/2017/06/101b_upgrade.png%3Fw%3D840" alt="101b_upgrade"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remove the FINALLY block entirely. On restart, DataDigger will recompile itself.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

</description>
      <category>openedge</category>
      <category>datadigger</category>
    </item>
  </channel>
</rss>
