The VDF Status Panel
What It Is and How to Customize It

A Data Access Worldwide White Paper
by John Kramel

August 5, 2002
Last Edited: August 5, 2002

Note: As of Visual DataFlex 12.0, the external status panel discussed in this white paper has been replaced by a new internal status panel. This panel is faster and much easier to customize. In most cases, the new panel should serve as "no change required" replacement for the old one. The old status panel is still available, it's just been renamed. Please see the Visual DataFlex help in Visual DataFlex 12.0 for cProgressStatusPanel, which is the new status panel class.

Contents

Introduction
The VDF Status Panel is used by both the Report and the BusinessProcess classes to enable user interface for these processes. The user is able to view the progress of, and to cancel, the process. The Help files and the product documentation describe how to use this status panel with reports and business processes. This White Paper goes beyond that to describe what is going on behind the scenes. It also describes how to create your own status panels that have the customized characteristics your application may require.

The visible Status Panel, or Sentinel program, is actually a separate program altogether, rather than a popup from the host process. The Host and the Sentinel programs share a common section of memory. The Host process is able to write to that piece of memory, where the Sentinel reads that information and displays it. Likewise, the Sentinel can write messages, such as “Cancel”, to the shared memory and the Host process will read that and handle it.

The default Status Panel program that is shipped with Visual DataFlex is named “Sentinel.EXE” (or “Sentinel.VD7, in VDF revision 7).


Figure 1: The Sentinel program, with its defined sections identified.

Shared Memory
The usable portion of shared memory used for this purpose is one kilobyte in length. The default mapping for this shared memory is contained in a file called Sentdat.pkg. The entire contents of this file are reproduced here:

//
//  Sentinel/Host common shared data
//

DEFINE BUTTONSTART   for 10
DEFINE BUTTONLENGTH  for 25

DEFINE CAPTIONSTART  for 35
DEFINE CAPTIONLENGTH for 250

DEFINE TITLESTART    for 285
DEFINE TITLELENGTH   for 250

DEFINE MESSAGESTART  for 535
DEFINE MESSAGELENGTH for 250

DEFINE ACTIONSTART   for 785
DEFINE ACTIONLENGTH  for 250

This default mapping is thus partitioned into 5 sections: Button, Caption, Title, Message and Action. These 5 sections correspond to those identified in Figure 1, above. Each side – the Host side running the BusinessProcess or Report and the Sentinel side displaying the panel – has its own program interface to this data. By default, each program interface uses, and is configured to, the default mapping contained in Sentdat.Pkg.

In this paper, all code examples pertaining to the Sentinel side appear in RED. All code examples pertaining to the Host side appear in GREEN.

The Host Program Interface
The Host interface is contained in a file named  Statpnl.pkg. It defines a class called StatusPanel. StatusPanel contains a paired “Procedure Set” and “Function” for the contents of each of the 5 defined sections of shared memory. With these methods, the host program can Set or Get the following:

Caption_Text
Message_Text
Action_Text
Button_Text
Title_Text
Typically, the host program will Set these values. When the contents of the shared memory area is changed by the Host, using one of these Set methods, the Sentinel receives a message called Data_Change. Data_Change passes the “Mode” of the change. The mode corresponds to, by default, one of the 5 defined segments of shared memory, or: HOST_DATA_ACTION, HOST_DATA_MESSAGE, HOST_DATA_CAPTION, HOST_DATA_TITLE or HOST_DATA_BUTTON. A 6th mode, HOST_DATA_ALL, defines the contents of the entire shared memory segment.

Statpnl.pkg also creates an object called Status_Panel. Both the BusinessProcess and the BasicReport classes are set up to use this predefined Status_Panel object by default. This can be changed, so that your customized status panel is used instead.

The Sentinel Program Interface
The Sentinel program is created from the SentinelPanel class contained in DfSent.pkg. Class SentinelPanel is set up as a ModalPanel. It defines the methods that are used to identify when the shared memory area has been changed by the Host program and to fetch the value of the pre-defined segments of this shared memory. It defines a SentinelButton class to Close the panel.

The Button_Text and Caption_Text label properties have pre-defined values. Button_Text is set to “Cancel” by the sentinel’s base class. Caption_Text is set by the host program. The BusinessProcess class sets it to “Running Process” and the BasicReport class sets it to "Printing Report”. These caption values may be changed in the host program without any customization of the sentinel.

The Cancel Button Is Optional
The status panel’s Cancel button is optional, without making any customizations. This is maintained from the Host side using the Allow_Cancel_State property. This property defaults to True in Reports, where it would usually make more sense to allow canceling. In Business Processes, it defaults to False, as most such processes are expected to run to completion. By setting it to False in the Initialize_StatusPanel procedure, a command line parameter, "-c0", is passed to the sentinel program when it is started. “-c1” is passed if it is set to True. The Sentinel, if receiving this parameter on the command line, will display the button based upon whether the number following the “c” is a 0 or a 1, and it will size it’s panel accordingly.

Customization
The status panel can be customized in a number of ways. In order to change the way the sentinel program looks or what it does, start by making a copy of Sentinel.Src (found in the Pkg subdirectory of the VDF install) and naming it something else, say MySentinel.Src. Changes are then made directly to that source code. The SentinelPanel methods may be augmented or overridden, properties may be added, objects may be added or removed, etc. On the Host side, it may be required to augment or override methods of the StatusPanel class and utilize these in the host program.

Simple Customization
The simplest kind of customization is to alter only the appearance of the panel but keep the default methods intact. A developer may, for instance, wish to modify the sentinel program so that the standard sections are displayed differently. Let’s say that the requirements are that the Title and the Message are displayed side by side, so that they read, e.g., “Now Processing Store Number 55”, where only the number of the store changes. The Title could have the value “Now Processing Store Number” and the Message could contain the store number. Only the Message would have to change on a store break. This would be quite easy to do. First, make a copy of Sentinel.Src under a different name. Only a few changes would be required. Sentinel.Src contains the following objects:

   Object Title_Txt is a CenterTextBox
       Set Location to 10 0
    End_object

    Object Message_Txt is a TextBox
        Set location to 25 10
        Set Auto_Size_State to False
        Set size to 20 150
    End_object

These would be changed to the following:
    Object Title_Txt is a TextBox
       Set Location to 10 27
       Set label to "Now Processing Store Number"
    End_object

    Object Message_Txt is a TextBox
        Set location to 10 126
        Set Auto_Size_State to False
        Set size to 10 50
    End_object

And the Action textbox could be made a CenterTextbox and moved up for a better appearance. These few changes would yield a status panel that looks like Figure 2.


Figure 2: Simple Customization 1

In order to cause this customized sentinel panel to be used rather than the standard one, only one change must be made in the Host program.  The Sentinel_Name of the Status_Panel object must be set. In the StatusPanel class, Sentinel_Name is set to “Sentinel”. This must be changed to the name of your customized sentinel program. In the host program, add the following line.

Set Sentinel_Name of (Status_Panel(self)) to "MySentinel"
As you know, the supplied status panel has only one Action field. Thus, by default, only one progress section is possible. Without altering any methods, you could improve on this by increasing this to three. You could create an alternate sentinel program in which the Title, Message and Action are equally configured and arranged. The result, as used in an application that may need it, might appear as in Figure 3. In a BusinessProcess, the Total Items would be updated using Set Process_Title, Total Sales with Set Process_Message, Sales Tax with Send Update_Status.


Figure 3: Simple customization 2

If necessary, the Caption value could be used, too, in order to enable a 4th line. In that case, the panel label would be set to a constant in the sentinel program.

This kind of customization could be taken a step further. What if the customer wished to see the posted values lined up in a column, rather than simply displayed immediately to the right of the identifier label? The developer could accomplish this easily by creating 6 textboxes rather than 3. The 3 on the left would contain the static text and the 3 on the right would contain only the values passed through the shared memory. Using static text, such a panel would be specific to the application. (However, see below about the optional command line parameters and consider how these could be used in order to supply the values for the identifier textboxes.)

Other kinds of simple changes that can be made to the sentinel without any alteration of methods might include the use of color or font changes, or the addition of text boxes with additional text information.

Customizing the Status Panel Methods for Specific Use
The appearance of the panel in Figure 3, above, may suit some applications adequately. However, what if the status panel needed 5 such lines, and not just 3 or 4? To enable this, the developer would need to alter the standard methods. This is not as difficult as it may seem. On the Host side, a custom StatusPanel object needs to be made. And a custom Sentinel program needs to be created in which 3 methods are augmented and several small new methods are added.

Essentially, what needs to be done is to remap, or sub-map, the shared memory segment with additional partitions to handle the additional parameters required. Looking at the mapping in Sentdat.pkg, we see that the Caption, Title, Message and Action areas are each allocated 250 bytes. This is really quite a lot of space. We could create a completely customized mapping. However, in this example we will simply sub-map the Action section into smaller parts. This allows us to, if needed, utilize the other sections in the way they are normally used.

Our goal is to create a status panel with the appearance shown in Figure 4. This  application requires us to display this panel during the process and then to leave it up for the user to examine after the process is concluded. We “cheat” a little by creating a dialog that matches our sentinel panel in every way except that the OK button is active. When the process is concluded, we load the values into the dialog labels and pop it up. Actually, we created the dialog first, using the IDE. We then merged it into a copy we made of of Sentinel.src in order to create the sentinel panel. This way, we were able to take advantage of the IDE’s WYSIWYG capabilities to create and align the textboxes.


Figure 4: Customized Sentinel for Specific Use

This panel contains 10 textboxes and one shadowed button. The 5 boxes on the left contain static text. The boxes on the right contain text values passed via the shared memory.

We need to identify the beginning position and the length of each section of shared memory we will be using. We have seen that the 5 default sections of memory are identified using Sentdat.Pkg. Both the Host and the Sentinel sides open this package. This is so that we have one common reference for these values. We will add our ancillary values to Sentdat.Pkg. We’ll add the following lines:

DEFINE P#START     for 785
DEFINE P#LENGTH    for 50

DEFINE PAMTSTART   for 835
DEFINE PAMTLENGTH  for 50

DEFINE S#START     for 885
DEFINE S#LENGTH    for 50

DEFINE SAMTSTART   for 935
DEFINE SAMTLENGTH  for 50

These 4 sections are actually sub-sections of the defined Action section. We have a section for Product items, one for Product amounts, and another pair for Service items and amounts. You might ask why we have not created a value for the total sales items (the first line in the panel. We don’t need one, as you will soon see.

In our sentinel program code, we must add some constant values for each of our new sections.  Here is the code, which we put directly after the Use DfSent.pkg:

Enum_List
    Define Host_Data_P# for 6
    Define Host_Data_PAmt
    Define Host_Data_S#
    Define Host_Data_Samt
End_Enum_List
We begin our numbering with 6, as DfSent has already enumerated 6 other constants in this series for 0 through 5.

Now, within our SentinelPanel object, we need to set a string property for each of our text values. Near the top of our object definition, we add:

    Property String  P#_Text       Public ""
    Property String  PAmt_Text     Public ""
    Property String  S#_Text       Public ""
    Property String  SAmt_Text     Public ""
Next, we augment the OnSentinelDataChange procedure. This is the procedure that is called whenever the Host writes to the shared memory. We’ll add code to handle the writes to the sections we’ve added.
    Procedure OnSentinelDataChange integer iBeg integer iLength
        integer Mode
        If iBeg eq P#START Begin
           Send Initialize_P#_Data
           Move HOST_DATA_P# to Mode
        End
        If iBeg eq PAMTSTART Begin
           Send Initialize_PAmt_Data
           Move HOST_DATA_PAmt to Mode
        End
        If iBeg eq S#START Begin
           Send Initialize_S#_Data
           Move HOST_DATA_S# to Mode
        End
        If iBeg eq SAMTSTART Begin
           Send Initialize_SAmt_Data
           Move HOST_DATA_SAmt to Mode
        End
        If Mode Send Data_Change Mode
        Forward Send OnSentinelDataChange iBeg iLength
    End_procedure
Now, we augment the Initialize_Shared_Data procedure:
    Procedure Initialize_Shared_Data
        Forward Send Initialize_Shared_Data
        Send Initialize_P#_Data
        Send Initialize_PAmt_Data
        Send Initialize_S#_Data
        Send Initialize_SAmt_Data
    End_procedure
Next, we have to add the 4 Initialize_***_Data procedures that we are sending in the augmentations above. These are all about the same. We show only one here:
    Procedure Initialize_P#_Data
        Local string sText
        get HostData P#START P#LENGTH to sText
        Set P#_Text to sText
    End_procedure
The remaining change to the standard Sentinel.src code is to write our own Procedure Data_Change to write the passed text to our value textboxes. Data_Change is called each time that the Host writes to shared memory.
    Procedure Data_Change integer Mode
        string sItems

        // We’re replacing this, so ignore it.
        If (Mode <> HOST_DATA_ACTION) Procedure_return

        // Here’s how we get the total Sales items.
        // Each time Data_Change fires, we have processed
        // 10 items (because that’s what we’ve told the
        // Host to do).
        Get label of (Items_Value(self)) to sItems
        Set Label of (Items_Value(self)) to (string((integer(sItems)) + 10))
        // Now we set the 4 values that were passed.
        Set Label of (PSales#_Value(self)) to (P#_Text(self))
        Set Label of (PSalesAmt_Value(self)) to (PAmt_Text(self))
        Set Label of (SSales#_Value(self)) to (S#_Text(self))
        Set Label of (SSalesAmt_Value(self)) to (SAmt_Text(self))
    End_Procedure

That’s all there is to it on the sentinel side.

Now for the Host side. This is considerably easier. We can do this right in the IDE. In the Outer-Component code, we add our StatusPanel object. Here it is, in its entirety. The code should be self-explanatory. We have simply added a property for each of our shared values and created a Procedure Set and a Function so that we may Set and Get these values:

Object PostStatus_Panel is a StatusPanel

    Property String  Private.P#_Text      Public ""
    Property String  Private.PAmt_Text    Public ""
    Property String  Private.S#_Text      Public ""
    Property String  Private.SAmt_Text    Public ""

    Procedure Set P#_Text string sText
       Send DoStatusPaneltoForeground
       Set Private.P#_Text to sText
       Set SentinelData of Desktop to sText P#START P#LENGTH
    End_Procedure // Set P#_Text

    Function P#_Text returns string
       Function_Return (Private.P#_Text(self))
    End_Function // P#_Text

    Procedure Set PAmt_Text string sText
       Send DoStatusPaneltoForeground
       Set Private.PAmt_Text to sText
       Set SentinelData of Desktop to sText PAMTSTART PAMTLENGTH
    End_Procedure // Set PAmt_Text

    Function PAmt_Text returns string
       Function_Return (Private.PAmt_Text(self))
    End_Function // PAmt_Text

    Procedure Set S#_Text string sText
       Send DoStatusPaneltoForeground
       Set Private.S#_Text to sText
       Set SentinelData of Desktop to sText S#START S#LENGTH
    End_Procedure // Set S#_Text

    Function S#_Text returns string
       Function_Return (Private.S#_Text(self))
    End_Function // S#_Text

    Procedure Set SAmt_Text string sText
       Send DoStatusPaneltoForeground
       Set Private.SAmt_Text to sText
       Set SentinelData of Desktop to sText SAMTSTART SAMTLENGTH
    End_Procedure // Set SAmt_Text

    Function SAmt_Text returns string
       Function_Return (Private.SAmt_Text(self))
    End_Function // SAmt_Text

End_Object

Within our BusinessProcess object, we set the Sentinel_Name to the name of our customized Sentinel program:

  Set Sentinel_Name of (PrtInsStatus_Panel(self)) to "PostSent"

We set  the Status_Panel_Id property to the StatusPanel object we defined just above.

  Set Status_Panel_Id to (PostStatus_Panel(self))

Within the OnProcess procedure, every 10 items that we process, we write our values to the shared memory. Here is one such setting:

    Set P#_Text of PostStatus_Panel to (string(iPS#))

That’s all there is to it. Using these techniques, many specific-use Host-Sentinel pairs may be created.

Customizing the Status Panel Methods for Generalized Use
It may be desirable to create a generalized sentinel and StatusPanel pair. Using the techniques described just above, you may create sections within the shared memory to pass the text that is displayed on the left of each line of the sentinel. We will this to you as an exercise, because we are tired of typing!

Halt and Resume/Cancel
When the button is displayed on the Sentinel Panel, without any customization, the user is able to halt the process by pressing the button and then to resume or cancel the process. This could, for instance, allow the user to add paper to the printer or put the printer online. By default, the BasicReport and BusinessProcess classes present a YesNo_Box, captioned "Report Interrupt” or “Process Interrupt”, respectively, to the user. This box asks, “Do you wish to cancel this process[/report]?” If Yes is clicked, the process will end, but if the user clicks No, the process will continue where it left off (assuming that the host process is totally in charge of the values displayed in the sentinel. If the sentinel maintains any of its own counters, these will need to be re-established at the time of resumption.).

The fact that the report can be halted and resumed may, however, not be clear to the user. The user is looking at a button labeled “Cancel”, and may well assume that if the button is clicked, the report will just quit.

If, at the top of the BasicReport definition, you include the following line, the button will read Halt instead of Cancel. This may make things clearer.

 Set Button_text of (status_panel(self)) to “Halt”

You may make things even clearer by presenting your own dialog in place of the standard YesNo_Box. All of your modifications will be performed on the Host side. You could create a dialog labeled "Report has been Halted". Include a textbox saying “Do you want to Resume or Cancel?” and two buttons – one labeled “Resume”, the other, “Cancel”.

Include the following function in the panel:

    Function CancelOrResume returns integer
       integer iCorR
       Send popup
       Get piCorR to iCorR
       function_return iCorR
    End_Function
The piCorR property needs to be declared. The Resume button sets the property to 0 and then sends close_panel. The Cancel button sets the property to 1 and then sends close_panel.

In the Report, include the dialog you created, with the Use command. The only other modification is that the method Report_Interrupt (in the BusinessProcess, the method is Process_Interrupt) needs to be overridden as follows:

Function Report_Interrupt Returns Integer
   integer rVal
   String Mess
   If (Error_Check_State(self)) begin
      Move C_$AnErrorWishToCancel to Mess
      Get YesNo_Box Mess C_$ReportInterrupt to rVal
      Function_Return (Rval=MBR_YES)
   End
   Else Get CancelOrResume of (oCancelOrResume(self)) to rVal
   Function_Return Rval
End_Function
What happens in the above function is that if the interrupt is due to an error, it will be handled in the default manner. However, if the interrupt is due to the user clicking the Cancel (or Halt) button, your custom dialog will be popped up. You could also override the standard error interrupt method by using a customized dialog. This would allow you to provide the user with information about the error.

One important note: In all cases, the sentinel program is deactivated when the user clicks the sentinel’s button. From that point, all the handling is done from the Host. This is important. Do not attempt to modify the sentinel so that it remains active during the halt interrupt. Also, do not attempt to enable any user interaction with the sentinel other than the button click. Either of these would likely lead to undesirable results, such as a runtime crash.

Customizing Using Command Line Parameters
As described above, the Cancel button can be disabled with the use of a command line parameter passed to the sentinel program. The Sentinel.src program code includes the ability to add additional command line arguments. On the Host side, these arguments would need to be added to the string property status_params so that when the host invokes the sentinel, it will pass the parameters.

Here is how to make one simple change to Sentinel: to optionally display the Action text centered horizontally in the panel rather than at the left. You decide to handle this with a command line parameter “-a”. In your renamed copy of the sentinel, you would make several changes. First, you would add a global integer CenterAction. (Normally, we frown on global integers, due to encapsulation priorities. However, because the sentinel program is so self-contained, encapsulation is not an issue.) Second, you would modify the ParseParam procedure to handle your command line argument. Currently, ParseParam includes the following lines:

    If sLParam eq "-c" Begin
       Move (sRParam="0") to NoCancel
    End

You would want to add another set of lines as follows:

    If sLParam eq "-a" Begin
       Move (sRParam="1") to CenterAction
    End

Then, you would modify the Action_txt object declaration. Currently, it is:

    Object Action_Txt is a TextBox
        Set location to 45 10
    End_object

You would modify it to the following:

If Not CenterAction Begin
   Object Action_Txt is a TextBox
       Set location to 45 10
   End_object
End
Else Begin
   Object Action_Txt is a CenterTextBox
       Set location to 45 0
   End_object
End
On the Host side, you would set the property Status_Params to "-a0".

Additional Reading

Contacting Data Access Worldwide

Data Access Worldwide
14000 SW 119 Ave
Miami, FL 33186
305-238-0012
Domestic Sales: 800-451-3539
Fax: 305-238-0017
email: sales@dataaccess.com
Newsgroup Server: news.dataaccess.com
Internet: http://www.dataaccess.com

Data Access Worldwide - Asia Pacific
Suite 5, 333 Wantirna Road, Wantirna VIC 3152 Australia
Phone: +61 3 9800 4233 f: +61 3 9800 4255
Sales: asiapacific@DataAccess.com
Support: support.asiapacific@DataAccess.com
Internet: http://www.DataAccess.com/AsiaPacific

Data Access Worldwide - Brasil
Av.Paulista, 1776 - 21st.Floor
São Paulo -SP - Brazil
CEP 01310-921
Phone: 5511-3262-2000
Fax 5511-3284-1579
Sales: info@dataaccess.com.br
Support: suporte@dataaccess.com.br
Internet: http://www.dataaccess.com.br

Data Access Worldwide - Europe
Lansinkesweg 4
7553 AE Hengelo
The Netherlands
Telephone: +31 (0)74 - 255 56 09
Fax: +31 (0)74 - 250 34 66
Sales: info@dataaccess.nl
Support: support@dataaccess.nl
Internet: http://www.dataaccess.nl

Data Access Technical Support
800-451-3539 / 305-232-3142
email: support@dataaccess.com
Visit our Support Home page to see all of our Support options: http://www.dataaccess.com/support

Data Access Technical Knowledge Base    http://www.dataaccess.com/kbase
Many answers to technical problems can be found online in the Data Access Technical Knowledge Base. Here, you can access the same live data that Data Access Worldwide technical support and development staff use to enter and track technical articles. 

Copyright Notice
This document is property of Data Access Corporation. With credit to Data Access Corporation for its authorship, you are encouraged to reproduce this information in any format either on paper or electronically, in whole or in part. You may publish this paper as a stand alone document within your own publications conditional on the maintenance of the intent, context, and integrity of the material as it is presented here.

DataFlexis a registered trademark of Data Access Corporation.

NO LIABILITY FOR CONSEQUENTIAL DAMAGES
To the maximum extent permitted by applicable law, in no event shall Data Access Corporation be liable for any special, incidental, indirect, or consequential damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use any information provided in this document, even if Data Access Corporation has been advised of the possibility of such damages. Because some states and jurisdictions do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitation may not apply to you.