Alternative SAP GUI Progress Indicator

If you have done much ABAP programming, you will probably have made use of the SAPGUI_PROGRESS_INDICATOR function at some point or other, which can be used to display the progress of a task. Desiring an alternative progress indicator, I set out to find an ActiveX object that provided a progress bar which I could control with OLE from ABAP.

Before I get into the solution, let me tell you that I searched high and low for such a component. I was at first convinced that something of the kind must be shipped standard with Windows, but I was simply unable to find an ActiveX dialog (not just a control) that would fulfil this need. I then even looked at using the objects that ship with SAPGUI (because ideally you want something the user already has on their machine) , hoping to make use of the bar chart graphic OCX controls. All to no avail.

Finally I stumbled on a little post that pointed to JSProgBr.exe, a free little component by JSWare, with a very liberal license. It’s light and fits in your pocket. It has a little drawback in that you cannot explicitly set the value of the progress bar, only advance it a unit (up to 30 units). In the code below I show you how to use this, though, to create a progress bar for your ABAP SAP GUI application in which you can specify a percentage of completion.

After querying the developer on this, he has very kindly replied and added a method to explicitly set the units on the newer version of his progress bar. I haven’t had time to look at it, but it adds a few more features, allowing you to also set the look and feel of the progress bar. So thanks, Joe, if you are reading this, for your hard work and for making this available for free!

Using this free component, and by wrapping the necessary OLE calls in an ABAP class, you have a nice little alternative to SAPGUI_PROGRESS_INDICATOR (when the drawbacks are not accounted for).

To whet your appetite or let you decide if this is maybe not what you want, I will give you an example usage of the finished solution with and show you the output. The following ABAP snippet shows us using the new class, ZCL_PROGRESS_BAR:

data: lo_progbar type ref to ZCL_PROGRESS_BAR.
data: lv_percent type i value 0.

create object lo_progbar.

lo_progbar->update( title = 'Waiting...' caption = 'System asleep' ).
wait up to 1 seconds.

lo_progbar->update( title = 'Working...' caption = 'System is busy' ).

* Task
do 5 times.
  add 20 to lv_percent.
  if lv_percent = 80.
    lo_progbar->update( caption = 'Almost there...' ).
   endif.

  lo_progbar->update( percent = lv_percent ).
  wait up to 1 seconds.
enddo.

* Free resources on server
lo_progbar->dispose( ).

This brings up a nice little progress dialog like this over your SAP GUI (OK, well, this one wasn’t produced by the code above):

Unfortunately, the window is merely on top of all windows, and not a modal window over your SAP GUI session window. This could be unfortunate if you have a very long-running process and have to switch to other windows, because this progress bar would always be in the way. Nonetheless, it’s usable, and I’m sure you could find instances of where it would be appropriate.

If you spent some money, you could probably find an ActiveX object that can do this, but then you are not likely to read about it on this blog :-)

Now another challenge is that we are not using something already available on the user’s desktop, so we have to do some work to get it there, and that makes our solution a bit more involved. As described below, you have to follow some additional steps to make the JSProgBr.exe executable available on the user’s desktop. What I do is to upload objects into the SAP Web Repository and then download them on to the SAP GUI Presentation Server (the user’s desktop) as needed.

I proceeded as follows: First, I downloaded and unzipped progbar.zip to a folder on my computer.

Next, I uploaded JSProgBr.exe as a binary object with transaction SMW0 (SAP Web Repository).

The flow of this transaction is not very intuitive. You must first execute and get a list of selected objects, and on that screen, you can then create and upload an objet to the SAP Web repository. Here is my entry for the object after I uploaded it:

Note that you might have to add an entry to your MIME settings in SMW0 from the Object Display screen by choosing Settings -> Maintain MIME Types from the menu, because if SMW0 does not know about the extension, you won’t be able to upload the object. Here is the entry I created:

After you have done this, we can start coding the class. When you create the class, you will need to include type group OLE2 in the forward declarations in the attributes of the class.

In my constructor, I first check to see whether the executable is present on the user’s desktop, and if not, download it. I use the user’s temporary folder for this. After that, I check if the necessary registry entries exist, and if not, I run the downloaded executable once to set create registry entries.

The reason I use the user’s temporary folder is because he or she may not have access on the machine to write somewhere else. However, this may not be a good idea if the machine is shared by several users, as once the registry entries are in place, the second user will probably not have access to the executable in the first user’s folder (as referred to by the registry entries). A workaround is to run the executable each time on start-up. Mind you, I haven’t actually checked whether a non-administrator can run the executable so that the registry entries can be created.

After having established that the executable and registry entries are present, I instantiate an OLE controller object, from where I can start making calls. All of this makes the code quite lengthy.

method CONSTRUCTOR.

  data: lv_regval type string.
  data: lv_exepath type string.
  data: lv_bool type abap_bool.

* Determine temp path where user will have read access
  call method cl_gui_frontend_services=>get_temp_directory
    changing
      temp_dir = lv_exepath
    exceptions
      others   = 4.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
               with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
      into message.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

* Need to flush automation queue to get value
  call function 'FLUSH'
    exceptions
      cntl_system_error = 1
      cntl_error        = 2
      others            = 3.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
      into message.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

* Determine path where executable should be
  concatenate lv_exepath '\JSProgBr.exe' into lv_exepath.

* Check whether the file exists
  call method cl_gui_frontend_services=>file_exist
    exporting
      file   = lv_exepath
    receiving
      result = lv_bool
    exceptions
      others = 5.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
               with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
      into message.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

* If the file does not yet exist on the client, download it
  if lv_bool ne 'X'.
    zcl_file_util=>mime_object_to_client(
      obj_name = 'Z_JSPROGBR'
      path     = lv_exepath ).
  endif.

* Determine whether CLSID exists on presentation server
  call method cl_gui_frontend_services=>registry_get_value
    exporting
      root                 = 0
      key                  = 'CLSID\{CBADC128-AC75-11D4-B233-EC91539FAD63}\ProgID'
    importing
      reg_value            = lv_regval
    exceptions
      get_regvalue_failed  = 1
      cntl_error           = 2
      error_no_gui         = 3
      not_supported_by_gui = 4
      others               = 5.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
               with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
      into message.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

* Need to flush automation queue to get value
  call function 'FLUSH'
    exceptions
      cntl_system_error = 1
      cntl_error        = 2
      others            = 3.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
      into message.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

* If the registry key does not yet exist, execute JSProgBr.exe
* once to put value into registry
  if lv_regval is initial.
    call method cl_gui_frontend_services=>execute
      exporting
        application = lv_exepath
        minimized   = 'X'
      exceptions
        others      = 10.
    if sy-subrc <> 0.
      message id sy-msgid type sy-msgty number sy-msgno
                 with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
         into message.
      raise exception type zcx_gen_appl
        exporting message = message.
    endif.
  endif.

* Instantiate ActiveX control for first-time use
  new_bar( ).

* If at this point we don't have a handle on the object, give error
* We could, alternatively, at this point, fall back on using
* SAPGUI_PROGRESS_INDICATOR instead of the user having to do it
  if jprogbar-HEADER is initial.
    message = 'Error during setup of progress bar'.
    raise exception type zcx_gen_appl
      exporting message = message.
  endif.

endmethod.

Some things to note:

  • I have my own utility class which includes a method to download a MIME object (the .exe we uploaded) to the client. If you are interested, I will gladly reveal the code. (Just let me know in the comments. At some point I hope to share some more on the utilities I have created in ABAP and which I find useful).
  • I make use of a custom exception class which I use in many places to signal general application exceptions. It takes a message as part of its constructor, which I then read in the calling program.

You will find a raw dump of the code below (because you will need to know what the method interfaces look like). I would have gladly made use of SAPLink to provide the solution as a nugget or slinky file, but I haven’t managed to download a class with it, and I don’t see a plugin for classes either:

Now we look at the protected new_bar() method:

method new_bar.

* Because this may be called multiple times first, clear OLE object
  if not jprogbar-header is initial.
    free object jprogbar.
  endif.

* Constructor should ensure we can use the automation object
  create object jprogbar 'JSProgBr.Bar'.

* This bar has not been shown yet
  clear visible.

* To keep track of units on bar used:
  units = 0.

endmethod.

As part of the workaround I describe above for not being able to set the value of the progress bar explicitly, I put this code in a central method which can be called again to re-instantiate the progress bar if necessary.

Now the user must be able to set texts on and change the value of the progress bar. For that, we have the update() method:

method update.

* Set caption and title only if supplied
  if caption is supplied.
    me->caption = caption.
*    set property of jprogbar 'Caption' = caption no flush.
  endif.
  if title is supplied.
    me->title = title.
*    set property of jprogbar 'Title' = title no flush.
  endif.

  if percent is supplied.
* Because we can only advance the timer up to 30 units, carefully
* calculate
    data: lv_units type i.
* Calculate number of units that should be filled
* The maximum supported by JSProgBar.exe is 30
    lv_units = 30 * percent / 100.
* Workaround because bar supports only an Advance method:
* If desired units is less than current units, reinstantiate
* bar (which requires us to keep track of the current title/caption)
    if lv_units < units. * To go back a certain percentage, have to reinstantiate prog bar       new_bar( ).     endif. * The number of units to advance will be new units minus existing units     subtract units from lv_units. * Advance calculated number of units     do lv_units times.       call method of jprogbar 'Advance' no flush.     enddo. * Update our count of the number of units on the bar     add lv_units to units.   endif. * The first call to this method after creating a new progress bar * will make the progress bar visible   if visible ne 'X'.     call method of jprogbar 'Show' no flush       exporting         #1 = 1  "Center on screen = true         #2 = 1. "On top of windows = true     visible = 'X'.   endif. * Set caption and title to whatever they were last set to   set property of jprogbar 'Caption' = me->caption no flush.
  set property of jprogbar 'Title' = me->title no flush.

* Flush automation queue
  call function 'FLUSH'.

endmethod.

In here, we set the title and caption if either has been given. Also, we convert the percentage given to a the number of units out of 30 that we can display on the bar. If we find that we have fewer units than are currently displayed, we have to create a new instance of the bar, so we call new_bar() again. Then we count how many units we must add to the bar, and call the Advance method on the bar that many times.

Also, there is a dispose () method which the calling program can invoke, which simply clears the OLE controller object:

method DISPOSE.
* Free resources
  free object jprogbar.
endmethod.

Lastly, here are the attributes that are needed throughout the method, and without which, the class wouldn’t compile. Of course, that by itself is useless too, without the method signatures, so you will still have to refer to the attached dump of the class.

Well, that’s pretty much it. Here you will find a raw dump of the class. If someone can explain to me how to create a nugget for an ABAP class with SAPLink, I would appreciate it, because then I can include the dependent utility and exception class.

ZCL_PROGRESS_BAR.txt

Tags: , , ,

  • kim

    great job!!
    could you open source “zcl_file_util” ??
    i have to delvelop custom activex control
    thank you!!

  • Martin

    Hello Kim, sorry for the late reply, and I am very sorry, but the code in question is on a system at a previous client, and I never bothered to take it along. However, I just took a look and you can do the same thing with function module DOWNLOAD_WEB_OBJECT. Just pass KEY-RELID = ‘MI’, KEY-OBJID = [your blob name from SMW0] and DESTINATION = [path on the desktop to download]. Seems a separate utility function is not even necessary!

  • Jon

    Hi all,

    Thanks for sharing this, anyone could prepare a NUGGET for SAP Link ?

    Jon