<CXP:BuildError/>

Compilation Error

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.

Files:

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

Message(s):

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)

Build Process

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


Full Source

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 &quot;How is this programmed?&quot; and &quot;Where is this implemented?&quot; 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 &quot;application type&quot; 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 := &quot;Toys &amp; Fun Inc. [Xbase++ - SDI Demo]&quot; 
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( &quot;8.Help.normal&quot; ) 
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( &quot;SDIDEMO.HLP&quot;, &quot;Help for SDI demo&quot; ) 
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, &quot;~File&quot; ) 
0170
0171    oMenu:addItem( { &quot;Options&quot;, } ) 
0172    oMenu:addItem( MENUITEM_SEPARATOR ) 
0173    oMenu:addItem( { &quot;~Exit&quot; , NIL } ) 
0174
0175    oMenu:itemSelected := ; 
0176       {|nItem,mp2,obj| MenuSelect(obj, 100+nItem) } 
0177
0178    oMenuBar:addItem( {oMenu, NIL} ) 
0179
0180    // Second sub-menu  -&gt; customer data 
0181    // 
0182    oMenu := SubMenuNew( oMenuBar, &quot;C~ustomer&quot; ) 
0183    oMenu:setName( CUST_MENU ) 
0184
0185    oMenu:addItem( { &quot;~New&quot;   , NIL } ) 
0186    oMenu:addItem( { &quot;~Seek&quot;  , NIL } ) 
0187    oMenu:addItem( { &quot;~Change&quot;, NIL } ) 
0188    oMenu:addItem( { &quot;~Delete&quot;,NIL , 0, ; 
0189                     XBPMENUBAR_MIA_DISABLED } ) 
0190    oMenu:addItem( { &quot;~Print&quot; ,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 &quot;Help&quot; 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, &quot;~Help&quot; ) 
0227    oMenu:addItem( { &quot;Help ~index&quot;, ; 
0228                     {|| HelpObject():showHelpIndex() } } ) 
0229
0230    oMenu:addItem( { &quot;~General help&quot;, ; 
0231                     {|| HelpObject():showGeneralHelp() } } ) 
0232
0233    oMenu:addItem( { &quot;~Using help&quot;, ; 
0234                     {|| HelpObject():showHelp(IPFID_HELP_HELP) } } ) 
0235
0236    oMenu:addItem( { &quot;~Keys help&quot;, ; 
0237                     {|| HelpObject():showKeysHelp() } } ) 
0238
0239    oMenu:addItem( MENUITEM_SEPARATOR ) 
0240
0241    oMenu:addItem( { &quot;~Product information&quot;, ; 
0242                     {|| MsgBox(&quot;Xbase++ SDI Demo&quot;) } } ) 
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 &quot;Using help&quot;. 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        := &quot;~Window&quot; 
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 &quot;Window&quot; 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 &gt; 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 &quot;Window&quot; 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 &quot;Gra.ch&quot; 
0379 #include &quot;Xbp.ch&quot; 
0380 #include &quot;AppEvent.ch&quot; 
0381 #include &quot;Mdidemo.ch&quot; 
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( { &quot;CUSTA.NTX&quot;, &quot;CUSTB.NTX&quot;, ; 
0392                          &quot;PARTA.NTX&quot;, &quot;PARTB.NTX&quot;  } ) 
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( &#64;mp1, &#64;mp2, &#64;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 &lt;= 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                  &quot;Do you really want to quit ?&quot;, ; 
0438                  &quot;Quit&quot;, ; 
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( {&quot;~Quit&quot;, {|| 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 &quot;Close&quot; 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">&quot;DataRef() - The connection between XBP and DBE&quot;</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: &quot;When and how is data input validated?&quot; and &quot;When is data written to the database?&quot;. 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 &quot;Save&quot; 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 &quot;life cycle&quot; of a DataDialog object: </p>
0492     <p><figure class="code-block-segment" >
0493        <div class="code-block">
0494         <pre><code  class="language-xpp">#include &quot;Gra.ch&quot; 
0495 #include &quot;Xbp.ch&quot; 
0496 #include &quot;Dmlb.ch&quot; 
0497 #include &quot;Common.ch&quot; 
0498 #include &quot;Appevent.ch&quot; 
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 &quot;life cycle&quot; 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 &quot;life cycle&quot; 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 &lt;&gt; Select()) 
0606
0607    ::xbpDialog:configure( oParent, oOwner , ; 
0608                           aPos   , aSize  , ; 
0609                           aPParam, lVisible ) 
0610    IF lRegister 
0611      (::area)-&gt;( 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)-&gt;( 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 &quot;life cycle&quot; 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 &lt;&gt; 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 &quot;Warning the record pointer position is about to change.&quot; 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 &quot;Ok, the pointer has been changed.&quot; 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 := &quot;Lastname:&quot;          // 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 &quot;Lastname:&quot; 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(&quot;Invalid data&quot;)     // 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( &quot;Record is currently locked&quot; ) 
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 &quot;empty&quot; 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 &quot;empty&quot; 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 &quot;Invalid data&quot; 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 &lt;= 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 := &quot;Customer No: &quot;+ 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 &lt;&gt; NIL 
0951          DbGoto( nRecno ) 
0952       ENDIF 
0953       lDone := .T. 
0954    ELSE 
0955       DbSelectArea( nOldArea ) 
0956       MsgBox( &quot;Database cannot be opened&quot; ) 
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 &quot;Smith&quot; 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 &quot;Overwrite&quot; 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 &quot;character&quot; 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-&gt;NUMERIC, &quot;&#64;N&quot;), ; 
0995                                   FIELD-&gt;NUMERIC := Val(x) ) } 
0996 oXbp:dataLink := {|x| IIf(x==NIL, DtoC( FIELD-&gt;DATE ), ; 
0997                                   FIELD-&gt;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 &quot;Search&quot;, &quot;Delete&quot; and &quot;New record&quot;. 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 := &quot;Customer context menu&quot; 
1026       soMenu:create() 
1027
1028       soMenu:addItem( { &quot;~New&quot;, ; 
1029                         {|mp1,mp2,obj| DbGoTo( LastRec()+1 ) } ; 
1030                     } ) 
1031
1032       soMenu:addItem( { &quot;~Seek&quot; ,  ; 
1033                         {|mp1,mp2,obj| SeekCustomer( obj:cargo ) } ; 
1034                     } ) 
1035
1036       soMenu:addItem( { &quot;~Delete&quot; , ; 
1037                         {|mp1,mp2,obj| DeleteCustomer( obj:cargo ) } ; 
1038                     } ) 
1039
1040       soMenu:addItem( { &quot;S~ave&quot; , ; 
1041                         {|mp1,mp2,obj| obj:cargo:writeData()  } ; 
1042                     } ) 
1043
1044       soMenu:addItem( MENUITEM_SEPARATOR ) 
1045
1046       soMenu:addItem( { &quot;~First&quot; , ; 
1047                         {|mp1,mp2,obj| DbGoTop() } ; 
1048                     } ) 
1049
1050       soMenu:addItem( { &quot;~Last&quot; , ; 
1051                         {|mp1,mp2,obj| DbGoBottom() } ; 
1052                     } ) 
1053
1054       soMenu:addItem( MENUITEM_SEPARATOR ) 
1055
1056       soMenu:addItem( { &quot;~Previous&quot; , ; 
1057                         {|mp1,mp2,obj| DbSkip(-1) } ; 
1058                     } ) 
1059
1060       soMenu:addItem( { &quot;~Next&quot; , ; 
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&amp;fid=F14BGD&amp;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