When Should I Use ui.Interface.refresh?
When should I use ui.Interface.refresh?
Support have told me to add a ui.Interface.refresh, why?
Support have told me to remove a ui.Interface.refresh, why?
The ui.Interface.refresh method is one of the more polarising pieces of Genero code. I say this because historically with the GDC development, and lately the GBC development, issues have been found to occur with a ui.Interface.refresh statement, or it maybe that the workaround is to add a ui.Interface.refresh statement. The GDC/GBC product developers might privately wish that the ui.Interface.refresh method had never been invented because of the times they have investigated an issue and found that the case they hadn’t considered is where a developer has used one or more ui.Interface.refresh methods in unexpected or unnecessary places.
To have a good understanding of the ui.Interface.refresh statement, it is important you understand the Dynamic User Interface and the Front End View Protocol
In the expanded view of the Front-End protocol, note the two way communication between the front-end workstation, and the back-end application server. The back-end application server sends to the front-end the changes to the AUI Tree (step 6), the workstation sends back the users response (step 4). Step 6, the changes to the AUI Tree are only sent from the back-end to the front-end when a response from the user is expected. The back-end effectively buffers up the user interface changes and sends them to be rendered only when the user is expected to do something, that is the 4gl code has reached an INPUT, DISPLAY ARRAY, MENU, INPUT ARRAY, CONSTRUCT, PROMPT, DIALOG etc. Other user interface syntax such as OPEN WINDOW, CLOSE WINDOW, MESSAGE, ERROR and methods such as ui.Form.setFieldHidden() don’t affect the visual user interface straight away, but are only rendered when the 4gl code is at the dialog syntax and ready for a user response.
This optimisation to minimise the network traffic has a side-effect in that what if we weren’t expecting the user to make a response then this user interface change will not be immediately sent and rendered. This is most commonly seen in code such as
FOREACH ... ... LET count = count + 1 MESSAGE SFMT("Processing record %1 OF %2", count, total) END FOREACH
As the FOREACH loop is processed, we are not expecting a response from the user. so the AUI Tree changes are not immediately sent to the front-end. The developers intention might have been to display “Processing record 1 of 100”, “Processing record 2 of 100” as the process runs, but all the end-user will see is no change to the UI except at the end they will see “Processing record 100 of 100” when the logic reaches the next statement requiring a response from the user.
The ui.Interface.refresh method was introduced for scenarios such as this. Its usage means that the UI changes will be sent to the front-end immediately for it to display. It will render these changes and won’t wait for the user to make a response, and the 4gl code will carry on. In the protocol diagram it effectively forces step 6 (and then 2) to occur without waiting for the user to do something (step 3 and 4) and so the control is back with the runtime system and processing continues.
By adding the call, that code then becomes …
FOREACH ... ... LET count = count + 1 MESSAGE SFMT("Processing record %1 OF %2", count, total) CALL ui.Interface.Refresh() END FOREACH
… so now the user will see “Processing record 1 of 100”, “Processing record 2 of 100”. However there is a problem. Chances are your system is capable of processing 100’s if not 1000’s of these transactions a second. The user has no need to see each individual message, and worse these message transmissions are clogging up your network. So for code like this we expect your code to be more like …
FOREACH ... ... LET count = count + 1 MESSAGE SFMT("Processing record %1 OF %2", count, total) IF count MOD 1000 = 0 THEN CALL ui.Interface.Refresh() END IF END FOREACH
… that is only update the user interface a fraction of the occurrences. The value 1000 in the above code is only an example. You should replace 1000 with a number that is appropriate for your system and what the processing is doing. As a guide, a movie camera operates at 24 frames per second (read here for an explanation of why) so it is a waste to have ui.Interface.refresh called more than 24 times a second, I normally aim for between 1 and 24 times a second.
That was the intended usage of ui.Interface.refresh(). In practise there are a few other places its use in my opinion is acceptable.
When displaying a PROGRESSBAR, you have similar requirements to update the display of the PROGRESSBAR before the next user interaction. Again you don’t want to do too many ui.Interface.refresh() and clog up the network, you only want to do a ui.Interface.refresh if the user will notice the difference.
WHILE ... DISPLAY value TO progress_bar IF value != last THEN -- Only display if value different, -- also consider how often this will occur, a MOD may also be appropriate CALL ui.Interface.refresh() LET last = value END IF END WHILE
Anywhere you have an ON TIMER or an ON IDLE that has some user interface, you typically will want a ui.Interface.refresh to force a display e.g.
ON IDLE 60 LET minutes = minutes + 1 MESSAGE SFMT("Inactive for %1 minutes", minutes) CALL ui.Interface.refresh()
When opening a window to allow the user to interrupt a long running process, you will want to use ui.Interface.refresh after the OPEN WINDOW so that the window is open whilst the long running process is operating e.g.
OPEN WINDOW interrupt WITH FORM "lib_interrupt" ATTRIBUTES(STYLE="dialog") CALL ui.Interface.refresh()
With ERROR’s you typically want them to appear in the window that generated the error. So if you opened a window after an error, typically with a display array to select a valid value …
AFTER FIELD fieldname IF NOT valid() THEN ... ERROR ... ... OPEN WINDOW ...ATTRIBUTES(STYLE="dialog") ... DISPLAY ARRAY ...
…. your expectation is most likely for the ERROR to be displayed in the same window as fieldname, not for it to be displayed in the dialog window that is opened. So again ui.Interface.refresh can come to your rescue and can force the display of ERROR to the current window before the next window is opened.
AFTER FIELD fieldname IF NOT valid() THEN ... ERROR ... CALL ui.Interface.Refresh() ... OPEN WINDOW ...ATTRIBUTES(STYLE="dialog") ... DISPLAY ARRAY ...
There are some customers who have wrapped the use of ERROR and MESSAGE in a library function so that there is a ui.Interface.refresh as part of the ERROR and MESSAGE. In a way this makes sense as with the text user interface, the error and message is display straight away. So they code
FUNCTION my_error(error_text STRING) ERROR error_text CALL ui.Interface.refresh() END FUNCTION
If you are to do this I would suggest a flag or additional function to control if ui.Interface.refresh() is executed so that you have the option of not executing a ui.Interface.refresh() e.g.
FUNCTION my_error(error_text STRING, refresh BOOLEAN) ERROR error_text IF refresh THEN CALL ui.Interface.refresh() END IF END FUNCTION
This use of ui.Interface.refresh() inside a library function is where we see a lot of unnecessary instances of a CALL ui.Interface.refresh(). For instance if you have a library function to hide a field and you have a ui.Interface.refresh() in it, then if you show/hide 20 fields at once, this will result in 20 ui.Interface.refreshes being executed. This is a place where you should not have a ui.Interface.refresh(), place the ui.Interface.refresh if necessary at the end of the calls to the library functions and chances are it ties in with one of the reasons above.
ui.Interface.refresh used to be used as a hack to ensure that a certain folder page was visible. Since the introduction of the ui.Form.ensureElementVisible this should not be necessary.
So in summary, ui.Interface.refresh() is a useful method but please curb your enthusiasm. Understand and use it only where it is appropriate and for what it is designed for.