Description: One or more errors occurred during the compilation of a resource required to build this page. Please review the following specific error details and modify your source code appropriately.
CXP File: C:\inetpub\wwwroot\webdoc-v2-standalone\v2\content\xppguide_h2_creating_gui_applications.cxp
Intermediate Xbase++ Code: C:\inetpub\wwwroot\webdoc-v2-standalone\v2\content\cxp-application\xppguide_h2_creating_gui_applications.cxp.20251014-05892957.dll
Compiler: error XBT0523: Length of input line exceeds input buffer length in Line 0
A single line of code which is broken down into several lines using the line continuation character (;) exceeds the limit of 20kB characters. This line must be split into several lines without line continuation, or blank spaces must be converted into tabs.
(No Source Code Location Determinable)
The builder was invoked with the following parameters, the output is shown below.
Current Directory:
C:\Program Files (x86)\Alaska Software\cxp20\bin
Command:
C:\Program Files (x86)\Alaska Software\cxp20\bin\cxc-builder.exe
Parameters:
"@C:\inetpub\wwwroot\webdoc-v2-standalone\v2\content\cxp-application\xppguide_h2_creating_gui_applications.cxp.20251014-05892957.lst"
Output:
c:\inetpub\wwwroot\webdoc-v2-standalone\v2\content\cxp-application\xppguide_h2_creating_gui_applications.cxp.prg(0:0): error XBT0523: Length of input line exceeds input buffer length
In the following the full source code of your resource is shown.
0001 <%#page implements="PageClass_3DAFF8D2BD07B2FFB3C53A357A5662B8B7FB3B8C" layout="/layouts/content-h2.layout" %>
0002
0003 @SECTION meta-title
0004 <title>Creating GUI applications</title>
0005
0006 @SECTION meta-description
0007 <meta name="description" content="This section shows how to design and program GUI applications." />
0008
0009 @SECTION menu-sidebar
0010 <div id="sidebar-menu-placeholder"><div class="ui blue inverted left vertical menu sidebar mobile only" id="sidebar-menu">
0011 <div class="item" id="wdg-sidebar-left-menu">
0012 <div class="header" >xppguide</div>
0013 <div class="menu" >
0014 <a class="item" href="xppguide_h1_user_interface_and_dialog_concepts.cxp">User Interface and Dialog Concepts</a><a class="item" href="xppguide_h2_applications_in_character_mode_vio_mode.cxp">Applications in character mode (VIO mode)</a><a class="item" href="xppguide_h2_applications_in_graphics_mode_gui_mode.cxp">Applications in graphics mode (GUI mode)</a><a class="item active" href="xppguide_h2_creating_gui_applications.cxp">Creating GUI applications</a><a class="item" href="xppguide_h2_touch_input.cxp">Touch input</a>
0015 </div>
0016 </div>
0017 </div>
0018 </div>
0019
0020 @SECTION top-header
0021 <div class="ui huge center aligned dividing header" id="top-level-header">
0022 <div class="content" id="wdg-top-level-title">
0023 <i class="compass icon" ></i>
0024 <span id="wdg-top-level-title-header">Programming Guide:xppguide</span>
0025 </div>
0026 </div>
0027
0028 @SECTION left-menu
0029 <div class="ui vertical accordion inverted orange fluid menu" id="wgd-left-menu">
0030 <a class="item" href="xppguide_h1_user_interface_and_dialog_concepts.cxp" id="wgd-left-menu-parent">
0031 <div class="header" id="wgd-left-menu-parent-header">User Interface and Dialog Concepts</div>
0032 </a>
0033 <a class="item" href="xppguide_h2_applications_in_character_mode_vio_mode.cxp">Applications in character mode (VIO mode)</a><a class="item" href="xppguide_h2_applications_in_graphics_mode_gui_mode.cxp">Applications in graphics mode (GUI mode)</a><a class="item active" href="xppguide_h2_creating_gui_applications.cxp">Creating GUI applications</a><a class="item" href="xppguide_h2_touch_input.cxp">Touch input</a>
0034 </div>
0035
0036 @SECTION breadcrumbs
0037 <a class="link item" href="bc_programming_guide.cxp" >
0038 <i class="compass icon" ></i>
0039 <span>Programming Guide</span>
0040 </a>
0041 <i class="right angle icon divider"></i>
0042 <a class="link item" href="bc_programming_guide.cxp" >
0043 <i class="compass icon" ></i>
0044 <span>xppguide</span>
0045 </a>
0046 <i class="right angle icon divider" ></i>
0047 <a class="link item" href="xppguide_h1_user_interface_and_dialog_concepts.cxp" >
0048 <i class="compass icon" ></i>
0049 <span>User Interface and Dialog Concepts</span>
0050 <!-- go to contentPageHeading1-->
0051 </a>
0052 <i class="right angle icon divider"></i>
0053 <span class="item" >
0054 <i class="compass icon" ></i>
0055 <span>Creating GUI applications</span>
0056 </span>
0057
0058 @SECTION name
0059 <span>Creating GUI applications</span>
0060
0061 @SECTION feature
0062 <a class="bg-blue ui tag label" href="/document-conventions.cxp#FeatureGrades" id="wdg-feature">Foundation</a>
0063
0064 @SECTION body
0065 <div id="wdg-central-column">
0066 <div id="wdg-segment">
0067 <div class="ui vertical basic segment">
0068 <div>
0069 <p>This section shows how to design and program GUI applications. It serves both as a guide for programmers new to a graphic user interface and as a source of solutions to some of the problems that may arise when programming GUI applications. Most of the example programs discussed in this section are also provided in the Xbase++ installation. Tips are included for organizing GUI applications and the answers to questions such as "How is this programmed?" and "Where is this implemented?" are discussed. </p>
0070 </div>
0071 </div>
0072 </div>
0073 <div>
0074 <a class="anchor" NAME="tasks_of_appsys"></a>
0075 <div class="ui vertical basic segment">
0076 <div class="ui header" >Tasks of AppSys()</div>
0077 <div>
0078 <p>The main task of the AppSys() function is to create the application window. Since AppSys() is an implicit INIT PROCEDURE, it is always called prior to the Main procedure. The application window object created in AppSys() depends on the type of application. It could be an XbpCrt window or an XbpDialog window. In order to ensure the widest possible compatibility, the default AppSys() routine included in Xbase++ creates an XbpCrt window. When developing new GUI applications using Xbase++, AppSys() should generally be changed to create an XbpDialog window instead. Additional tasks that must be performed only once at application startup can also be included in this procedure. This often includes creating the menu system, providing a help routine and initializing system wide variables or other necessary resources. These tasks can be accomplished before the application window is even visible, which allows the essential parts of the application to already be available when the Main procedure is called. </p>
0079 <p>The first decision in implementing AppSys() is whether to use XbpCrt or XbpDialog windows. In the case of a GUI application, the application type must also be considered. The concept "application type" designates the kind of user interface that the application will provide. The simpler case is an SDI application (Single Document Interface) where the application consists of a single window. The alternative is an MDI application (Multiple Document Interface). An MDI application runs in multiple windows and AppSys() just creates the main window allowing the additional windows within the main window to be generated later in the program. The size of the application window generally depends on the application type. The application window of an SDI application can be smaller than that of an MDI application, since no additional windows are needed in an SDI application. Also, the window size of an SDI application can be fixed, not allowing the user to change the size. The size of the application window of an MDI application must be changeable by the user. </p>
0080 <p>The following example is the AppSys() procedure from the file SDIDEMO.PRG that presents a complete example of an SDI application. The various tasks performed by AppSys() are demonstrated in the following procedure: </p>
0081 <p><figure class="code-block-segment" >
0082 <div class="code-block">
0083 <pre><code class="language-xpp">PROCEDURE AppSys
0084 LOCAL oDlg, oXbp, aPos[2], aSize, nHeight:=400, nWidth := 615
0085
0086 // Get size of desktop window
0087 // to center the application window
0088 aSize := SetAppWindow():currentSize()
0089 aPos[1] := Int( (aSize[1]-nWidth ) / 2 )
0090 aPos[2] := Int( (aSize[2]-nHeight) / 2 )
0091
0092 // Create application window
0093 oDlg := XbpDialog():new()
0094 oDlg:title := "Toys & Fun Inc. [Xbase++ - SDI Demo]"
0095 oDlg:border:= XBPDLG_THINBORDER
0096 oDlg:create( ,, aPos, {nWidth, nHeight},, .F. )
0097
0098 // Set background color for drawing area
0099 oDlg:drawingArea:SetColorBG( GRA_CLR_PALEGRAY )
0100
0101 // Select font
0102 oDlg:drawingArea:SetFontCompoundName( "8.Help.normal" )
0103
0104 // Create menu system (UDF)
0105 MenuCreate( oDlg:menuBar() )
0106
0107 // Provide online help via UDF
0108 oXbp := XbpHelpLabel():new():create()
0109 oXbp:helpObject := ;
0110 HelpObject( "SDIDEMO.HLP", "Help for SDI demo" )
0111 oDlg:helpLink := oXbp
0112
0113 // Display application window and set focus to it
0114 oDlg:show()
0115 SetAppWindow( oDlg )
0116 SetAppFocus ( oDlg )
0117
0118 RETURN
0119 </code></pre>
0120 </div>
0121 </figure></p>
0122 <p>In this example, a dialog window is created with the size 615 x 400 pixels. This size allows it to be completely displayed even on a low resolution screen. The window is provided for an SDI application and has a fixed size (XBPDLG_THINBORDER). The first call to SetAppWindow() provides a reference to the desktop on which the application window is displayed. The <span class="italic">:currentSize()</span> method of this object provides the size of the desktop window corresponding to the current screen resolution. This information is used to position the application window when the Xbase++ application is called. In the example, the application window is displayed centered on the screen. </p>
0123 <p>After the background color for the drawing area of the dialog window is set, the menu system is generated in the function MenuCreate(). This user-defined function (UDF) receives the return value of the method <span class="italic">:menuBar()</span> as an argument. The <span class="italic">:menuBar()</span> method creates an XbpMenuBar object and installs it in the application window. The menu system must then be constructed in the UDF. This approach is recommended because the menu system construction can be performed before the application window is visible. The mechanics of constructing a menu system is described in the next section. </p>
0124 <p>In this AppSys() example, the mechanism for the online help is implemented after the menu system is created in MenuCreate(). This includes the generation of an XbpHelpLabel object that is assigned to the instance variable <span class="italic">:helpLink</span>. The help label object references help information and activates the window of the online help. The online help window is in turn managed by an XbpHelp object which must be provided to the XbpHelpLabel object. This is done by assigning an XbpHelp object to the <span class="italic">:helpObject</span> instance variable of the XbpHelpLabel object. The XbpHelp object manages online help windows and should exist only once within an Xbase++ application. For this reason it is created in the user-defined function HelpObject() which is shown below: </p>
0125 <p><figure class="code-block-segment" >
0126 <div class="code-block">
0127 <pre><code class="language-xpp">********************************************************************
0128 * Routine to retrieve the help object. It manages the online help
0129 ********************************************************************
0130 FUNCTION HelpObject( cHelpFile, cTitle )
0131 STATIC soHelp
0132
0133 IF soHelp == NIL
0134 soHelp := XbpHelp():new()
0135 soHelp:resGeneralHelp := IPFID_HELP_GENERAL
0136 soHelp:resKeysHelp := IPFID_HELP_KEYS
0137 soHelp:create( SetAppWindow(), cHelpFile, cTitle )
0138 ENDIF
0139 RETURN soHelp
0140 </code></pre>
0141 </div>
0142 </figure></p>
0143 <p>An XbpHelp object is created and stored in a STATIC variable the first time this function is called. The XbpHelp object manages the online help window of an Xbase++ application and a reference to it can be retrieved by calling the function HelpObject() at any point in the program. This allows any number of XbpHelpLabel objects to be created that always activate the same XbpHelp object (or the same online help). </p>
0144 <p>The function HelpObject() needs to receive the file name for the HLP file and the window title for the online help. Otherwise this function is generic. It also uses two #define constants which reference the two help windows available in each application. These constants can only be user-defined and must be used in the source code of the online help as numeric IDs in order to reference the specific help window. </p>
0145 </div>
0146 </div>
0147 </div>
0148 <div>
0149 <a class="anchor" NAME="the_menu_system_of_an_application"></a>
0150 <div class="ui vertical basic segment">
0151 <div class="ui header" >The menu system of an application</div>
0152 <div>
0153 <p>The menu system of a GUI application plays a centrol role for program control. This menu must be created only once, generally within the function AppSys() prior to the first display of the application window. When the menu system is created within AppSys() or before the application window is displayed, the user does not see the construction of the menu system. The menu in the application window is already complete when the window is displayed for the first time. </p>
0154 <p>The menu system consists of an XbpMenuBar object which manages the horizontal menu bar in the application window and several XbpMenu objects that are inserted in the menu bar as submenus. There are several ways to implement program control using menus. The simplest form is shown in the SDIMENU.PRG file (which presents an example SDI application). The most important steps are shown in the following code: </p>
0155 <p><figure class="code-block-segment" >
0156 <div class="code-block">
0157 <pre><code class="language-xpp">/* Call in AppSys() */
0158
0159 MenuCreate( oDlg:menuBar() )
0160
0161 ********************************************************************
0162 * Create menu system in the menu bar of the dialog
0163 ********************************************************************
0164 PROCEDURE MenuCreate( oMenuBar )
0165 LOCAL oMenu
0166
0167 // First sub-menu
0168 //
0169 oMenu := SubMenuNew( oMenuBar, "~File" )
0170
0171 oMenu:addItem( { "Options", } )
0172 oMenu:addItem( MENUITEM_SEPARATOR )
0173 oMenu:addItem( { "~Exit" , NIL } )
0174
0175 oMenu:itemSelected := ;
0176 {|nItem,mp2,obj| MenuSelect(obj, 100+nItem) }
0177
0178 oMenuBar:addItem( {oMenu, NIL} )
0179
0180 // Second sub-menu -> customer data
0181 //
0182 oMenu := SubMenuNew( oMenuBar, "C~ustomer" )
0183 oMenu:setName( CUST_MENU )
0184
0185 oMenu:addItem( { "~New" , NIL } )
0186 oMenu:addItem( { "~Seek" , NIL } )
0187 oMenu:addItem( { "~Change", NIL } )
0188 oMenu:addItem( { "~Delete",NIL , 0, ;
0189 XBPMENUBAR_MIA_DISABLED } )
0190 oMenu:addItem( { "~Print" ,NIL , 0, ;
0191 XBPMENUBAR_MIA_DISABLED } )
0192
0193 oMenu:itemSelected := ;
0194 {|nItem,mp2,obj| MenuSelect(obj, 200+nItem) }
0195
0196 oMenuBar:addItem( {oMenu, NIL} )
0197
0198 /* And so forth... */
0199 </code></pre>
0200 </div>
0201 </figure></p>
0202 <p>XbpMenu objects are created to contain the menu items. These submenus are created in the user-defined function SubMenuNew() (which is shown below), and menu items are attached to the submenus using the <span class="italic">:addItem()</span> method. A menu item is an array containing between two and four elements. In the simplest case the first element is the character string to be displayed as the menu item caption and the second element is NIL. Any character in the character string can be identified as a short-cut key by placing a tilde (~) in front of it. The second element is the code block to be executed when the menu item is selected by the user. In this example, instead of defining individual code blocks for each menu item a callback code block is defined for the entire submenu and the numeric position of the selected item and the menu object itself are passed to the selection routine MenuSelect(). In this routine a simple DO CASE...ENDCASE structure provides branching to the appropriate program module. </p>
0203 <p>The second menu in the example is assigned a numeric ID (#define constant CUST_MENU) in the call to the method <span class="italic">:setName()</span>. This allows a specific XbpMenu object to be found later, since this value is found in the child list of the XbpMenuBar object which in turn is stored in the child list of the application window. The expression SetAppWindow():childFromName( CUST_MENU ) would provide a reference to this XbpMenu object. This can be used to make individual menu items temporarily unavailable (or available again) if this is desired in a specific program situation. </p>
0204 <p>Inserting submenus into the main menu is done using the method <span class="italic">:addItem()</span> executed by the XbpMenuBar object. The title of a menu serves as text for the menu item. In the example program this text is set for a new submenu as follows: </p>
0205 <p><figure class="code-block-segment" >
0206 <div class="code-block">
0207 <pre><code class="language-xpp">********************************************************************
0208 * Create sub-menu in a menu
0209 ********************************************************************
0210 FUNCTION SubMenuNew( oMenu, cTitle )
0211 LOCAL oSubMenu := XbpMenu():new( oMenu )
0212 oSubMenu:title := cTitle
0213 RETURN oSubMenu:create()
0214 </code></pre>
0215 </div>
0216 </figure></p>
0217 <p>In this function the main menu (or the immediately higher menu) is provided as the parent of the submenu. Assigning the title must occur prior to the call of the method <span class="italic">:create()</span> for correct positioning: </p>
0218 <p><span class="bold">The default help menu</span></p>
0219 <p>Each application should have a "Help" menu item that generally includes the same set of menu items. In the example program, this help menu is created by a separate procedure which creates the default menu items. Program control is implemented by code blocks that are passed to the menu in the method <span class="italic">:addItem()</span>. In this case the callback code block <span class="italic">:itemSelected</span> is not used. </p>
0220 <p><figure class="code-block-segment" >
0221 <div class="code-block">
0222 <pre><code class="language-xpp">********************************************************************
0223 * Create standard help menu
0224 ********************************************************************
0225 PROCEDURE HelpMenu( oMenuBar )
0226 LOCAL oMenu := SubMenuNew( oMenuBar, "~Help" )
0227 oMenu:addItem( { "Help ~index", ;
0228 {|| HelpObject():showHelpIndex() } } )
0229
0230 oMenu:addItem( { "~General help", ;
0231 {|| HelpObject():showGeneralHelp() } } )
0232
0233 oMenu:addItem( { "~Using help", ;
0234 {|| HelpObject():showHelp(IPFID_HELP_HELP) } } )
0235
0236 oMenu:addItem( { "~Keys help", ;
0237 {|| HelpObject():showKeysHelp() } } )
0238
0239 oMenu:addItem( MENUITEM_SEPARATOR )
0240
0241 oMenu:addItem( { "~Product information", ;
0242 {|| MsgBox("Xbase++ SDI Demo") } } )
0243
0244 oMenuBar:addItem( {oMenu, NIL} )
0245 RETURN
0246 </code></pre>
0247 </div>
0248 </figure></p>
0249 <p>The online help is managed by the XbpHelp object that is stored as a static variable in the user-defined function HelpObject(). This means it is always available when the function HelpObject() is called. Default help information can be called from the help menu by executing the XbpHelp objects methods provided for these purposes. A special method does not exist for the item "Using help". Here a #define constant is specified to the XbpHelp object that designates the numeric ID for the appropriate help window in the online help. The same ID must also be used in the IPF source code. </p>
0250 <p><span class="bold">A dynamic menu for managing windows</span></p>
0251 <p>In addition to the help menu that is available in both SDI and MDI applications, MDI applications have a second default menu that is used to bring different child windows of the MDI application to the front. The text in the title of each opened window appears as a menu item and selecting a menu item sets focus to the corresponding child window. This requires a dynamic approach to the menu, because the number of menu items corresponds to the number of open windows. A dynamic window menu is implemented for this purpose in the MDIMENU.PRG file (which is part of the source code for the MDIDEMO sample application). It is a good example of deriving new classes from an Xbase Part. To accomplish this, a way to easily determine the main menu of the application window (the parent window) is needed. The function AppMenu() is included in MDIDEMO.PRG for this purpose and returns the main menu of the application. There is also only one window menu per application so it can be stored in a STATIC variable. The function WinMenu() performs this task as shown in the following code: </p>
0252 <p><figure class="code-block-segment" >
0253 <div class="code-block">
0254 <pre><code class="language-xpp">********************************************************************
0255 * Create menu to manage open windows
0256 ********************************************************************
0257 FUNCTION WinMenu()
0258 STATIC soMenu
0259
0260 IF soMenu == NIL
0261 soMenu := WindowMenu():new():create( AppMenu() )
0262 ENDIF
0263 RETURN soMenu
0264 </code></pre>
0265 </div>
0266 </figure></p>
0267 <p>The window menu is an instance of the class WindowMenu and receives the return value of AppMenu() as its parent. This means it is displayed as a submenu of the MDI application main menu. The user-defined class WindowMenu is derived from XbpMenu: </p>
0268 <p><figure class="code-block-segment" >
0269 <div class="code-block">
0270 <pre><code class="language-xpp">********************************************************************
0271 * Menu class for management of open windows
0272 ********************************************************************
0273 CLASS WindowMenu FROM XbpMenu
0274 EXPORTED:
0275 CLASS VAR windowStack
0276 CLASS METHOD initClass
0277 METHOD init, addItem, delItem, setItem
0278 ENDCLASS
0279
0280 ********************************************************************
0281 // Stack for open dialog windows as class variable
0282 //
0283 CLASS METHOD WindowMenu:initClass
0284 ::windowStack := {}
0285 RETURN self
0286 </code></pre>
0287 </div>
0288 </figure></p>
0289 <p>The class variable <span class="italic">:windowStack</span> is declared to reference opened windows. The class method <span class="italic">:initClass()</span>, whose only task is to initialize the class variable with an empty array is also included. The four methods of the XbpMenu class are overloaded. The method <span class="italic">:init()</span> is executed immediately after the class method <span class="italic">:new()</span>terminates. The <span class="italic">:init()</span> method of the XbpMenu class must also be called in order to initialize the member variables implemented there: </p>
0290 <p><figure class="code-block-segment" >
0291 <div class="code-block">
0292 <pre><code class="language-xpp">********************************************************************
0293 // Select a window via callback code block
0294 //
0295 METHOD WindowMenu:init( oParent, aPresParam, lVisible )
0296 ::xbpMenu:init( oParent, aPresParam, lVisible )
0297 ::title := "~Window"
0298 ::itemSelected := ;
0299 {|nItem,mp2,obj| SetAppFocus( obj:windowStack[nItem] ) }
0300 RETURN self
0301 </code></pre>
0302 </div>
0303 </figure></p>
0304 <p>After the superclass is initialized, the menu title is assigned in <span class="italic">:init()</span>. A code block is assigned to the callback slot <span class="italic">:itemSelected</span>. This code block sets the focus to the window whose window title is selected from the menu. The numeric position of the selected menu item is passed to the code block as the parameter <span class="italic">nItem</span> and <span class="italic">obj</span> contains a reference to the menu object itself. Within this code block, the class variable <span class="italic">:windowStack</span> is accessed. <span class="italic">:windowStack</span> contains references to all the child windows of the MDI application. The selected window is passed to the function SetAppFocus() which sets it as the foreground window. </p>
0305 <p>The last three methods of the window menu class allow menu items to be inserted, changed or deleted. These methods have the same names as methods of the XbpMenu class but the parameter passed to the methods are different. Instead of an array with between two and four elements, the passed parameter is an XbpDialog or XbpCrt object that is to receive focus if the menu item is selected. </p>
0306 <p><figure class="code-block-segment" >
0307 <div class="code-block">
0308 <pre><code class="language-xpp">********************************************************************
0309 // Use title of the dialog window as text for menu item
0310 //
0311 METHOD WindowMenu:addItem( oDlg )
0312 LOCAL cItem := oDlg:getTitle()
0313
0314 AAdd( ::windowStack, oDlg )
0315
0316 ::xbpMenu:addItem( {cItem, NIL} )
0317 IF ::numItems() == 1
0318 ::setParent():insItem( ::setParent():numItems(), {self, NIL} )
0319 ENDIF
0320 RETURN self
0321 </code></pre>
0322 </div>
0323 </figure></p>
0324 <p>An opened window is passed to the <span class="italic">:addItem()</span> method. Within this method, the window is added to the class variable <span class="italic">:windowStack</span> and the window title is added as a menu item by passing it to the :addItem() method of the XbpMenu class. A special characteristic of the window menu is that it is only displayed in the main menu when at least one child window is open. Otherwise the "Window" menu item does not appear in the main menu. The window menu inserts itself as a menu item in its parent (the main menu) after the first time the method <span class="italic">:addItem()</span> is executed. </p>
0325 <p><figure class="code-block-segment" >
0326 <div class="code-block">
0327 <pre><code class="language-xpp">********************************************************************
0328 // Transfer changed window title to menu item
0329 //
0330 METHOD WindowMenu:setItem( oDlg )
0331 LOCAL aItem, i := AScan( ::windowStack, oDlg )
0332
0333 IF i == 0
0334 ::addItem( oDlg )
0335 ELSE
0336 aItem := ::xbpMenu:getItem(i)
0337 aItem[1] := oDlg:getTitle()
0338 ::xbpMenu:setItem( i, aItem )
0339 ENDIF
0340
0341 RETURN self
0342
0343 ********************************************************************
0344 // Delete dialog window from window stack and from menu
0345 //
0346 METHOD WindowMenu:delItem( oDlg )
0347 LOCAL i := AScan( ::windowStack, oDlg )
0348 LOCAL nPos := ::setParent():numItems()-1 // window menu is always
0349 // next to last
0350 IF i > 0
0351 ::xbpMenu:delItem( i )
0352 ADel( ::windowStack, i )
0353 Asize( ::windowStack, Len(::windowStack)-1)
0354 IF ::numItems() == 0
0355 ::setParent():delItem( nPos )
0356 ENDIF
0357 ENDIF
0358 RETURN self
0359 </code></pre>
0360 </div>
0361 </figure></p>
0362 <p>The <span class="italic">:setItem()</span> method is used when the window title of an opened dialog window changes. This change must also be made in the menu item of the dynamic window menu. The <span class="italic">:delItem()</span> method is called when a dialog window is closed. This method removes the title of the dialog window from the window menu. If no child windows remain open, the window menu removes itself from the main menu (the parent) and the menu item "Window" is no longer visible. </p>
0363 </div>
0364 </div>
0365 </div>
0366 <div>
0367 <a class="anchor" NAME="tasks_of_the_main_procedure"></a>
0368 <div class="ui vertical basic segment">
0369 <div class="ui header" >Tasks of the Main procedure</div>
0370 <div>
0371 <p>After the application window including the menu system has been created in AppSys(), program execution continues in the Main procedure (assuming there is no other INIT PROCEDURE). At the start of the Main procedure all conditions required for an error free run of the GUI application should be checked. For example, this might include testing for the existence of all required files, creating index files that are not available and initialization of variables required throughout the application (PUBLIC variables). Retrieving configuration variables using the command RESTORE FROM should also generally occur within the Main procedure before the program goes into the event loop. The event loop performs the central task of the Main procedure. In this loop, events are retrieved and sent on to the addressee. The following program code is from the MDIDEMO.PRG file and shows some of what needs to be included in the Main procedure or in functions called by the Main procedure. </p><div class="ui blue icon message" >
0372 <i class="icon info circle"></i>
0373 <div class="content">
0374 <p> The example is not intended to cover all aspects that might be included in a Main procedure. </p>
0375 </div>
0376 </div><p><figure class="code-block-segment" >
0377 <div class="code-block">
0378 <pre><code class="language-xpp">#include "Gra.ch"
0379 #include "Xbp.ch"
0380 #include "AppEvent.ch"
0381 #include "Mdidemo.ch"
0382
0383 ********************************************************************
0384 * Main procedure and event loop
0385 ********************************************************************
0386 PROCEDURE Main
0387 LOCAL nEvent, mp1, mp2, oXbp
0388 FIELD CUSTNO, LASTNAME, FIRSTNAME, PARTNO, PARTNAME
0389
0390 // Check index files and create them if not existing
0391 IF ! AllFilesExist( { "CUSTA.NTX", "CUSTB.NTX", ;
0392 "PARTA.NTX", "PARTB.NTX" } )
0393 USE Customer EXCLUSIVE
0394 INDEX ON CustNo TO CustA
0395 INDEX ON Upper(LastName+Firstname) TO CustB
0396
0397 USE Parts EXCLUSIVE
0398 INDEX ON Upper(PartNo) TO PartA
0399 INDEX ON Upper(PartName) TO PartB
0400
0401 CLOSE DATABASE
0402 ENDIF
0403
0404 SET DELETED ON
0405
0406 // Infinite loop. The program is terminated in AppQuit()
0407 DO WHILE .T.
0408 nEvent := AppEvent( @mp1, @mp2, @oXbp )
0409 oXbp:handleEvent( nEvent, mp1, mp2 )
0410 ENDDO
0411 RETURN
0412
0413 ********************************************************************
0414 * Check if all files of the array aFiles exist
0415 ********************************************************************
0416 FUNCTION AllFilesExist( aFiles )
0417 LOCAL lExist := .T., i:=0, imax := Len(aFiles)
0418
0419 DO WHILE ++i <= imax .AND. lExist
0420 lExist := File( aFiles[i] )
0421 ENDDO
0422 RETURN lExist
0423 </code></pre>
0424 </div>
0425 </figure></p>
0426 <p>In this example, the Main procedure simply tests whether all the index files exist and recreates the index files if any are not found. The existence of the files is tested in the function AllFilesExist(). When this is complete, the Main procedure enters an infinite loop that reads events from the queue using AppEvent() and sends them on to the addressee by calling the addressees method <span class="italic">:handleEvent()</span>. </p>
0427 <p>Looking at this implementation, the inevitable question is: Where and how is the program terminated? The infinite loop in the Main procedure cannot be terminated based on its condition DO WHILE .T.. A separate routine is used to terminate the program. The code for this routine is shown below: </p>
0428 <p><figure class="code-block-segment" >
0429 <div class="code-block">
0430 <pre><code class="language-xpp">********************************************************************
0431 * Routine to terminate the program
0432 ********************************************************************
0433 PROCEDURE AppQuit()
0434 LOCAL nButton
0435
0436 nButton := ConfirmBox( , ;
0437 "Do you really want to quit ?", ;
0438 "Quit", ;
0439 XBPMB_YESNO , ;
0440 XBPMB_QUESTION+XBPMB_APPMODAL+XBPMB_MOVEABLE )
0441
0442 IF nButton == XBPMB_RET_YES
0443 COMMIT
0444 CLOSE ALL
0445 QUIT
0446 ENDIF
0447
0448 RETURN
0449 </code></pre>
0450 </div>
0451 </figure></p>
0452 <p>In the termination routine AppQuit(), confirmation that the program should actually be terminated is received from the user via the ConfirmBox() function. If the application is to be terminated, all data buffers are written back into the files and all database are closed using CLOSE ALL. The command QUIT then terminates the program. If the user does not confirm that the program should be terminated, the infinite loop in the Main procedure is continued. </p>
0453 <p>It is generally recommended that the source code for a GUI application be broken down into three sections: program start, program execution and program end. The program start is contained in AppSys() and the program code executed within the Main procedure prior to the event loop. The event loop itself is the program execution. Often within this loop the program code that was generated in MenuCreate() during program start up is called by the menu system. Program termination occurs in the user defined procedure AppQuit(), where verification by the user can be requested and any data can be saved. </p>
0454 <p>There are only two places in a program where the procedure AppQuit() is called. AppQuit() is generally called from a menu item and from a callback code block or from a callback method. The next two lines illustrate this: </p>
0455 <p><figure class="code-block-segment" >
0456 <div class="code-block">
0457 <pre><code class="language-xpp">oMenu:addItem( {"~Quit", {|| AppQuit() } } )
0458 oDialog:close := {|| AppQuit() }
0459 </code></pre>
0460 </div>
0461 </figure></p>
0462 <p>In the first line, AppQuit() is executed after a menu item is selected so there must obviously be a menu containing a menu item to terminate the application. The second line defines a callback code block for the dialog window to execute after the system menu icon of the dialog window is double clicked or the "Close" menu item is selected in the system menu of the window. Generally, the routine for terminating a GUI application should be available in the menu of the application as well as in response to the xbeP_Close event. </p>
0463 </div>
0464 </div>
0465 </div>
0466 <div>
0467 <a class="anchor" NAME="a_datadialog_class_for_integrating_databases"></a>
0468 <div class="ui vertical basic segment">
0469 <div class="ui header" >A DataDialog class for integrating databases</div>
0470 <div>
0471 <p>An important aspect in programming GUI applications is the connection between the elements of the dialog window and the DatabaseEngine. The link between a single dialog element and a single database field is created via the data code block contained in the instance variable <span class="italic">:dataLink</span> of the DataRef class that manages data. This mechanism is described in the section <a href="xppguide_h2_applications_in_graphics_mode_gui_mode.cxp#dataref_op_minus_the_connection_between_xbp_and_dbe">"DataRef() - The connection between XBP and DBE"</a>. A window generally contains several dialog elements that are linked to different database fields. Special situations can result that must be considered when programming GUI applications. The programmer must also remember that such an application is completely event driven. As soon as there is a menu system in a window, an exactly defined order of program execution is no longer assured since the user has control of the application rather than the programmer. </p>
0472 <p>The two example applications SDIDEMO and MDIDEMO are provided as examples for GUI applications under Xbase++. The difficulties that arise in accessing databases are taken into account in different ways in these two programs. In SDIDEMO, a procedural approach is implemented and an object-oriented style is used in MDIDEMO. Both of these program examples solve the problem of non-modality of entry fields resulting from the event driven nature of a GUI application. The problems of non-modality are described by the questions: "When and how is data input validated?" and "When is data written to the database?". Since data entry fields can be activated with a mouse click, prevalidation (validation before data is entered) is not possible (after a mouse click an entry field has the input focus). This condition requires some consideration by programmers who have previously developed only under DOS without a mouse. Validating data in a GUI application can occur in the framework of postvalidation (validation after data is entered). The <span class="italic">:validate()</span> method in the DataRef class serves this purpose. If postvalidation fails, the method <span class="italic">:undo()</span> of the entry field (Xbase Part) should be called. In an event driven application, this is the only way to assure that no invalid data is written into the database. </p>
0473 <p>However, the major task in programming GUI applications is generally not validating the data, but transferring the input data to the database. In the SDIDEMO and MDIDEMO example programs, the philosophy is used that the data needs to be written to the database when the record pointer is changed. All Xbase Parts have their own edit buffer to hold the modified data and the value to write into the database fields is stored in this edit buffer of each Xbase Part. For all of the database fields that can be changed within a dialog window, an Xbase Part must exist to store the value in its edit buffer. The following code fragment illustrates this: </p>
0474 <p><figure class="code-block-segment" >
0475 <div class="code-block">
0476 <pre><code class="language-xpp">oXbp := XbpSLE():new( oDlg:drawingArea,, {95,135}, {180,22} )
0477 oXbp:bufferLength := 20
0478 oXbp:dataLink := {|x| IIf( x==NIL, LASTNAME, LASTNAME := x ) }
0479 oXbp:create():setData()
0480 </code></pre>
0481 </div>
0482 </figure></p>
0483 <p>In this code, an entry field is created for editing the data in the database field LASTNAME. Calling the method <span class="italic">:setdata()</span> in connection with <span class="italic">:create()</span> copies the data from the database field into the edit buffer of the XbpSLE object. Within a dialog window any number of entry fields can exist to access database fields. The edit buffer of all entry fields in the dialog window can be changed at any time (a mouse click in an entry field is sufficient to begin editing). For this reason, it must be determined when changes to the data in an entry field will be copied back into the file. There are two approaches: changes to individual data entry fields are written into the file as soon as the change occurs or all changes from all data entry fields in a window are written into the file as soon as a "Save" routine is explicitly called or the record pointer is repositioned. </p>
0484 <p>The second approach is preferred in GUI applications that are designed for simultaneous access on a network. This approach allows several data entry fields to be changed in a dialog window without each change being individually copied to the database. In concurrent or network operation saving each change to the database would require a time consuming lock and release of the current record. A performance optimized GUI application only locks a record when it can write several fields to the database or when the record pointer changes. </p>
0485 <p>The problems of validating and saving data into databases is present in every application. The following code shows several aspects of this problem and is based on the example application MDIDEMO. In this example application the DataDialog class is used to provide dialog windows for accessing the DatabaseEngine. A DataDialog object coordinates a DatabaseEngine with a dialog window. The source code for this class is contained in the file DATADLG.PRG. An example of an input screen based on DataDialog, is shown in the following illustration: </p><div class="ui basic center aligned segment wdg-centered-image">
0486 <div class="ui image image-content">
0487 <img class="wdg-centered-image-img"
0488 src="./images/img_xppguide_mdicuste.gif"/>
0489 <div class="ui bottom attached header image-title wdg-centered-image-title">Input screen for customer data</div>
0490 </div>
0491 </div><p>The DataDialog class is derived from XbpDialog. It adds seven new instance variables and eleven additional methods for transferring data from a database to the dialog and vice versa. Three of the instance variables are for internal use only and are declared as PROTECTED:. The four methods <span class="italic">:init()</span>, <span class="italic">:create()</span>, <span class="italic">:configure()</span> and <span class="italic">:destroy()</span> perform steps in the "life cycle" of a DataDialog object: </p>
0492 <p><figure class="code-block-segment" >
0493 <div class="code-block">
0494 <pre><code class="language-xpp">#include "Gra.ch"
0495 #include "Xbp.ch"
0496 #include "Dmlb.ch"
0497 #include "Common.ch"
0498 #include "Appevent.ch"
0499
0500 ********************************************************************
0501 * Class declaration
0502 ********************************************************************
0503 CLASS DataDialog FROM XbpDialog
0504 PROTECTED:
0505 VAR appendMode // Is it a new record?
0506 VAR editControls // List of XBPs for editing data
0507 VAR appendControls // List of XBPs enabled only
0508 // during APPEND
0509
0510 EXPORTED:
0511 VAR area READONLY // current work area
0512 VAR newTitle // code block to change window title
0513 VAR contextMenu // context menu for data dialog
0514 VAR windowMenu // dynamic window menu in
0515 // application window
0516
0517 METHOD init // overloaded methods
0518 METHOD create
0519 METHOD configure
0520 METHOD destroy
0521 METHOD addEditControl // register XBP for edit
0522 METHOD addAppendControl // register XBP for append
0523 METHOD notify // process DBO message
0524 METHOD readData // read data from DBF
0525 METHOD validateAll // validate all data stored in XBPs
0526 METHOD writeData // write data from XBPs to DBF
0527 METHOD isIndexUnique // check index value for uniqueness
0528 ENDCLASS
0529 </code></pre>
0530 </div>
0531 </figure></p>
0532 <p>The protected instance variable <span class="italic">:appendMode</span> contains the logical value .T. (true) only when the phantom data record (record number LastRec()+1) is current. The other two protected instance variables <span class="italic">:editControls</span> and <span class="italic">:appendControls</span> are arrays containing lists of Xbase Parts that can modify data. In order to create a data dialog, editable XBPs are required as well as Xbase Parts that cannot edit data but display static text or boxes (XbpStatic objects). The instance variable <span class="italic">:editControls</span> contains a list of references to those XBPs in the child list (all XBPs that are displayed in the dialog window are contained in this list) that can be edited. </p>
0533 <p>The task of the <span class="italic">:appendControls</span> instance variable is similar and contains a list of XBPs that are only enabled when a new record is appended. In all other cases, these XBPs are disabled. They only display data and do not allow the data in them to be edited. This is useful for editing database fields that are contained in the primary database key which should not be changed once they are entered in the database. <span class="italic">:editControls</span> and <span class="italic">:appendControls</span> are both initialized with empty arrays. This is done in the <span class="italic">:init()</span> method after it calls the <span class="italic">:init()</span> method of the XbpDialog class as shown below: </p>
0534 <p><figure class="code-block-segment" >
0535 <div class="code-block">
0536 <pre><code class="language-xpp">********************************************************************
0537 * Initialize data dialog
0538 ********************************************************************
0539 METHOD DataDialog:init( oParent, oOwner , ;
0540 aPos , aSize , ;
0541 aPParam, lVisible )
0542
0543 DEFAULT lVisible TO .F.
0544
0545 ::xbpDialog:init( oParent, oOwner, ;
0546 aPos , aSize , ;
0547 aPParam, lVisible )
0548
0549 ::area := 0
0550 ::border := XBPDLG_THINBORDER
0551 ::maxButton := .F.
0552 ::editControls := {}
0553 ::appendControls := {}
0554 ::appendMode := .F.
0555 ::newTitle := {|obj| obj:getTitle() }
0556
0557 RETURN self
0558 </code></pre>
0559 </div>
0560 </figure></p>
0561 <p>All instance variables are set to values with the valid data type in the <span class="italic">:init()</span> method. Only the instance variables <span class="italic">:border</span> and <span class="italic">:maxButton</span> change the default values assigned in the XbpDialog class. The window of a DataDialog object is fixed in size and cannot be enlarged. The method has the same parameter list as the method <span class="italic">:new()</span> and <span class="italic">:init()</span> in the XbpDialog class. This allows it to receive parameters and simply pass them on to the superclass. The DataDialog is different in that it is created as hidden by default. This is recommended when many XBPs will be displayed in the window after the window is generated. The construction of the screen with the method <span class="italic">:show()</span> is faster if everything can be displayed at once after the XBPs have been added to the dialog window. </p>
0562 <p>The instance variable <span class="italic">:newTitle</span> must contain a code block that the DataDialog object is passed to. For this reason a code block is defined in the <span class="italic">:init()</span> method, but it must be redefined later. This code block changes the window title while the dialog window is visible. The default code block is assigned to the instance variable in the <span class="italic">:init()</span> method to ensure that the instance variable has the correct data type. </p>
0563 <p>The next method in the "life cycle" of a DataDialog object is <span class="italic">:create()</span>. A database must be open in the current work area prior to this method being called. A DataDialog object continues to use the work area that is current when the <span class="italic">:create()</span> method is executed: </p>
0564 <p><figure class="code-block-segment" >
0565 <div class="code-block">
0566 <pre><code class="language-xpp">********************************************************************
0567 * Load system resources
0568 * Register DataDialog in current work area
0569 ********************************************************************
0570 METHOD DataDialog:create( oParent, oOwner , ;
0571 aPos , aSize , ;
0572 aPParam, lVisible )
0573
0574 ::xbpDialog:create( oParent, oOwner , ;
0575 aPos , aSize , ;
0576 aPParam, lVisible )
0577
0578 ::drawingArea:setColorBG( GRA_CLR_PALEGRAY )
0579
0580 ::appendMode := Eof()
0581 ::area := Select()
0582
0583 ::close := {|mp1,mp2,obj| obj:destroy() }
0584 ::setDisplayFocus := {|mp1,mp2,obj| ;
0585 DbSelectArea( obj:area ) }
0586
0587 DbRegisterClient( self )
0588
0589 RETURN self
0590 </code></pre>
0591 </div>
0592 </figure></p>
0593 <p>The most important task of <span class="italic">:create()</span> is requesting system resources for the dialog window. This occurs when the method of the same name in the superclass is called and the parameters are simply passed on to it. The background color for the drawing area (<span class="italic">:drawingArea</span>) of the dialog window is then set. The call to <span class="italic">:setColorBG()</span> also defines the background color for all XBPs later displayed in the dialog window. This affects all XBPs that have a caption for displaying text. This simplifies programming because the background color of the individual XBPs with captions do not have to be set separately. Generally when the system colors defined in the system configuration are to be used <span class="italic">:setColorBG()</span> cannot be called. </p>
0594 <p>The lines that follow are important because they link the DataDialog object and the work area. First, whether the pointer is currently at Eof() is determined, then Select() determines the number of the current work area. Two code blocks are assigned to the callback slots <span class="italic">:close</span> and <span class="italic">:setDisplayFocus</span>. The method <span class="italic">:destroy()</span>(described below) is called after the xbeP_Close event. As soon as the DataDialog object receives focus, the code block in <span class="italic">:setDisplayFocus</span> is executed. In this code block, the work area managed by the DataDialog object is selected as the current work area using DbSelectArea(). This means that if the mouse is clicked in a DataDialog window, the correct work area is automatically selected. </p>
0595 <p>The call to DbRegisterClient() is critical for the program logic. This registers the DataDialog object in the work area so that it is automatically notified whenever anything in the work area changes. This includes notification of changes in the position of the record pointer. When the record pointer changes, the new data must be displayed by the XBPs that are listed in the instance variable <span class="italic">:editControls</span>. This is done using the method <span class="italic">:notify()</span> which is described later after the remaining methods in the DataDialog "life cycle" are discussed. The method <span class="italic">:configure()</span> is provided to handle changes in the work area managed by the DataDialog object and is shown below: </p>
0596 <p><figure class="code-block-segment" >
0597 <div class="code-block">
0598 <pre><code class="language-xpp">********************************************************************
0599 * Configure system resources
0600 * Register data dialog in new work area if necessary
0601 ********************************************************************
0602 METHOD DataDialog:configure( oParent, oOwner , ;
0603 aPos , aSize , ;
0604 aPParam, lVisible )
0605 LOCAL lRegister := (::area <> Select())
0606
0607 ::xbpDialog:configure( oParent, oOwner , ;
0608 aPos , aSize , ;
0609 aPParam, lVisible )
0610 IF lRegister
0611 (::area)->( DbDeRegisterClient( self ) )
0612 ENDIF
0613
0614 ::area := Select()
0615 ::appendMode := Eof()
0616
0617 IF lRegister
0618 DbRegisterClient( self )
0619 ENDIF
0620
0621 RETURN self
0622 </code></pre>
0623 </div>
0624 </figure></p>
0625 <p>A DataDialog object always manipulates the current work area. Because of this, the method <span class="italic">:configure()</span> compares the instance variable <span class="italic">:area</span> to Select() to determine whether the current area has changed. If it has changed, the object is deregistered in the old work area and registered in the new area. In addition, the system resources for the dialog window are also reconfigured in the call to the <span class="italic">:configure()</span> method of the superclass. </p>
0626 <p>The final method of the DataDialog life cycle is <span class="italic">:destroy()</span>. This method closes the database used by the DataDialog object and releases the system resources. The instance variables declared in the DataDialog class are reset to the values assigned in the method <span class="italic">:init()</span>: </p>
0627 <p><figure class="code-block-segment" >
0628 <div class="code-block">
0629 <pre><code class="language-xpp">********************************************************************
0630 * Release system resources and unregister data dialog from work area
0631 ********************************************************************
0632 METHOD DataDialog:destroy()
0633
0634 ::writeData()
0635 ::hide()
0636
0637 (::area)->( DbCloseArea() )
0638
0639 IF ! Empty( ::windowMenu )
0640 ::windowMenu:delItem( self ) // delete menu item in window menu
0641 ::windowMenu := NIL
0642 ENDIF
0643
0644 IF ! Empty( ::contextMenu )
0645 ::contextMenu:cargo := NIL // Delete reference of data
0646 ::contextMenu := NIL // dialog and context menu
0647 ENDIF
0648
0649 ::xbpDialog:destroy() // release system resources
0650 ::Area := 0 // and set instance variables
0651 ::appendMode := .F. // to values corresponding to
0652 ::editControls := {} // :init() state
0653 ::appendControls := {}
0654 ::newTitle := {|obj| obj:getTitle() }
0655
0656 RETURN self
0657 </code></pre>
0658 </div>
0659 </figure></p>
0660 <p>The method <span class="italic">:writeData()</span> is called in <span class="italic">:destroy()</span> in order to write all the data changes into the database before it is closed using DbCloseArea(). After the database is closed, the DataDialog object is implicitly deregistered from the work area and a call to DbDeRegisterClient() is not necessary. If a menu object is contained in the instance variable <span class="italic">:windowMenu</span>, the DataDialog object is removed from the list of menu items in this menu (the WindowMenu class is described in a previous section). The instance variable <span class="italic">:contextMenu</span> can contain a context menu that is activated by clicking the right mouse button. This mechanism is described in a later section. It is essential that the reference to the DataDialog object in the instance variable <span class="italic">:cargo</span> of the context menu be deleted because the method <span class="italic">:destroy()</span> is expected to eliminate all references to the DataDialog object. If a DataDialog object remains referenced anywhere, whether in a variable, an array, or an instance variable, it will not be removed from memory by the garbage collector. This concludes the discussion of the methods that perform tasks in the "life cycle" of a DataDialog object. </p>
0661 <p>One of the most important method of the DataDialog class is the <span class="italic">:notify()</span> method. This method is called whenever something is changed in the work area associated with the object. An abbreviated version of this method highlighting its essential elements is shown below: </p>
0662 <p><figure class="code-block-segment" >
0663 <div class="code-block">
0664 <pre><code class="language-xpp">********************************************************************
0665 * Notify method:
0666 * - Write data to fields prior to moving the record pointer
0667 * - Read data from fields after moving the record pointer
0668 ********************************************************************
0669 METHOD DataDialog:notify( nEvent, mp1, mp2 )
0670
0671 IF nEvent <> xbeDBO_Notify // no notify message
0672 RETURN self // ** return **
0673 ENDIF
0674
0675 DO CASE
0676 CASE mp1 == DBO_MOVE_PROLOG // record pointer is about
0677 ::writeData() // to be moved
0678
0679 CASE mp1 == DBO_MOVE_DONE .OR. ; // skip is done
0680 mp1 == DBO_GOBOTTOM .OR. ;
0681 mp1 == DBO_GOTOP
0682 ::readData()
0683
0684 ENDCASE
0685 RETURN self
0686 </code></pre>
0687 </div>
0688 </figure></p>
0689 <p>Calling the function DbRegisterClient() in the <span class="italic">:create()</span> method of the DataDialog object registers the object in the work area it uses. As soon as anything changes in this work area, the <span class="italic">:notify()</span> method is called. For record pointer movement, this method is called twice. The first time the DataDialog object receives the value represented by the constant DBO_MOVE_PROLOG (defined in the DMLB.CH file) as the <span class="italic">mp1</span>parameter. This is a signal that means "Warning the record pointer position is about to change." When it receives this message, the DataDialog object executes the method <span class="italic">:writeData()</span> which writes the data of the current record into the database. In the second call to <span class="italic">:notify()</span>, the object receives the value of the constant DBO_MOVE_DONE. This message tells the object "Ok, the pointer has been changed." In response to this message, the object executes the <span class="italic">:readData()</span> method which copies the fields of the new record into the edit buffers of the XBPs that are in the data dialogs <span class="italic">:editControls</span> instance variable. This allows the data in the new record to be edited. </p>
0690 <p>The <span class="italic">:notify()</span> method provides important program logic for the DataDialog object. In this method, the DataDialog object reacts to messages sent by the work area it uses. This method is only called after the object is registered in the work area using DbRegisterClient(). Or more precisely, it is only called when the object is registered in the database object (DBO) that manages the work area (a DBO is automatically created when a database is opened). Based on the event passed, the <span class="italic">:notify()</span> event determines whether a record should be read into XBPs or whether the data in the XBPs should be written into the database. The DataDialog object does not directly manage the data but does manage the XBPs contained in the array <span class="italic">:editControls</span>. Adding XBPs to this array is done using the method <span class="italic">:addEditControl()</span>. </p>
0691 <p><figure class="code-block-segment" >
0692 <div class="code-block">
0693 <pre><code class="language-xpp">********************************************************************
0694 * Add an edit control to internal list
0695 ********************************************************************
0696 METHOD DataDialog:addEditControl( oXbp )
0697 IF AScan( ::editControls, oXbp ) == 0
0698 AAdd( ::editControls, oXbp )
0699 ENDIF
0700 RETURN self
0701
0702 ********************************************************************
0703 * Add an append control to internal list
0704 ********************************************************************
0705 METHOD DataDialog:addAppendControl( oXbp )
0706 IF AScan( ::appendControls, oXbp ) == 0
0707 AAdd( ::appendControls, oXbp )
0708 ENDIF
0709 RETURN self
0710 </code></pre>
0711 </div>
0712 </figure></p>
0713 <p>The two methods <span class="italic">:addEditControl()</span> and <span class="italic">:addAppendControl()</span> are almost identical. One adds an Xbase Part to an array stored in the instance variable <span class="italic">:editControls</span> and the other adds an Xbase Part to <span class="italic">:appendControls</span>. When a DataDialog object executes the method <span class="italic">:readData()</span> or <span class="italic">:writeData()</span>, it sequentially processes the elements in the <span class="italic">:editControls</span> array and sends each element (each Xbase Part) the message to read or write its data. A code fragment is included below to illustrate how Xbase Parts can be added to the window of a DataDialog object and to the <span class="italic">:editControls</span> instance variable if appropriate. The variable <span class="italic">oDlg</span> references a DataDialog object. </p>
0714 <p><figure class="code-block-segment" >
0715 <div class="code-block">
0716 <pre><code class="language-xpp">oXbp := XbpStatic():new( oDlg:drawingArea,, {5,135}, {80,22} )
0717 oXbp:caption := "Lastname:" // static text is stored
0718 oXbp:options := XBPSTATIC_TEXT_RIGHT // only in the child list
0719 oXbp:create( )
0720
0721 oXbp := XbpSLE():new( oDlg:drawingArea,, {95,135}, {180,22} )
0722 oXbp:bufferLength := 20 // entry field linked to
0723 oXbp:tabStop := .T. // database
0724 oXbp:dataLink := {|x| IIf( x==NIL, LASTNAME, LASTNAME := x ) }
0725 oXbp:create():setData()
0726
0727 oDlg:addEditControl( oXbp ) // adds new XBP to :editControls
0728 </code></pre>
0729 </div>
0730 </figure></p>
0731 <p>The Xbase Parts appear in the drawing area of a dialog window, so <span class="italic">oDlg:drawingArea</span> must be specified as the parent. The code fragment creates an XbpStatic object to display the text "Lastname:" and an XbpSLE object to access and edit the database field called LASTNAME. Passing the XbpSLE object to the method <span class="italic">:addEditControl()</span> adds this Xbase Part to the <span class="italic">:editControls</span>array. In the child list of the DataDialog object there are now two XBPs but the <span class="italic">:editControls</span> array contains only the XBP for data that can be edited. The methods <span class="italic">:readData()</span>, <span class="italic">:validateAll()</span>and <span class="italic">:writeData()</span> assume that all the Xbase Parts that can edit data are included in the <span class="italic">:editControls</span> array. The program code for <span class="italic">:readData()</span> is shown below: </p>
0732 <p><figure class="code-block-segment" >
0733 <div class="code-block">
0734 <pre><code class="language-xpp">********************************************************************
0735 * Read current record and transfer data to edit controls
0736 ********************************************************************
0737 METHOD DataDialog:readData()
0738 LOCAL i, imax := Len( ::editControls )
0739
0740 FOR i:=1 TO imax // Transfer data from file
0741 ::editControls[i]:setData() // to XBPs
0742 NEXT
0743
0744 Eval( ::newTitle, self ) // Set new window title
0745
0746 IF Eof() // enable/disable XBPs
0747 IF ! ::appendMode // active only during
0748 imax := Len( ::appendControls ) // APPEND
0749 FOR i:=1 TO imax //
0750 ::appendControls[i]:enable() // Hit Eof(), so
0751 NEXT // enable XBPs
0752 ENDIF
0753 ::appendMode := .T.
0754 ELSEIF ::appendMode // Record pointer was
0755 imax := Len( ::appendControls ) // moved from Eof() to
0756 FOR i:=1 TO imax // an existing record.
0757 ::appendControls[i]:disable() // Disable append-only
0758 NEXT // XBPs
0759 ::appendMode := .F.
0760 ENDIF
0761
0762 RETURN
0763 </code></pre>
0764 </div>
0765 </figure></p>
0766 <p>The <span class="italic">:setData()</span> method in the first FOR...NEXT loop causes all the XBPs referenced in the instance variable <span class="italic">:editControls</span> to re-read their edit buffers by copying the return value of the data code block contained in <span class="italic">:dataLink</span> into their edit buffer. The remaining code just enables and disables the XBPs in the <span class="italic">:appendControls</span> list. In addition to reading the data in the database fields, this method is the appropriate place to enable or disable those Xbase Parts that should only be edited when a new record is being appended. </p>
0767 <p>The counterpart of <span class="italic">:readData()</span> is the <span class="italic">:writeData()</span> method. In this method, the data in the edit buffer of each Xbase Part listed in <span class="italic">:editControls</span> is written back to the database. This method involves relatively extensive program code, because it performs record locking and identifies whether a new record should be appended. </p>
0768 <p><figure class="code-block-segment" >
0769 <div class="code-block">
0770 <pre><code class="language-xpp">********************************************************************
0771 * Write data from edit controls to file
0772 ********************************************************************
0773 METHOD DataDialog:writeData()
0774 LOCAL i, imax
0775 LOCAL lLocked := .F. , ; // Is record locked?
0776 lAppend := .F. , ; // Is record new?
0777 aChanged := {} , ; // XBPs containing changed data
0778 nOldArea := Select() // Current work area
0779
0780 dbSelectArea( ::area )
0781
0782 IF Eof() // Append a new record
0783 IF ::validateAll() // Validate data first
0784 APPEND BLANK
0785 lAppend := .T.
0786 aChanged := ::editControls // Test all possible changes
0787 lLocked := ! NetErr() // Implicit lock
0788 ELSE
0789 MsgBox("Invalid data") // Do not write invalid data
0790 DbSelectArea( nOldArea ) // to new record
0791 RETURN .F. // *** RETURN ***
0792 ENDIF
0793 ELSE
0794 imax := Len( ::editControls ) // Find all XBPs containing
0795 FOR i:=1 TO imax // changed data
0796 IF ::editControls[i]:changed
0797 AAdd( aChanged, ::editControls[i] )
0798 ENDIF
0799 NEXT
0800
0801 IF Empty( aChanged ) // Nothing has changed, so
0802 DbSelectArea( nOldArea ) // no record lock necessary
0803 RETURN .T. // *** RETURN ***
0804 ENDIF
0805
0806 lLocked := DbRLock( Recno() ) // Lock current record
0807 ENDIF
0808
0809 IF ! lLocked
0810 MsgBox( "Record is currently locked" )
0811 DbSelectArea( nOldArea ) // Record lock failed
0812 RETURN .F. // *** RETURN ***
0813 ENDIF
0814
0815 imax := Len( aChanged ) // Write access is necessary
0816 FOR i:=1 TO imax // only for changed data
0817 IF ! lAppend
0818 IF ! aChanged[i]:validate()
0819 aChanged[i]:undo() // invalid data !
0820 LOOP // undo changes and validate
0821 ENDIF // next XBP
0822 ENDIF
0823 aChanged[i]:getData() // Get data from XBP and
0824 NEXT // write to file
0825
0826 DbCommit() // Commit file buffers
0827 DbRUnlock( Recno() ) // Release record lock
0828
0829 IF ::appendMode // Disable append-only XBPs
0830 imax := Len( ::appendControls ) // after APPEND
0831 FOR i:=1 TO imax
0832 ::appendControls[i]:disable()
0833 NEXT
0834 ::appendMode := .F.
0835
0836 IF ! Empty( ::contextMenu )
0837 ::contextMenu:disableBottom()
0838 ::contextMenu:enableEof()
0839 ENDIF
0840 ENDIF
0841
0842 DbSelectArea( nOldArea )
0843
0844 RETURN .T.
0845 </code></pre>
0846 </div>
0847 </figure></p>
0848 <p>Appending a new record requires special logic in the <span class="italic">:writeData()</span>method of the DataDialog object. A special empty record (the phantom record) is automatically available when the pointer is positioned at Eof(). If the pointer is positioned at Eof(), the method <span class="italic">:readData()</span> has copied "empty" values from the database fields into the XBP edit buffers for all of the XBPs listed in the instance variable <span class="italic">:editControls</span>. Because of this, all XBPs contain valid data types. But there is no guarantee that valid data is also contained in the edit buffers of each XBP. This means that data validation must be performed before the record is even appended. Since there is not yet a record, all the data to be saved is found only in the edit buffer of the corresponding Xbase Parts. The method <span class="italic">:validateAll()</span> is called before a new record is appended which is to receive data from the edit buffers of the Xbase Parts. </p>
0849 <p>Data validation is especially important when a new record is appended because no previously valid data exists to allow the changes to the individual edit buffers to be voided. For records that are being edited, the method <span class="italic">:undo()</span> allows changes to the values in the edit buffers to be voided. But this approach assumes there is an original field value that is valid. This is only true if the record being edited existed prior to editing. When a record is appended, the original values are "empty" values which are probably not valid. </p>
0850 <p>In <span class="italic">:writeData()</span>, this situation is handled by calling the method <span class="italic">:validateAll()</span> before the new record is appended to the file using APPEND BLANK. If data validation fails on even one field, a message box containing the text "Invalid data" is displayed and a new record is not appended. The invalid data remains in the edit buffers of the corresponding Xbase Parts (<span class="italic">:editControls</span>) and can be corrected by the user. When an existing record is edited, data validation occurs individually for each Xbase Part. If the <span class="italic">:validate()</span> method of an XBP returns the value .F. (false) (indicating invalid data), the <span class="italic">:undo()</span> method of the XBP is executed which copies the original, valid data back into the edit buffer. </p>
0851 <p>If any XBPs listed in <span class="italic">:editControls</span> have been changed, the record is locked using DbRLock(). After validation, data from the edit buffers is written into the database by calling the method <span class="italic">:getData()</span>. The function DbCommit() ensures that data in the file buffers are written into the database. Finally the record lock is released. </p>
0852 <p>The <span class="italic">:writeData()</span> method handles the problems of data validation and appending records as they occur in an event driven environment. This process is controlled by the mouse or rather by the user who causes the mouse clicks. Even though <span class="italic">:writeData()</span> is called from only one place in the <span class="italic">:notify()</span> method, it is impossible to foresee when this method will be called. While it is clear that it is called when the record pointer moves it is not possible to predict which record will be current when the method <span class="italic">:writeData()</span> is called. The special case occurs when the pointer is located on the phantom record. In this case data validation cannot be reversed using the <span class="italic">:undo()</span> method because no previously validated data exists. For this reason, all data must be validated before a new record can be appended. The method which checks that all data is valid is called <span class="italic">:validateAll()</span> and is shown below: </p>
0853 <p><figure class="code-block-segment" >
0854 <div class="code-block">
0855 <pre><code class="language-xpp">********************************************************************
0856 * Validate data of all edit controls
0857 * This is necessary prior to appending a new record to the database
0858 ********************************************************************
0859 METHOD DataDialog:validateAll()
0860 LOCAL i := 0, imax := Len( ::editControls )
0861 LOCAL lValid := .T.
0862
0863 DO WHILE ++i <= imax .AND. lValid
0864 lValid := ::editControls[i]:validate()
0865 ENDDO
0866
0867 RETURN lValid
0868 </code></pre>
0869 </div>
0870 </figure></p>
0871 <p>The method consists only of a DO WHILE loop that is terminated as soon as the XBP :validate method signals invalid data. The method <span class="italic">:validate()</span> is executed for all XBPs listed in <span class="italic">:editControls</span>. This method always returns the value .T. (true) unless there is a code block contained in the instance variable <span class="italic">:validate</span>. If a code block is contained in this instance variable, it is executed and receives the XBP as the first parameter. This code block performs data validation and returns .T. (true) if the data is valid. </p>
0872 <p>Special data validation is needed for the primary key in a database. The primary key is the value in the database that uniquely identifies each record. There is always an index for the primary key. The method <span class="italic">:isIndexUnique()</span> (shown below) tests whether a value already exists as a primary key in an index file of the database. This method demonstrates an extremely important aspect for the use of DataDialog objects (more precisely: for the use of the function DbRegisterClient()): </p>
0873 <p><figure class="code-block-segment" >
0874 <div class="code-block">
0875 <pre><code class="language-xpp">********************************************************************
0876 * Check whether an index value does *not* exist in an index
0877 ********************************************************************
0878 METHOD DataDialog:isIndexUnique( nOrder, xIndexValue )
0879 LOCAL nOldOrder := OrdNumber()
0880 LOCAL nRecno := Recno()
0881 LOCAL lUnique := .F.
0882
0883 DbDeRegisterClient( self ) // Suppress notification from DBO
0884 // to self during DbSeek() !!!
0885 OrdSetFocus( nOrder )
0886
0887 lUnique := .NOT. DbSeek( xIndexValue )
0888
0889 OrdSetFocus( nOldOrder )
0890 DbGoTo( nRecno )
0891
0892 DbRegisterClient( self )
0893
0894 RETURN lUnique
0895 </code></pre>
0896 </div>
0897 </figure></p>
0898 <p>The functionality of the <span class="italic">:isIndexUnique()</span> method is very limited. All it does is search for a value in the specified index and return the value .T. (true) if the value is not found. An important point shown here is that the DataDialog object executing the method must be deregistered in the work area. It was initially registered in the work area by the method <span class="italic">:create()</span>, causing the method <span class="italic">:notify()</span> to be called every time the record pointer changes. In this case, it is a method of the DataDialog object changing the pointer by calling DbSeek(). If the DataDialog object were not deregistered, an implicit recursion would result since each change to the pointer via DbSeek() calls the method <span class="italic">:notify()</span>. For this reason, DbDeRegisterClient() is used to deregister the DataDialog object prior to the call to DbSeek(). It is again registered in the work area using DbRegisterClient() after DBSeek(). </p>
0899 <p>In summary, the DataDialog class solves many problems which must be considered when programming GUI applications that work with databases. Record pointer movements are easily identified in the method <span class="italic">:notify()</span> that is automatically called when the DataDialog object is registered in the current work area using the function DbRegisterClient(). Before the record pointer is moved, a DataDialog object copies the changed data in <span class="italic">:editControls</span> back into the database. After the record pointer is changed, a DataDialog object displays the current data. Data validation occurs prior to data being written into the database either by a new record being appended or existing data being overwritten. Whether new data is being saved or existing data modified is determined by the DataDialog object. </p>
0900 </div>
0901 </div>
0902 </div>
0903 <div>
0904 <a class="anchor" NAME="datadialog_and_data_entry_screens"></a>
0905 <div class="ui vertical basic segment">
0906 <div class="ui header" >DataDialog and data entry screens</div>
0907 <div>
0908 <p>Objects of the DataDialog class described in the previous section are appropriate for programming data entry screens in GUI applications. Each input screen is an independent window that is displayed as a child of the application window. In each child window (input screen) Xbase Parts are added to edit the database fields. Because they are separate windows, it is recommended that each entry screen be programmed in a separate routine. The tasks of this routine include opening all databases required for the entry screen, creating the child window (DataDialog), and adding the Xbase Parts needed for editing the database fields to the entry screen. In the example application MDIDEMO, two entry screens are programmed, one for customer data and one for parts data. The process of creating the data entry screen is the same in both cases. Sections of the program code from the file MDICUST.PRG are discussed below to illustrate various aspects significant when programming data entry screens: </p>
0909 <p><figure class="code-block-segment" >
0910 <div class="code-block">
0911 <pre><code class="language-xpp">********************************************************************
0912 * Customer Dialog
0913 ********************************************************************
0914 PROCEDURE Customer( nRecno )
0915 LOCAL oXbp, oStatic, drawingArea, oDlg
0916 FIELD CUSTNO, MR_MRS, LASTNAME, FIRSTNAME, STREET, CITY, ZIP , ;
0917 PHONE , FAX , NOTES , BLOCKED , TOTALSALES
0918
0919 IF ! OpenCustomer( nRecno ) // open customer database
0920 RETURN
0921 ENDIF
0922
0923 oDlg := DataDialog():new( RootWindow():drawingArea ,, ;
0924 {100,100}, {605,315},, .F. )
0925 oDlg:title := "Customer No: "+ LTrim( CUSTNO )
0926 oDlg:icon := ICON_CUSTOMER
0927 oDlg:create()
0928 /* ... */
0929 </code></pre>
0930 </div>
0931 </figure></p>
0932 <p>The Customer() procedure creates a new child window in the MDI application where customer data can be edited. LOCAL variables are first declared to reference the Xbase Parts created and all of the database fields are identified to the compiler as field variables. Before the child window (DataDialog) is created, the required database(s) must be open. This occurs in the function OpenCustomer() which returns the value .F. (false) only if the customer database could not be opened. Opening the database might fail because another workstation has the file exclusively open or the file is simply not found. </p><div class="ui blue icon message" >
0933 <i class="icon info circle"></i>
0934 <div class="content">
0935 <p> Testing for the existence of files should have already been done at startup in the Main procedure. </p>
0936 </div>
0937 </div><p>When the required file(s) can be opened, the dialog window is created. This is done using DataDialog class method <span class="italic">:new()</span> which generates a new instance of the DataDialog class. The parent of the new object is the drawing area (<span class="italic">:drawingArea</span>) of the application window created in AppSys() and returned by the user-defined function RootWindow(). As soon as the child window is created, the Resource ID for an icon must be entered into the instance variable <span class="italic">:icon</span>. This icon is displayed within the application window when the child window is minimized. In this example, the #define constant ICON_CUSTOMER is used. An icon is declared in a resource file and must be linked to the executable file using the resource compiler. If no icon ID is specified for a child window, the window contents in the range from point {0,0} to point {32,32} are used as the icon when the window is minimized. This means everything visible in the lower left corner of the child window up to the point {32,32} appears as the symbol for the minimized child window. </p>
0938 <p>In the example program MDICUST.PRG, only the CUSTOMER.DBF database file needs to be opened. It is important for the customer database to be reopened each time the procedure Customer() is called. This is shown in the program code of the function OpenCustomer(): </p>
0939 <p><figure class="code-block-segment" >
0940 <div class="code-block">
0941 <pre><code class="language-xpp">********************************************************************
0942 * Open customer database
0943 ********************************************************************
0944 FUNCTION OpenCustomer( nRecno )
0945 LOCAL nOldArea := Select(), lDone := .F.
0946
0947 USE Customer NEW
0948 IF ! NetErr()
0949 SET INDEX TO CustA, CustB
0950 IF nRecno <> NIL
0951 DbGoto( nRecno )
0952 ENDIF
0953 lDone := .T.
0954 ELSE
0955 DbSelectArea( nOldArea )
0956 MsgBox( "Database cannot be opened" )
0957 ENDIF
0958
0959 RETURN lDone
0960 </code></pre>
0961 </div>
0962 </figure></p>
0963 <p>Each instance of the DataDialog class (each data entry screen) manages its own work area. If the Customer() procedure is executed 10 times, 10 data entry screens are created for customer data and the CUSTOMER.DBF file is opened 10 times. This rule is standard for event oriented GUI applications: <span class="bold">Each dialog opens its own database</span>. This requires some consideration for programmers coming from DOS procedural programming, since the same approach is not appropriate under DOS because of the 255 file handle limit. This limit does not exist under a 32bit operating system. Access to a single database file from several dialogs does require that protection mechanisms be implemented in the Xbase++ application. The mechanisms for locking records or files is sufficient under Xbase++ so that when the program allows simultaneous access on a network, it will also handle the file being opened multiple times within a single application. </p>
0964 <p>Each call to OpenCustomer() opens the customer database in a new work area and the method <span class="italic">DataDialog:create()</span> registers the dialog window in this new work area. From this point on, the DataDialog object is notified about each change in the work area (via the method <span class="italic">:notify()</span>) and can be assigned the appropriate XBPs (via <span class="italic">:editControls</span>) so that they can automatically be handled by the DataDialog methods. (Note for Clipper programmers: the expression USE Customer NEW is allowed in Xbase++ without specifying an alias name. If a database is opened multiple times, Xbase++ provides a unique alias name formed from the file name and the number of the current work area). </p>
0965 <p>When the database is open and the DataDialog (the child window) is created, the most important processes for programming a data entry screen are nearly complete. Xbase Parts must still be added to the dialog window. These include both XBPs that contribute to the visual organization of the data entry screen (borders and text) and XBPs that allow access to database fields via <span class="italic">:dataLink</span>. This second group is primarily made up of objects from the classes XbpSLE, XbpCheckBox and XbpMLE. XbpSLE objects provide single line data entry fields, XbpCheckBox objects manage logical values and XbpMLE objects provide multiple line data entry fields that allow memo fields to be edited. Objects of the classes XbpSLE, XbpCheckBox and XbpMLE are sufficient to program the sections of data entry screens where database fields are edited. </p>
0966 <p>Boxes that are displayed by XbpStatic objects are used to provide visually organization of data entry screens. Entry fields are not only visual separated when they appear in a box, but the fields can also be grouped in the program logic. The following program section shows another example of code defining a part of a data entry screen. This example is a continuation of the Customer() procedure: </p>
0967 <p><figure class="code-block-segment" >
0968 <div class="code-block">
0969 <pre><code class="language-xpp">// Get drawing area from dialog
0970 drawingArea := oDlg:drawingArea
0971
0972 oStatic := XbpStatic():new( drawingArea ,, {12,227}, {579,58} )
0973 oStatic:type := XBPSTATIC_TYPE_GROUPBOX
0974 oStatic:create()
0975 </code></pre>
0976 </div>
0977 </figure></p>
0978 <p>In the above sample, the drawing area (<span class="italic">:drawingArea</span>) of the dialog window is retrieved and passed as the parent for the dialog elements to be displayed in the window. The first dialog element is an XbpStatic object responsible for displaying a group box. This box displays text (the caption) in the upper left corner. A group box is used for grouping data entry fields and acts as the parent for all the Xbase Parts which are displayed within the group box. In other words: the parent for a group box is the <span class="italic">:drawingArea</span> and the parent for the Xbase-Parts displayed within the group box is the XbpStatic object representing the box. For this reason the XbpStatic object is referenced in the variable <span class="italic">oStatic</span> and is used as the parent in the example of creating data entry fields shown below: </p>
0979 <p><figure class="code-block-segment" >
0980 <div class="code-block">
0981 <pre><code class="language-xpp">oXbp := XbpSLE():new( oStatic,, {95,135}, {180,22} )
0982 oXbp:bufferLength := 20
0983 oXbp:dataLink := {|x| IIf( x==NIL, Trim(LASTNAME), LASTNAME := x ) }
0984 oXbp:create():setData()
0985
0986 oDlg:addEditControl( oXbp ) // register Xbp as EditControl
0987 </code></pre>
0988 </div>
0989 </figure></p>
0990 <p>In the above sample, a data entry field is created for display within a group box (the parent of the data entry field is oStatic). The XbpSLE object accesses the database field NAME and the length of the edit buffer is limited to the length of the database field. The field LASTNAME has 20 characters in the example. A general incompatibility between database fields and XbpSLE objects is handled in the <span class="italic">:dataLink</span> code block. When data is read from the database field, the padding blank spaces are included. If the data is copied directly from the field LASTNAME into the edit buffer of the XbpSLE object, 20 characters are always included in the edit buffer even for a name such as "Smith" that is only five characters long. The blank spaces stored in the database field are copied into the edit buffer of the XbpSLE object. The result is that the edit buffer of the XBP object is already full and characters can only be added to the edit buffer in "Overwrite" mode. An XbpSLE object considers blank spaces as fully valid characters and to prevent these problems, the blank spaces at the end of the name (trailing spaces) are explicitly removed using Trim() when the data is read from the database field LASTNAME within <span class="italic">:dataLink</span>. </p>
0991 <p>An XbpSLE object can only edit values in its edit buffer that are of "character" type. The maximum number of characters is 32KB. Values of numeric or date type must be converted to a character string when copied into the edit buffer of an XbpSLE object and converted back to the correct type before being written into the database field. This must be done in the data code block contained in <span class="italic">:dataLink</span>. Examples for code blocks which perform type conversions are shown below: </p>
0992 <p><figure class="code-block-segment" >
0993 <div class="code-block">
0994 <pre><code class="language-xpp">oXbp:dataLink := {|x| IIf(x==NIL, Transform( FIELD->NUMERIC, "@N"), ;
0995 FIELD->NUMERIC := Val(x) ) }
0996 oXbp:dataLink := {|x| IIf(x==NIL, DtoC( FIELD->DATE ), ;
0997 FIELD->DATE := CtoD(x) ) }
0998 </code></pre>
0999 </div>
1000 </figure></p>
1001 <p>When database fields are read into the edit buffer of an XbpSLE object blank spaces must be deleted and numeric and date values must be converted to character strings. When the modified data is saved to the database fields, values for date and numeric fields must again be converted to the correct data type. This task is performed by the data code block assigned to the instance variable <span class="italic">:dataLink</span>. </p>
1002 <p>Another task of the data code block contained in <span class="italic">:dataLink</span> occurs when more than one file is required for the data entry screen (the DataDialog). In this case, fields from several databases are edited in a single data entry screen and the data code block must also select the correct work area for the field variable. </p>
1003 </div>
1004 </div>
1005 </div>
1006 <div>
1007 <a class="anchor" NAME="program_control_in_dialog_windows"></a>
1008 <div class="ui vertical basic segment">
1009 <div class="ui header" >Program control in dialog windows</div>
1010 <div>
1011 <p>The previous discussions of GUI application concepts have focused on the basic organization of a GUI program. The key issues discussed were the program start, program execution, and program end. These correspond to AppSys() with a menu system, the Main procedure with the event loop and AppQuit(), respectively. The DataDialog class was discussed as a mechanism for linking dialog windows with DatabaseEngines. This class offers solutions to problems that can occur during simultaneous access on a network or when a database is opened multiple times in a single application. In the previous section, incorporating Xbase Parts into a dialog window was illustrated. The final remaining question for programming GUI applications is: How is the program controlled within an individual dialog window? </p>
1012 <p>A distinction must be made between controlling a window and controlling an application. The overall running of the application is controlled by the application menu installed in the application window. In an SDI application, control of the application is basically the same as control of the dialog window, since the application consists of only a single dialog window. In the SDIDEMO example application, control of the application through the menu system includes selecting the data entry screens for customer data or for parts data. Control within windows occurs using pushbuttons that allow record pointer movement within the customer file or parts file, cause the current data to be saved or terminate the data input. </p>
1013 <p>In the example application MDIDEMO, the application control is limited to opening the customer or parts data entry screen. A child window presents data for a customer or a part. As soon as a child window is opened, the application and the application menu no longer have control over the newly opened window. Program control within a child window is performed in an MDI application by a context menu that is an essential control element for program control. A context menu is generally activated by clicking the right mouse button. It is displayed on the screen as a Popup menu. Its menu items provide a selection of actions that are appropriate to execute within the window or in relation to the dialog element where the right mouse click occurred. </p>
1014 <p>The context menu in the MDIDEMO example application includes program control of database navigation (DbSkip(), DbGoBottom(), DbGoTop()) and elementary database operations such as "Search", "Delete" and "New record". Programming a context menu requires the definition of an XbpMenu object and is otherwise similar to programming application menu objects. As an example, the program code to create the context menu for the customer database used in MDIDEMO is shown below: </p>
1015 <p><figure class="code-block-segment" >
1016 <div class="code-block">
1017 <pre><code class="language-xpp">********************************************************************
1018 * Create context menu for customer dialog
1019 ********************************************************************
1020 STATIC FUNCTION ContextMenu()
1021 STATIC soMenu
1022
1023 IF soMenu == NIL
1024 soMenu := DataDialogMenu():new()
1025 soMenu:title := "Customer context menu"
1026 soMenu:create()
1027
1028 soMenu:addItem( { "~New", ;
1029 {|mp1,mp2,obj| DbGoTo( LastRec()+1 ) } ;
1030 } )
1031
1032 soMenu:addItem( { "~Seek" , ;
1033 {|mp1,mp2,obj| SeekCustomer( obj:cargo ) } ;
1034 } )
1035
1036 soMenu:addItem( { "~Delete" , ;
1037 {|mp1,mp2,obj| DeleteCustomer( obj:cargo ) } ;
1038 } )
1039
1040 soMenu:addItem( { "S~ave" , ;
1041 {|mp1,mp2,obj| obj:cargo:writeData() } ;
1042 } )
1043
1044 soMenu:addItem( MENUITEM_SEPARATOR )
1045
1046 soMenu:addItem( { "~First" , ;
1047 {|mp1,mp2,obj| DbGoTop() } ;
1048 } )
1049
1050 soMenu:addItem( { "~Last" , ;
1051 {|mp1,mp2,obj| DbGoBottom() } ;
1052 } )
1053
1054 soMenu:addItem( MENUITEM_SEPARATOR )
1055
1056 soMenu:addItem( { "~Previous" , ;
1057 {|mp1,mp2,obj| DbSkip(-1) } ;
1058 } )
1059
1060 soMenu:addItem( { "~Next" , ;
1061 {|mp1,mp2,obj| DbSkip(1) } ;
1062 } )
1063
1064 // menu items are disabled after Bof() or GoTop()
1065 soMenu:disableTop := { 6, 9 }
1066
1067 // menu items are disabled after GoBottom()
1068 soMenu:disableBottom := { 7, 10 }
1069
1070 // menu items are disabled at Eof()
1071 soMenu:disableEof := { 1, 2, 3 }
1072
1073 ENDIF
1074
1075 RETURN soMenu
1076 </code></pre>
1077 </div>
1078 </figure></p>
1079 <p>A code block is defined for each menu item in the context menu. This code block is executed when the user selects the menu item. Many of the code blocks control database navigation using functions such as DbSkip(), DbGoTop(), and DbGoBottom(). The DataDialog object is automatically notified of these operations (its <span class="italic">:notify()</span> method is called) since it is registered in the work area. The context menu itself can only be activated on the DataDialog object (child window) which currently has focus. The menu is activated with a right mouse click that must occur within the DataDialog window. The DataDialog window activates its context menu through the following callback code block (see MDICUST.PRG file, function Customer()): </p>
1080 <p><figure class="code-block-segment" >
1081 <div class="code-block">
1082 <pre><code class="language-xpp">drawingArea:RbDown := {|mp1,mp2,obj| ;
1083 ContextMenu():cargo := obj:setParent(), ;
1084 ContextMenu():popup( obj, mp1 ) }
1085 </code></pre>
1086 </div>
1087 </figure></p>
1088 <p>The <span class="italic">:drawingArea</span> is the drawing area of the DataDialog window. The ContextMenu() function is shown above. This function returns the contents of the STATIC variable <span class="italic">soMenu</span>, which is the context menu. The code block parameter <span class="italic">obj</span> contains a reference to the Xbase Part that is processing the event xbeM_RbDown (right mouse button is pressed). In this case, this is the drawing area of the DataDialog (<span class="italic">:drawingArea</span>) and the expression <span class="italic">obj:setParent()</span> returns the DataDialog object that is assigned to the <span class="italic">:cargo</span> instance variable of the context menu. This all occurs before the context menu is displayed using the method <span class="italic">:popUp()</span>. The current mouse coordinates (relative to <span class="italic">obj</span>) are contained in <span class="italic">mp1</span>. This allows the return value of ContextMenu() (the context menu) to be displayed at the position of the mouse pointer. </p>
1089 <p>When a menu item is selected in the context menu, the DataDialog object where the context menu is activated is always contained in the <span class="italic">:cargo</span> instance variable. This DataDialog object has the focus (otherwise it would not react to a right mouse button click). The DataDialog object with the focus was previously selected via the callback code block <span class="italic">:setDisplayFocus</span> which sets the appropriate work area as the current work area. Database navigation can occur in the context menu by simply calling DbSkip() or DbGobottom() without the work area where the movement is to occur being specified. The work area is selected by the DataDialog object when it receives the focus. The context menu can only be activated on a DataDialog object that has the focus because only the DataDialog object with focus reacts to the event xbeM_RbDown and the context menu is only activated in the callback code block <span class="italic">:RbDown</span>. </p>
1090 <p>This discussion outlines program control via a context menu as it is used in the example application MDIDEMO (it may be easier to follow by stepping through the code in the debugger). In conclusion, a context menu can be an important control element in a GUI application. Generally a context menu is not specific to a work area but calls functionality that must operate regardless of the work area. In short: a context menu controls an Xbase Part. </p>
1091 </div>
1092 </div>
1093 </div>
1094 </div>
1095
1096 @SECTION feedback
1097 <a href="/feedback/feedback.cxp?bid=2515&fid=F14BGD&title=Creating%20GUI%20applications" rel="nofollow">this form</a>
1098
1099 @SECTION right-menu-go-to
1100 <div class="menu" id="wdg-goto-menu">
1101 <a class="item" href="#feedback" id="wdg-right-menu-feedback">Feedback</a><a class="item" href="#tasks_of_appsys" id="wdg-right-menu-item">Tasks of AppSys()</a><a class="item" href="#the_menu_system_of_an_application" id="wdg-right-menu-item">The menu system of an application</a><a class="item" href="#tasks_of_the_main_procedure" id="wdg-right-menu-item">Tasks of the Main procedure</a><a class="item" href="#a_datadialog_class_for_integrating_databases" id="wdg-right-menu-item">A DataDialog class for integrating databases</a><a class="item" href="#datadialog_and_data_entry_screens" id="wdg-right-menu-item">DataDialog and data entry screens</a><a class="item" href="#program_control_in_dialog_windows" id="wdg-right-menu-item">Program control in dialog windows</a>
1102 <a class="item" href="#end-of-page">End of page</a>
1103 </div>
1104