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
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:
//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.
// Sentinel/Host common shared data
//DEFINE BUTTONSTART for 10
DEFINE BUTTONLENGTH for 25DEFINE CAPTIONSTART for 35
DEFINE CAPTIONLENGTH for 250DEFINE TITLESTART for 285
DEFINE TITLELENGTH for 250DEFINE MESSAGESTART for 535
DEFINE MESSAGELENGTH for 250DEFINE ACTIONSTART for 785
DEFINE ACTIONLENGTH for 250
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_TextTypically, 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.
Message_Text
Action_Text
Button_Text
Title_Text
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 CenterTextBoxThese would be changed to the following:
Set Location to 10 0
End_objectObject Message_Txt is a TextBox
Set location to 25 10
Set Auto_Size_State to False
Set size to 20 150
End_object
Object Title_Txt is a TextBoxAnd 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.
Set Location to 10 27
Set label to "Now Processing Store Number"
End_objectObject Message_Txt is a TextBox
Set location to 10 126
Set Auto_Size_State to False
Set size to 10 50
End_object
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 785These 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.
DEFINE P#LENGTH for 50DEFINE PAMTSTART for 835
DEFINE PAMTLENGTH for 50DEFINE S#START for 885
DEFINE S#LENGTH for 50DEFINE SAMTSTART for 935
DEFINE SAMTLENGTH for 50
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_ListWe begin our numbering with 6, as DfSent has already enumerated 6 other constants in this series for 0 through 5.
Define Host_Data_P# for 6
Define Host_Data_PAmt
Define Host_Data_S#
Define Host_Data_Samt
End_Enum_List
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 ""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.
Property String PAmt_Text Public ""
Property String S#_Text Public ""
Property String SAmt_Text Public ""
Procedure OnSentinelDataChange integer iBeg integer iLengthNow, we augment the Initialize_Shared_Data procedure:
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
Procedure Initialize_Shared_DataNext, 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:
Forward Send Initialize_Shared_Data
Send Initialize_P#_Data
Send Initialize_PAmt_Data
Send Initialize_S#_Data
Send Initialize_SAmt_Data
End_procedure
Procedure Initialize_P#_DataThe 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.
Local string sText
get HostData P#START P#LENGTH to sText
Set P#_Text to sText
End_procedure
Procedure Data_Change integer ModeThat’s all there is to it on the sentinel side.
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
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 StatusPanelWithin our BusinessProcess object, we set the Sentinel_Name to the name of our customized Sentinel program: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#_TextFunction P#_Text returns string
Function_Return (Private.P#_Text(self))
End_Function // P#_TextProcedure 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_TextFunction PAmt_Text returns string
Function_Return (Private.PAmt_Text(self))
End_Function // PAmt_TextProcedure 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#_TextFunction S#_Text returns string
Function_Return (Private.S#_Text(self))
End_Function // S#_TextProcedure 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_TextFunction SAmt_Text returns string
Function_Return (Private.SAmt_Text(self))
End_Function // SAmt_TextEnd_Object
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 integerThe 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.
integer iCorR
Send popup
Get piCorR to iCorR
function_return iCorR
End_Function
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 IntegerWhat 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.
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
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 BeginOn the Host side, you would set the property Status_Params to "-a0".
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
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.