How To use a progressbar in Dynamics NAV?

AdministratorAdministrator Member, Moderator, Administrator Posts: 2,500
edited 2014-04-01 in How Tos section
How To use a progressbar in Dynamics NAV?

http://www.mibuso.com/howtoinfo.asp?FileID=17

Discuss this How To here.

Comments

  • gerdhuebnergerdhuebner Member Posts: 155
    I have a proposal for an advancement:
    Instead of actual the progress bar every second it would be better to actual it about 100 times regardless of how long the process durates, because with a fixed intervall of 1 sec, you have the effect for a process that lasts 3 sec that only 33, 67 an 100 % ist displayed as progress. In my opinion the progress bar should work for all processes identically. To accomplish this, one has to calculate just the 100th part of the number of iterations and every time this value is passed, actual the progress bar and set the counter back to zero. Of course you would need two counters, one for the whole progress and the one which is reset to zero whenever actualisation is necessary. In this way the TIME function together with the - 1sec - calculation is avoided which may be more time consuming than just incrementing another int variable...
  • Timo_LässerTimo_Lässer Member Posts: 481
    Hi Gerd,

    your idea is the way, I do it a long time.

    Here is an example:
    Window.OPEN('Processing data... @1@@@@@@@@@@');
    NoOfRecs := MyRecord.COUNT;
    REPEAT
      CurrRec += 1;
      IF NoOfRecs <= 100 THEN
        Window.UPDATE(1,(CurrRec / NoOfRecs * 10000) DIV 1)
      ELSE IF CurrRec MOD (NoOfRecs DIV 100) = 0 THEN
        Window.UPDATE(1,(CurrRec / NoOfRecs * 10000) DIV 1);
    
      // Your processing here...
    UNTIL MyRecord.NEXT = 0;
    Window.CLOSE;
    
    This way doen't need another variable but an IF-Statement for the case that less than 100 records exists.
    Timo Lässer
    Microsoft Dynamics NAV Developer since 1997
    MSDynamics.de - German Microsoft Dynamics Community - member of [clip]
  • krikikriki Member, Moderator Posts: 9,115
    In the beginning, I used that system, but it has some drawbacks:
    E.g. you are processing 1.000.000 records and each records takes some amount of time to process, it means that you wont update your dialogbox for a lot of seconds (or minutes) giving the impression that Navision does not react and giving the user the bad idea of killing the Navision session.
    With the 1 second rule, even when the progressbar is not moving, the user will see that the session responds to his mouseclicks and will know the session is still working.
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • gerdhuebnergerdhuebner Member Posts: 155
    kriki wrote:
    ... it means that you wont update your dialogbox for a lot of seconds (or minutes) giving the impression that Navision does not react...
    ... and even more, there is no possibility to cancel during that interval.

    This is a really good argument for the 1s method.

    So in the long run, I think the 1s method is the safest one (though not the fastest one) and should be used throughout, because in the case of a fast process the lack of too few actualisations of the bar is negligible (perhaps the 1s could be changed to 500ms) - in the case of a long lasting process, however, the possibility to cancel at any time (or any second) is by far more import.

    Nevertheless I will present here the ultimate progress bar, as far and safe as possible :wink:
    ----------------------------- OnPreDataItem ---
    DiagProgress.OPEN('@1@@@@@@@@@@@@@@@@@@');
    
    // --- evtl. put your code here
    
    NoOfRecs := COUNT;
    NoOfRecsProgress := NoOfRecs DIV 100;
    Counter := 0;
    NoOfProgressed := 0;
    TimeProgress := TIME;
    
    ----------------------------- OnAfterGetRecord ---
    Counter := Counter + 1;
    
    IF (Counter >= NoOfRecsProgress) OR (TIME - TimeProgress > 1000) THEN BEGIN
      NoOfProgressed := NoOfProgressed + Counter;
      DiagProgress.UPDATE(1,ROUND(NoOfProgressed / NoOfRecs * 10000,1));
      Counter := 0;
      TimeProgress := TIME;
    END;
    
    // --- evtl. put your code here
    
    ----------------------------- OnPostDataItem ---
    // --- evtl. put your code here
    
    DiagProgress.CLOSE;
    

    Some Remarks:
    The TIME function is called twice, if the progress bar is to be updated - this could be avoided by a further time variable.
    In the IF statement always both conditions of the OR expression are evaluated (even if the first one already gives TRUE)...
  • DenSterDenSter Member Posts: 8,307
    This type of discussion is awesome, I love it when people contribute their ideas, and show how you can do things in more than one way. :mrgreen:
  • krikikriki Member, Moderator Posts: 9,115
    I think your ultimate progressbar is a little overkill.

    To be honest, when I defined my progressbar a few years ago, I also thought about that system, but like you said, Navision ALWAYS tests both expressions and I wanted a dialogbox that is always useful and not too complex to use. Also when I am looping an internal variable with some calculations on it without any I/O operations. In this case it gets important that the processor can work as fast as possible. Meaning give it as little as possible overhead.
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • TohilTohil Member Posts: 1
    In some developments I've seen a runtime DivByZero error using the progressbar, due to the lack of control on intProgressTotal variable: I prefer to set it to Count+1 in my code.

    ...but I'm quite sure that in standard code this situation is always checked! O:)
    Marco Silvestri
  • krikikriki Member, Moderator Posts: 9,115
    Tohil wrote:
    In some developments I've seen a runtime DivByZero error using the progressbar, due to the lack of control on intProgressTotal variable: I prefer to set it to Count+1 in my code.

    ...but I'm quite sure that in standard code this situation is always checked! O:)
    If COUNT is 0, you shouldn't even get into that code that divides by the total.
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • DenSterDenSter Member Posts: 8,307
    Then you catch the total number of records and you exit before even starting the loop. Logically, that wouldn't even make sense... if there are no records to process, then you sholdn't even go IN the loop, so you SHOULD never have that error.

    In code it would be something like:
    MyRec.SETFILTER(whatever fields, whatever values
    IF MyRec.FINDSET THEN BEGIN
      // prepare the dialog box with the progress bar
      // initialize variables, one of them would be
      NoOfProgressed := 0;
      NoOfRecords := COUNTAPPROX;
      REPEAT
        NoOfProgressed += 1;
        // update progress bar
      UNTIL NEXT = 0;
    END;);
    
    So if there are no records in the record var, it simply never executes the progress bar, and you never get divide by 0 errors.
  • krikikriki Member, Moderator Posts: 9,115
    DenSter wrote:
    Then you catch the total number of records and you exit before even starting the loop. Logically, that wouldn't even make sense... if there are no records to process, then you sholdn't even go IN the loop, so you SHOULD never have that error.

    In code it would be something like:
    MyRec.SETFILTER(whatever fields, whatever values
    IF MyRec.FINDSET THEN BEGIN
      // prepare the dialog box with the progress bar
      // initialize variables, one of them would be
      NoOfProgressed := 0;
      NoOfRecords := COUNTAPPROX;
      REPEAT
        NoOfProgressed += 1;
        // update progress bar
      UNTIL NEXT = 0;
    END;);
    
    So if there are no records in the record var, it simply never executes the progress bar, and you never get divide by 0 errors.
    Exactly my point!
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • SEBruhnSEBruhn Member Posts: 8
    My humble suggestion is this (simpler than using the timer):
    dWindow.OPEN('Campaigns processed:                @1@@@@@@@@@\'
                        +'Pending orders:             @2@@@@@@@@@\'
                        +'Posted orders:              @3@@@@@@@@@\'
                        +'Credit Memos:               @4@@@@@@@@@\'
                        +'Press Esc to abort');
    
    CurrentRecordNo += 1;
    
    IF (CurrentRecordNo MOD 500) = 0 THEN
       dWindow.UPDATE(1,ROUND(CurrentRecordNo /  NoOfRecords * 10000,1));
    

    The important part is MOD 500. Set another number if the expected number of records in the table justifies it.

    I normally use COUNTAPPROX even though the number can be out of sync. I find that it's not that important in reports.
  • krikikriki Member, Moderator Posts: 9,115
    SEBruhn wrote:
    My humble suggestion is this (simpler than using the timer):

    [code]
    dWindow.OPEN('Campaigns processed: @\'
    +'Pending orders: @\'
    +'Posted orders: @\'
    +'Credit Memos: @\'
    +'Press Esc to abort');

    CurrentRecordNo += 1;

    IF (CurrentRecordNo MOD 500) = 0 THEN
    dWindow.UPDATE(1,ROUND(CurrentRecordNo / NoOfRecords * 10000,1));

    The important part is MOD 500. Set another number if the expected number of records in the table justifies it.

    I normally use COUNTAPPROX even though the number can be out of sync. I find that it's not that important in reports.
    My first try was like that, but sometimes, you have 50 records and each take 10 seconds to process, so you don't see moving the dialogbox.
    And you can have 1.000.000 loops going at 1.000 per second and if you have a to low "MOD n", it slows down a lot. You can check it with this:
    [code]FOR int := 1 to 1000000000 DO BEGIN
    IF (int MOD 500) = 0 THEN
    dWindow.UPDATE(1,ROUND(int / 1000000000 * 10000,1));
    END;[/code]
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • BeliasBelias Member Posts: 2,998
    I love this solution kriki, but i noticed one thing while developing a process for nav 2009 3tiers client.
    the opening of the progress bar (obviously, not only with your solution :) ) takes some time, and it is useless when the process takes less than 2 seconds for example...based on the design of the process, and based on how much time does a single loop takes (mine is to read some lines in a table and process them, one line takes about 0.1 secs) we can deny the opening of the dialog box at all.
    repeat
          IF INTProgressCounter = 5 THEN BEGIN
            DLGProgressDialog.OPEN('#1##############\@2@@@@@@@@@@@@@@@@@@@@@@@@@\',TXTDialogText,INTProgress);
          END;
          IF INTProgressCounter > 5 THEN BEGIN
            IF TMTimeProgress < TIME - 1000 THEN BEGIN           //update the dialog box every second
              TMTimeProgress := TIME;
              INTProgress := ROUND(INTProgressCounter/INTProgressTotal * 10000,1);
              DLGProgressDialog.UPDATE;
            END;
          END;
        INTProgressCounter += 1;
    until condition = true;
    IF INTProgressCounter > 6 THEN  //6 because the counter is increased after the process...viceversa, it would be 5
      DLGProgressDialog.CLOSE;
    
    No further variables to add to the original solution :wink:
    -Mirko-
    "Never memorize what you can easily find in a book".....Or Mibuso
    My Blog
  • krikikriki Member, Moderator Posts: 9,115
    If you know it is fast (less then a few seconds), you can avoid using a progressbar. After all: a progressbar serves only to show the user something is happening.

    But if you don't know how much time each loop takes, you can't use your code. If each loop takes 20 seconds and you have only 3 loops to do, the total is 60 seconds, but in your case nothing will be shown.....
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • BeliasBelias Member Posts: 2,998
    sure, that's why i specified that the developer must know how much a single loop takes...
    in my process, the user selects "n" lines from a list and then process these lines: he can choose 3 or 100 lines...
    -Mirko-
    "Never memorize what you can easily find in a book".....Or Mibuso
    My Blog
  • kinekine Member Posts: 12,562
    Just one tip: Instead TIME datatype use the DATATIME, else it will freeze when running over midnight... :-) it is known problem in standard Cost Adjustment batch...
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • gerdhuebnergerdhuebner Member Posts: 155
    kine wrote:
    Just one tip: Instead TIME datatype use the DATATIME, else it will freeze when running over midnight... :-) it is known problem in standard Cost Adjustment batch...
    Well, good idea, but what about adjustment of summer time? If a dataport accidentally runs on 31/10/2010 at 2.59 a.m. (GMT + 1.00) it may also freeze for one hour... :-k
  • kinekine Member Posts: 12,562
    But only for on hour... :-) and it is more acceptable than each midnight... 8)
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • ppavukppavuk Member Posts: 334
    I newer was satisfied with standard progress bar behaviour, nor "time" solution so, what about:
    IF ((RecNo MOD (TotalRecNo DIV 100)) = 0) THEN 
        Window.UPDATE(1,ROUND(RecNo / TotalRecNo * 10000,1));
    

    no new variables, DIV and MOD are fast enough.
  • reijermolenaarreijermolenaar Member Posts: 256
    Cool! 8)

    Thanks.
    Reijer Molenaar
    Object Manager
  • ppavukppavuk Member Posts: 334
    As usual, my code fails :)

    If TotalRecNo < 100 this cause division by 0 error
    so,
    IF (TotalRecNo > 100) THEN
      IF  ((RecNo MOD (TotalRecNo DIV 100)) = 0) THEN
        Window.UPDATE(1,ROUND(RecNo / TotalRecNo * 10000,1));
    
  • reijermolenaarreijermolenaar Member Posts: 256
    Ah, of course, with small number it isn't work very accurate. :-k
    If you have a total of 199, the progress bar will go to 50%.

    This one is better:
    IF (RecNo DIV (TotalRecNo / 100)) <> ((RecNo - 1) DIV (TotalRecNo / 100)) THEN
      Window.UPDATE(1, ROUND(RecNo / TotalRecNo * 10000, 1));
    
    Reijer Molenaar
    Object Manager
  • reijermolenaarreijermolenaar Member Posts: 256
    the progress bar will go to 50%.
    Ahum, just the opposite!
    It will update the bar 200 times.
    Reijer Molenaar
    Object Manager
  • ppavukppavuk Member Posts: 334
    Actually, when you got 200 records you do not need to deal with window at all :) can't imagine a process which will process 200 records in some time, which can be spotted on UI :) Or, if you need to update window - just update unconditionally. All this code make sense if you got big enough number of records.

    And you are right, if recordcount is less than 100 my code will not update progressbar at all, if it is between 100 and 199 we will got update for each record, but in all other cases this will be updated only when whole next percent is reached. no, only if record count is more than 999 the progress bar will be updated each percent. Everything below 1000 will be wrong :)

    Apparently, there is no way to make progressbar accurate :) Look your code is accurate enough. Thanks!
  • manisharma31manisharma31 Member Posts: 285
    How to increase the size of the window dialog box.
    Text001 = Generating data for the month of April-2013

    When I run the report the dialog box show only Generating data for the.
    Regards,
    Manish
  • krikikriki Member, Moderator Posts: 9,115
    'Campaigns processed: @\'
    =>
    'Campaigns processed: @\'

    and if it is not enoug, add more @.
    Regards,Alain Krikilion
    No PM,please use the forum. || May the <SOLVED>-attribute be in your title!


  • manisharma31manisharma31 Member Posts: 285
    I am using ## for showing in what is going.
    But adding more # does not works.
    Regards,
    Manish
Sign In or Register to comment.