Simple Dialog Box Management Detailed Instructions One- hour presentation first presented at Xojo Developer Conference 2014 Audience level: Beginning to Intermediate Demonstrated Xojo development: Xojo desktop 2013 R 4.1 (OS X and Windows) Contact information: Ken Whitaker Leading Software Maniacs, LLC Seattle, WA USA 1.206.497.9257 Email: kenw@leadingswmaniacs.com Website: http://www.leadingswmaniacs.com Download package consists of: File Simple Dialog Box Management.pdf Simple Dialog Box Management Handout.pdf SimpleDialog0.xojo_binary_project SimpleDialog1.xojo_binary_project SimpleDialog2.xojo_binary_project SimpleDialog3.xojo_binary_project SimpleDialog4.xojo_binary_project Description This document. Two- page handout summary of presentation. Simple MsgBox project. Information status dialog box project. Adding pizzazz to your dialog box project. Modal dialog box project. Modeless, splash dialog box project. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 11 1
Table of Contents STRUCTURE OF DOCUMENT INTRODUCING DIALOG BOXES APPLICATION AND DATA SCOPE SIMPLE MESSAGE WITH MSGBOX INFORMATION STATUS MESSAGE WITH USER ACTION INSTANTIATING THE DIALOG BOX, SETTERS, AND GETTERS DIALOG BOX DESIGN PATTERNS ADD PIZZAZZ TO YOUR DIALOG BOX USE CUSTOM BACKGROUND AND TEXT COLORS LANGUAGE SUPPORT CREATING PLATFORM INDEPENDENCE PROTECT AGAINST MULTIPLE DIALOG BOX INVOCATIONS INTERACTIVE MODAL DIALOG BOX DATA MODEL ELEMENTS KEY MEMBER FUNCTIONS WINDISPLAY() MEMBER FUNCTION INITUI() MEMBER FUNCTION LOADUI() MEMBER FUNCTION UNLOADUI() MEMBER FUNCTION EVENT HANDLERS Level Selection Popup Menu I Accept Check Box OK AND CANCEL BUTTON HANDLING RETRIEVING DATA FROM THE MODAL DIALOG BOX MODELESS WITH MODAL DIALOG BOX LOGIC USER INTERFACE INVOKING THE DIALOG BOX DIALOG BOX CONSTRUCTION AND DESTRUCTION CHANGES IN THE DIALOG BOX CODE BIO 3 4 5 6 8 10 11 15 15 18 20 23 25 32 32 34 35 37 38 39 40 41 44 47 48 49 50 51 56 57 Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 2
Structure of Document To plan how to simplify the complexities of the various characteristics of handling dialog boxes, I used a mind map: Instantiation and setters Model 1. Simple message Design patterns View 2. Informational status Controller 3. Modal dialog getters 4. Modeless/Splash dialog Types 0A. Introduction Types Goals App Simple Dialog Box mechanics 0B. App and Data Scope Caller Dialog box Custom background and text colors Translated language Customization Portable dismissal buttons Restrict dialog box invocations Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 3
Introducing Dialog Boxes You could associate the handling of a dialog box as a module of functions with a user interface window usually consisting of controls residing in a window. There are several types of dialog boxes to discuss: Simple message. Message requiring user action. Modal dialog box where the user is expected to dismiss dialog and retrieve data. Modeless splash dialog box where the user isn t expected to dismiss the dialog box. The goal is to construct a dialog box framework that can accommodate a wide variety of dialog box requirements while maintaining a flexible, consistent workflow. The complexity comes in when you have to consider the following nuances: Incorporating consistency with a dialog s look and feel Enabling operating system portability while ensuring platform- specific behavior. Passing and retrieving information with the dialog box. User input that dynamically changes other dialog box controls. Planning for local language. The building of a framework starts with a basic dialog box code foundation that is enhanced until all of the dialog box requirements above are covered. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 4
Application and Data Scope The figure below shows three levels of application scope associated with working with dialog box windows (starting from the outside to the inside and back to the app). 3 1 2 Dialog box Caller 4 5 App Dialog box three- level scope App (1, 5): The overall application control logic that provides support for app initialization and provides overall application data services for preference settings, language, and consistent UI look and feel. Caller (2, 4): Program logic that invokes the dialog box logic. The caller usually passes information to be used by the dialog box logic and optionally retrieves information after the dialog box is dismissed. Dialog box (3): The programming logic that is used to handle interactions and local data while the dialog box window is in use. Local data can be returned once the dialog box is dismissed (either by the user or programmatically). Three layers of scope with application and data control Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 5
Simple Message With MsgBox Example code: SimpleDialog0. xojo_binary_project The first example is the simplest message dialog using the MsgBox standard services which presents simple status to the user. Once the caller invokes the MsgBox call, a message is displayed. Control doesn t return to the caller until the user dismisses the message box. Caller (app) Dialog management suspended Simple message with MsgBox The benefit of using MsgBox is that this call offers flexible options including standard icons, dismiss buttons, and message and title text without the need for you to design dialog box or write sophisticated dialog box code. Dim intresult As Integer intresult = MsgBox ("This is a simple message with an OK button.", _ 0, "Simple message") Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 6
When the caller (the application) invokes a MsgBox function call, it immediately suspends and control passes to the MsgBox dialog box management code (hidden in the bowels of Xojo s runtime library). All of the dialog box samples start with a main winapp window that is used to display the appropriate dialog box: Main window (Windows) Main window (OS X) With the Dialog0 example, a message is displayed when you click the Go! button. Simple message with MsgBox (Windows) Simple message with MsgBox (OS X) The OS X version ignores the title bar but includes a default icon. This message box is similar to the operation of a modal dialog box that requires the user to act upon the window by pressing the OK button in order to return control back to the caller. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 7
Information Status Message With User Action Example code: SimpleDialog1. xojo_binary_project There are a few glaring limitations of using MsgBox even for relatively simple information status dialog box handling. If you want a dialog box to be consistent across operating systems, construct your own equivalent to MsgBox. The overall flow appears to be the same as MsgBox: resources Caller (app) Dialog management suspended Overall flow of the information message modal dialog box A walkabout through the dialog box workflow is as follows: 1. The app caller passes the message and title to the dialog box controlling code. 2. The dialog box code displays and initializes the window UI. 3. In response to the user clicking the OK button, any changed UI elements are retrieved. 4. The dialog box is dismissed and control returns to the caller. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 8
In this example, an information modal dialog box resulting in more uniform, platform- independent user interface: Information status dialog box (Windows) Information status dialog box (OS X) Set the dialog box Frame properties with the Inspector: Properties Type Close Button Resizeable Maximize Button Minimize Button Full- screen Button Setting Floating window On Off Off Off Off A dialog box is created with Xojo given the name dboxinfo: with a public member function named windisplay() for the caller to invoke in the Go! Button s pbtngo Action: Dim dialog As New dboxinfo dialog.windisplay( This is an informational message, _ Informational Status ) Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 9
Instantiating the Dialog Box, Setters, and Getters Dialog box programming logic has two basic external types of member functions: Member function Type Invoked by Description windisplay() Setter App caller Saves the passed- in data parameters for use in the dialog box and initializes the UI. Get () Getter User dismisses the dialog box Called only if there s data to be retrieved from the dialog box (not required for informational status dialog boxes). The caller instantiates the dboxinfo Window class and to display the dialog box window, it calls a public member function called windisplay which basically provides the mechanism to pass information to display (commonly known as a setter). windisplay doesn t return control back until the user dismisses the window by clicking the OK button. Note: In fact, if you comment out the dialog.windisplay line of code, Xojo will still display the dialog box, but the two string parameters won t be called and the title and message will be displayed as using its default text values (normally as Untitled). Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 10
Dialog Box Design Patterns Dialog boxes like many software components need structure, a framework, in order to coordinate the interactions and responses in an orderly fashion. The dialog box code logic is composed of three separate, but interconnected components: model, view, and controller (MVC): M updates manipulates V C sees uses dialog box Model, view, and controller design pattern Model: Local data that always reflects the current state of data required by the dialog box processing logic. View: Updating of UI elements that the user sees. Controller: When a user or event forces data change, controller logic accesses model data (reads and writes) and can update the view. The controller acts as the brains of the dialog box. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 11
What is really going on within the dialog box code is this sequence (based on the separate, but interconnected MVC components): Remote data dboxinfo Local data (M) Dialog functions (C) User Input and Output (V) Save passed and global data as local data Initialize user interface Label Field Event handling Dialog dismissed Information status dialog box actions Each of the following private member functions support the MVC model and represents the foundation of all dialog box processing: Member function Invoked by Description initui() loadui() unloadui() windisplay() (view) windisplay() (view) User action (controller) Update the UI with information that doesn t change. Update the UI from the data model. Triggered by an event handler, retrieve information from the UI to the data model. helper () Controller Shared logic used by multiple dialog box functions. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 12
The dialog box s public windisplay() member function is passed the message the window title and saved as private data model for later display: ' Setters to the model mstrmessage= pstrmessage mstrtitle = pstrtitle ' initialize the look of the UI once and then load the UI controls from the model initui() loadui() ' Start up controller logic and interact with the user Self.ShowModal This data (the message and title text string) is then loaded to the UI controls with initui() private function: ' Load common look and feel ' Now load UI controls (static) self.lblmessage.text = mstrmessage self.title = mstrtitle ' Load UI controls (dynamic) Since in an information status dialog box, there s no need to use loadui() to load dynamic data, display the dialog box in the final statement in the windisplay() function. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 13
The OK button s control, pbtndone, uses the Action event to call a private function named helperdone(): helperdone() HelperDone does basic unloading of any changed data (nothing s changed since it is nothing more than a message display, of course!) and dismisses the dialog box: unloadui() Self.Close In practice, the helperdone() private member function can be called from multiple event handler actions for both the Close button or the OK button: Close button (OS X) dialog box Close button (Windows) OK helperdone Unload UI data and properly dismiss the dialog box Helper functions provided shared services to other dialog box event handlers Although there appears to be a little much dialog box infrastructure for displaying a simple message, this foundation is used for even the most complex enhancements coming next. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 14
Add Pizzazz to Your Dialog Box Example code: Dialog2. xojo_binary_project There are three additional things we d like to add to our dialog box code: 1. Use custom background and text colors. 2. Support translated language for certain UI controls. 3. Properly place the dismiss buttons for the target operation system. 4. Ensure that the dialog box can t be called multiple times at the same time. Supporting these enhancements in the dialog box requires consideration for both scope (where the information is kept and accessed) and app simplicity at the expense of a little bit more dialog box code. Use Custom Background and Text Colors There s nothing better than ensuring that every dialog box window has a similar look and feel. To apply a consistent background color and text label color you ll probably want to save these color settings saved in a preferences file. Changing the coloring can give your app a more distinguishable look. Customizable look For simplicity sake, these values will be saved in the app s global space with public member functions available for the dialog box logic to retrieve these settings. Note: Other dialog box user interface services can also be centralized to enable customization the use of a different font family, text size, bitmap image backgrounds, and so on. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 15
Assuming that every dialog wants to use the same color scheme, one app public getter function is made available: clrgetcolor(). This App function is called from the dialog box initui() private member function with a text- based identifier specifying what color value is being requested: ' Load common look and feel Self.HasBackColor = True Self.BackColor = App.getColor("BackgroundColor") Dim clrtextcolor As Color = App.getColor("LabelColor") Self.lblMessage.TextColor = clrtextcolor ' Now load UI controls (static) self.lblmessage.text = mstrmessage self.title = mstrtitle ' Load UI controls (dynamic) The background color needs to be enabled (window s Self.HasBackColor property set to True) and Self.BackColor is set by calling clrgetcolor() once for the dialog s background window. Since most dialog boxes usually have more than one text label, it is a good idea to get used to retrieving the label color once and saving it locally to be applied to other labels or text elements. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 16
The app public getter functions usually use a parameters file to retrieve language and color values, but for this example, getcolor() uses hard- coded values in the code. Function getcolor(pstrcolorname As String) As Color Dim clrvalue As Color = &c0 ' Assume color name (ID) isn't found Select Case (pstrcolorname) Case "BackgroundColor" clrvalue = &c8c8c8c ' Dark Gray Case "LabelColor" clrvalue = &cffffff ' White color ' Add other case functions here... End Select Return clrvalue Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 17
Language Support Assuming that you ll want your app to be easily support just about any translated language, there are all sorts of techniques, including Xojo s very own Lingua feature. However, it is just as easy to use a special type of preferences language file that can be translated by a non- computer whiz a text or XML file. Using an external file can make actual translation easier by emailing a simple text file to a translator! Any hardcoded English strings are replaced by calls to getlanguagetext. There are two places where the text needs to be translated. When the Go button is clicked and the dboxinfo class is invoked by Action, the message text and title are translated: Dim dialog As New dboxinfo dialog.windisplay(app.getlanguagetext("dboxinfomessage"), _ App.getLanguageText("dboxInfoTitle")) In fact, English should be treated as any language. To make sure every text string is actually translated, the default text strings defined in the original Xojo window is preceded with a z character to. (You were probably wondering what those z characters all over the place meant, right?) Forcing translation with the dialog box default text values (Windows) This will essentially force the proper loading of even English text resources. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 18
We ll use a global app function getlanguagetext(), that can be called by the dialog box code to ask for the translated text string by passing a string code: Function getlanguagetext(pstrtextname As String) As String Dim strvalue As String = "" ' Assume string isn't found Select Case (pstrtextname) Case "dboxinfotitle" strvalue = "Informational Status" Case "dboxinfomessage" End Select Return strvalue Of course, special casing of each resource string within a member function isn t the best solution. A better choice would be to use either a dictionary function or a resource file. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 19
Creating Platform Independence Although most UI control placement in a dialog box should be platform independent, there is one fundamental set of buttons that are not: OK and Cancel. On Windows, the Cancel button is located on the bottom right and on OS X, the OK button is on the right. Placement of OK and Cancel buttons (Windows) Placement of OK and Cancel buttons (OS X) For that reason, when the dialog box UI is initialized with initui(), a shared app public member function is added to determine which operating system (Windows or OS X) the app is running on. Dim strlabelright As String ' Load common look and feel Self.HasBackColor = True Self.BackColor = App.getColor("BackgroundColor") Dim clrtextcolor As Color = App.getColor("LabelColor") Self.lblMessage.TextColor = clrtextcolor ' Now load UI controls (static) self.lblmessage.text = mstrmessage self.title = mstrtitle ' Load UI controls (dynamic) mfrightbuttonisok = App.ifMacOSX() strlabelright = App.getLanguageText("ButtonOk") Self.pbtnRight.Caption = strlabelright Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 20
Even though in this example there is only a single OK dialog box dismissal button, additional code can be inserted to ensure that the correct buttons are labeled depending on the OS. The App s ifmacosx() public method function is quite simple (and could be expanded to incorporate Linux, but the result would have to be an enumeration or integer). Dim fplatformmacosx As Boolean = False ' Assume Windows #If (TargetMacOS = True) Then fplatformmacosx = True #Endif Return fplatformmacosx You could avoid using the local variable strlabelright altogether, but I wanted to show that if any retrieved global data may be used more than one time, to treat the service, getlanguagetext(), as a privileged function that may be expensive to use repeatedly. This logic will be further enhanced in the next example to cover two dismissal buttons for OK and Cancel (with left and right buttons). And since we already have an initui() method function defined in winapp, let s go right ahead and make it s window take on the new, customizable look, too. The main app window has the same customizable look as the dialog boxes (OS X) The code added to initui() is the following: ' Load common look and feel Self.HasBackColor = True Self.BackColor = App.getColor("BackgroundColor") Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 21
Note: The good thing about saving the operating system status App.ifMacOSX(), is that you can provide other UI changes in addition to the OK and Cancel buttons. For that reason, private property mfrightbuttonisok is added to the dialog box code. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 22
Protect Against Multiple Dialog Box Invocations Finally the last enhancement is one of the most important ones. Each time the Go button is clicked in the main app, yet another dialog box instance is displayed. Clicking Go button brings up a new dialog box instance (OS X) There s a simple, yet inelegant method of preventing that from occurring. When the dialog box is to be invoked, add the following three lines of code before and after the dboxinfo is instantiated and windisplay() called by the Go! button s pbtngo Action: If (App.fwinInvoke("dboxInfo") = True) Then Dim dialog As New dboxinfo dialog.windisplay(app.getlanguagetext("dboxinfomessage"), _ App.getLanguageText("dboxInfoTitle")) App.winDismiss("dboxInfo") End if The general idea is to store a flag for each dialog box, referenced by its function name in the app layer so that it can be maintained throughout the duration of the app s lifetime. fwininvoke() should return a True if it isn t currently displayed or a False if it is displayed. Conversely, the fwindismiss() function is called to reset the global flag. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 23
The public app member function fwininvoke() sets a global Boolean flag (gfwindlginfo, for example) specifically for each function. Dim fwindlgstatus As Boolean = False Select Case (pstrwindowname) Case "dboxinfo" If (gfwindlginfo = False) Then gfwindlginfo = True fwindlgstatus = gfwindlginfo End If ' Add other case values here... End Select Return fwindlgstatus Conversely, windismiss() resets the global Boolean flag: Select Case (pstrwindowname) Case "dboxinfo" gfwindlginfo = False ' Add other case values here... End Select Best of all, there s absolutely no change to any of the dialog box code at all! Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 24
Interactive Modal Dialog Box Example code: Dialog3. xojo_binary_project Let s start with the dialog box enhancements presented in the last section: 1. Use custom background and text colors. 2. Support translated language for certain UI controls. 3. Properly place the dismiss buttons for the target operation system. 4. Protect the dialog box from being called multiple times at a time. Let s extend the modal dialog example to take into account the following behavior: 1. Add a popup menu with a corresponding text description label every time a different menu item is selected. There can be any number of menu items provided, but only one can be selected. For this example, we ll use beginner, experience, and expert. 2. Add an I acknowledge check box. You can t dismiss the dialog box successfully until this check box is checked. 3. Add a text field that needs to include a valid email address. You can t dismiss the dialog box successfully until a rudimentary validation of the email address is verified. 4. Include a Cancel as well as an OK button to dismiss the dialog box. 5. Rather than display a message box with error text, we ve simplified the two warning conditions with two error messages that use red text (much like what you see with online forms processing). The dialog box looks like this: Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 25
If the email address and the checkbox aren t completed, error messages are displayed in the dialog box: Although this example may appear to be a very simplistic dialog box, there s plenty of activity going on behind the scenes: The caller passes all of the current values to the dialog box as setters. Whenever a popup menu item is selected, the corresponding description label is automatically changed. The OK and Cancel buttons are appropriately placed according to the target platform. When the user clicks OK, the email address and the I Acknowledge check box are verified. If not valid, an error message With every user interaction, the dialog box local data (the model) is always updated with the latest selected or inputted information: 1. Popup menu item number. 2. Checkbox status. 3. Email text field. Finally, upon dialog box dismissal, the proper button return (OK or Cancel) indicates to the caller if it wants to get the last entered dialog box data for saving until the next time. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 26
Note: Just a note about conventions and rules used. Put the work at the location where it makes sense and try never to duplicate the same code. Create a helper member function instead. Don t complicate dialog box interactions by accidentally handling events more than once or cause events to fire when not needed. Use Hungarian notation coupled with scope predecessors with all data. For example, a local data Boolean are assumed to be a member m in the dialog box container code, mfstatus, and a global data item available for any code to use, regardless of level, should be preceded by a g as in gfappstatus. Minimize accessing those elements that can be expensive, like UI controls. That s why the dialog box data model elements always reflect the latest information kept in the UI controls. Also, any function calls to the shared app functions should be minimized with the assumption that expensive disk I/O takes place as a result of every call. Overall logic is as follows: resources Caller (app) Dialog management suspended OK or Cancel getters Interactive modal dialog box workflow Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 27
But what goes on within the dialog box programming logic is where things can get a little complicated without sticking to a design pattern structure. Compared to the logic that was used in Dialog Box Design Patterns, an interactive modal dialog box s logic is more elaborate: Remote data dboxinfo Local data (M) Dialog functions (C) User Input and Output (V) Save passed and global data as local data Initialize user interface Label Field Event handling Act upon user input, update local data, update UI A Field Dialog dismissed Retrieve local data Interactive modal dialog box flow Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 28
Examining the caller s code, a couple more data elements are allocated: mintlevel Level from 1 (beginning) to 3 (advanced) with the default set to 1. mstremail The email address (default is blank). The Go! button s pbtngo Action needs to be modified slightly. A red text color item is added to the getcolor() member and several more text definitions are added in getlanguagetext(). If (App.fwinInvoke("dboxModal") = True) Then Self.lblStatus.Text = "" Dim strstatus As String Dim dialog As New dboxmodal If (dialog.windisplay(mintlevel, mstremail, _ App.getLanguageText("dboxModalTitle")) = True) Then ' Save results mintlevel = dialog.getlevel() mstremail = dialog.getemail() strstatus = NthField(App.getLanguageText("Dbox Status"), ":", 2) + _ NthField(App.getLanguageText("Level Options"), ":", mintlevel) + _ Else App.getLanguageText("Dbox Level Selected") + mstremail strstatus = NthField(App.getLanguageText("Dbox Status"), ":", 1) End If Self.lblStatus.Text = strstatus App.winDismiss("dboxModal") End if Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 29
The bolded code represents the most important logic to invoke the dialog box. The current settings level and email address is passed to the dialog box s windisplay() member function as setters. A Boolean True is returned if the user clicked OK (False, otherwise). Note: What is the difference between a True and a False dialog box return? With a modal dialog box where control returns a False if the user clicks either the close icon or the Cancel button. Control returns a True only if the user clicks the OK button. The getlevel() and getemail() public functions are called to remember the last values to be used the next time the modal dialog box is called. As the user selects the level and enters the email and selects the check box: Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 30
shows up on the main window as: Alternatively, if the Cancel button is clicked in the modal dialog box, a different status is displayed on the main window: Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 31
Data Model Elements The private data model used by the dialog box member functions. The data model elements are summarized below: Each data elements has a purpose during dialog box processing. Once the dialog is dismissed and its object destructed, these local variables vanish. Key Member Functions The easiest way to show what takes place within the dialog box logic is to show the code as the following components you became acquainted with in the last section: windisplay() Overall dialog box controller logic and setters that save passed values in the dialog box data model. initui() loadui() unloadui() Initialization of static components used by the dialog box. This is invoked only once. Update the latest data model to the dialog box UI elements. This can be invoked many times as dialog box UI elements become outdated. Save current data from the dialog box UI elements to the data model. For each event handler, either a helper () function will be Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 32
called or UnloadUI(). This can be invoked many times during a dialog box lifetime as dialog box UI elements are changed (typically by user interaction). getter () There s a getter function for each key data model item that can be queried by the caller after the user clicks the OK button. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 33
windisplay() Member Function The windisplay() member function expects the setter values that can be unique to the caller (including the title of the dialog box): Function windisplay(pintlevel As Integer, pstremail As String, pstrtitle As String) As Boolean ' Setters to the model mfdialoginitialized = False mintlevel = pintlevel mstremail = pstremail mstrtitle = pstrtitle mstrleveloptions = App.getLanguageText("Level Options") mstrleveldescriptions = App.getLanguageText("Level Descriptions") mfreturnok = False ' Assume the user cancels mfiaccept = False ' initialize the look of the UI once and then load the UI controls from the model initui() loadui() ' Start up controller logic and interact with the user mfdialoginitialized = True Self.ShowModal ' Return status Return mfreturnok Initially, passed setter values are saved in the data model, including those language- dependent character strings that will be used by the dialog box. Since the check box is always reset when the dialog box starts, the corresponding mfiaccept data model Boolean will be set to false. You ll notice that no user interface elements are Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 34
touched directly by windisplay(), even though you re probably aching to program the dialog box UI controls just about now! Of important note, is the use of a private flag mfdialoginitialized (bolded). Although always necessary, the first time you invoke initui() and loadui() member functions specific UI events may be triggered just by touching UI elements. This can create an unwanted endless loop of event handling especially if the UI elements are not completely setup. For this reason, UI event handlers can ignore an unexpected trigger simply by ensuring that mfdialoginitialized is set to False. There does appear to be some missing logic after ShowModal. Who sets the mfreturnok status and how does windisplay() know whether the user dismissed the dialog box with an OK or Cancel? Well, it doesn t. Control drops out of Self.ShowModal after the user clicks the OK or Cancel button whose function is to indicate dialog box success and dialog box dismissal. We ll cover this in a moment. Note: You re already seeing the benefit of the loosely structure model-view-controller components with specific dialog box member functions perform very specific tasks. We re going to avoid performing redundant or confusing dialog box logic by strictly keeping to the rules associated with our model. initui() Member Function initui() is invoked once: Dim strlabelleft, strlabelright As String Dim intlevelcount As Integer ' Load common look and feel Self.HasBackColor = True Self.BackColor = App.getColor("BackgroundColor") Dim clrerrortextcolor As Color = App.getColor("ErrorColor") Dim clrtextcolor As Color = App.getColor("LabelColor") Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 35
Self.lblErrorMessage1.TextColor = clrerrortextcolor Self.lblErrorMessage2.TextColor = clrerrortextcolor Self.lblEmail.TextColor = clrtextcolor Self.lblIaccept.TextColor = clrtextcolor Self.lblLevel.TextColor = clrtextcolor Self.lblLevelDescription.TextColor = clrtextcolor ' Now load UI controls (static) intlevelcount = CountFields(mstrLevelOptions,":") - 1 For i As Integer = 1 to intlevelcount Self.popLevel.AddRow NthField(mstrLevelOptions, ":", i) Next i Self.lblLevel.Text = App.getLanguageText("Dbox Level Label") Self.lblIaccept.Text = App.getLanguageText("Dbox I accept Label") Self.lblEmail.Text = App.getLanguageText("Dbox Email Label") Self.lblErrorMessage1.Text = "" Self.lblErrorMessage2.Text = "" Self.Title = mstrtitle ' Load UI controls (dynamic) mfrightbuttonisok = App.ifMacOSX() If (mfrightbuttonisok = True) Then strlabelleft = App.getLanguageText("ButtonCancel") strlabelright = App.getLanguageText("ButtonOk") Self.pbtnRight.Default = True Self.pbtnLeft.Cancel = True Else strlabelright = App.getLanguageText("ButtonCancel") strlabelleft = App.getLanguageText("ButtonOk") Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 36
Self.pbtnLeft.Default = True Self.pbtnRight.Cancel = True End If Self.pbtnLeft.Caption = strlabelleft Self.pbtnRight.Caption = strlabelright The initui() code basically does the following: 1. Set common look and feel. 2. Initialize the UI controls. 3. Set platform- specific OK and Cancel buttons. loadui() Member Function Surprisingly, but in this example most of the initial preparation work on the dialog box UI controls is where most of the work is performed. The actual act of updating all of the UI controls that might have changed is very minor: ' Load UI controls (refresh) helpersetiapprovecheckbox(mfiaccept) Self.txtEmail.Text = mstremail Self.popLevel.ListIndex = mintlevel - 1 Self.lblLevelDescription.Text = NthField(mstrLevelDescriptions, _ ":", mintlevel) In this case, the only three data elements that need to be updated are: 1. I accept check box setting through a helper function (we ll explain soon). 2. Email address. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 37
3. The level of play (starting with beginner) along with its description. unloadui() Member Function Since loadui() member function is top of mind, the contrary member function, unloadui(), does basically the opposite: ' Unload UI controls to local data mfiaccept = helpergetiapprovecheckbox() mstremail = Self.txtEmail.Text mintlevel = Self.popLevel.ListIndex + 1 In this case, the only three data elements that need to be updated are: 1. I accept check box setting through a helper function (we ll explain real soon). 2. Email address. 3. The level of play (starting with beginner). The associated description isn t unloaded, because it is always updated with a subsequent loadui() call. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 38
Event Handlers But, unload() doesn t operate alone. In fact, unloadui() is usually called as a result of a user- initiated event handler being called. The dialog box event handlers are shown below: UI control Event handler Cause poplevel Change A level (one of three) is selected. chkboxiaccept Action Clicks check box directly. lblaccept MouseUp Clicks label text which toggles the check box. pbtnleft Action OK button (Windows) or Cancel (OS X) is clicked. pbtnright Action OK button (OS X) or Cancel (Windows) is clicked. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 39
Level Selection Popup Menu Probably the easiest way to handle a change of selection of the popup menu is to read the current selection with a call to unloadui() and then update the level description by invoking loadui(). poplevel Change UnloadUI() LoadUI() As a precaution, a special test is performed to make sure that the selection isn t handled until all of the items are loaded in initui() with the flag: Sub Change() If (mfdialoginitialized = True) Then unloadui() loadui() End If End Sub Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 40
I Accept Check Box chkboxiaccept Action Unload() When the I accept check box is clicked, the current changed status is saved in the data model as a Boolean True or False in mfiaccept. However, there s more to this Action event than you think: If (mfdialoginitialized = True) Then UnloadUI() End If The reason why this UnloadUI function call is protected (by not executing until the dialog is initialized) is because this event will likely be fired when another control (like initializing the menu items in the popup menu control) is accessed. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 41
In addition, many users like to click the adjacent label that describes the check box in order to toggle the check box value. This won t happen automatically. You need to enable the MouseUp handling with returning True from MouseDown and handle the MouseUp event: lbliaccept MouseDown MouseUp Return True The MouseUp() event handler code retrieves the current value of the check box and complements it before writing it back: Dim fstatus As Boolean = True If (helpergetiapprovecheckbox() = True) Then fstatus = False End If helpersetiapprovecheckbox(fstatus) All of the UI control values could have been loaded and unloaded, but updating this single UI control is very efficient. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 42
The helpergetcheckbox() is what s known as a helper function that retrieves the current check box setting. This is invoked by more than one dialog box function (thus, a helper ): Private Function helpergetiapprovecheckbox() As Boolean Dim fstatus As Boolean If (Self.chkboxIaccept.State = CheckBox.CheckedStates.Checked) Then Else fstatus = True fstatus = False End If Return fstatus End Function The helpersetcheckbox() is what s known as a helper function is used more than once: Private Sub helpersetiapprovecheckbox(pfcheckboxstate As Boolean) If (pfcheckboxstate = True) Then Self.chkboxIaccept.State = Checkbox.CheckedStates.Checked Else Self.chkboxIaccept.State = CheckBox.CheckedStates.Unchecked End If mfiaccept = pfcheckboxstate End Sub Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 43
OK and Cancel Button Handling Finally, the left and right buttons are routed to the appropriate helper functions: helpercancel() helperok() Invoked whenever the appropriate Cancel button (left button under OS X or right button under Windows): pbtnleft (OS X) chkboxiaccept pbtnright (Windows) Action helpercancel() Unload() The helper code for dialog box cancellation is: Private Sub helpercancel() mfreturnok = False Self.Close End Sub Alternatively if the user clicks the dialog box close icon, normal window close implied Xojo logic will properly close the dialog box. The caller will receive the proper OK/Cancel status because the default mfreturnok private property value was already set to False (meaning that the dialog box was cancelled). Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 44
When the user clicks the OK button, the entire logic is passed from the Action event handler which calls the helperok() private helper function: pbtnleft pbtnright (OS X) X) chkboxiaccept pbtnright pbtnleft (Windows) Action helperok() unloadui() verify fields All fields are valid Update invalid field errors Self.Close The helperok() function should assume that the data model has current level and I agree check box selection. However to be safe, it invokes an unloadui() since the Email text field may have been changed, but not saved recently. At this point, the Email and I agree values are verified and if any field isn t valid, the appropriate error message is displayed directly on the dialog box. Otherwise, the dialog box is dismissed back to the caller. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 45
Private Sub helperok() mfreturnok = True ' Assume everything is OK unloadui() _ ' Now validate the check box and the email text box Self.lblErrorMessage1.Text = "" If (Len(mstrEmail) = 0) Then Self.lblErrorMessage1.Text = NthField(App.getLanguageText _ ("DBox Errors"), ":", 1) mfreturnok = False Else If (isvalidemail() = False) Then Self.lblErrorMessage1.Text = NthField(App.getLanguageText ("DBox Errors"), ":", 2) mfreturnok = False End If End If Self.lblErrorMessage2.Text = "" If (mfiaccept = False) Then Self.lblErrorMessage2.Text = NthField(App.getLanguageText _ ("Dbox Errors"), ":", 3) mfreturnok = False End If If (mfreturnok = True) Then Self.Close End If End Sub Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 46
There are three possible error messages that can be displayed and if any of the errors occurs, the user cannot dismiss the dialog box with a successful result: No email address. Email address not correctly formed. I agree check box not selected. Retrieving Data From the Modal Dialog Box Finally there are three getter () public member functions: getemail() getlevel() getreturnstatus() The logic with each of these functions simply returns the last saved value unloaded from the control. Here s one example: Function getemail() As String ' A "getter" Return mstremail End Function Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 47
Modeless with Modal Dialog Box Logic Example code: Dialog4. xojo_binary_project Modeless dialog box functionality can be extended to the modal dialog box so that one dialog box code can actually perform either modal or modeless processing. A modeless dialog that acts as a splash window is a special case dialog box that doesn t force the user to have to wait to click an OK button to dismiss the dialog box. In our example, a modeless dialog box that is configured as a splash window has a built- in timer that will automatically dismiss the dialog box. A modal dialog box tends to have both an OK and Cancel button whereas a modeless dialog box will have either a single Done button or no closure button at all if the window is displayed for a limited amount of time. To give the user some control, you ll want to allow the user to click anywhere on the window to dismiss it before waiting for the timed delay. resources Caller (app) Dialog management Continue Timer or click (on window) Immediate return There s a further complication in that modeless dialog box windows can be viewed as a lazy closure object where the caller doesn t really know when the modeless dialog box has been closed. As opposed to an orderly, synchronized dismissal procedure with modal dialog boxes, modeless dialog boxes are largely asynchronous. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 48
User Interface Highlights of changes are: The main window lets the user select between modal and modeless operation. The modal dialog box allows the user to select and enter information: The modeless dialog box acts as a splash screen that has less information and isn t, in our sample, interactive (like an about box). Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 49
Dismissal of the dialog box can be performed by two methods: Letting the dialog box timeout (notice the highlighted dots decrease from 5 to none) or Clicking the mouse anywhere on the dialog box. Invoking the Dialog Box Modifying the app to handle both types of dialog boxes from a single code base requires the following modifications to our previous modal code example: Updating dialog box instance construction and destruction. Augmenting the dialog box interface to include modeless parameters. Modifying the dialog box processing to separate modal from modeless user interface differences. Adding timer and mouse click support for closing the modeless window to behave like a splash window. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 50
Dialog Box Construction and Destruction In the previous modal dialog box code sample, the dialog object was created inline before the dialog box was invoked by the Go button, pbtngo, Action code: If (App.fwinInvoke("dboxModal") = True) Then Self.lblStatus.Text = "" Dim strstatus As String Dim dialog As New dboxmodal In fact, the dialog object is automatically destructed when Action returns. Not so with modeless dialog processing since there is a high probability that the modeless dialog box window is still displayed after Action completes. To handle this dynamically, the dialog instance is defined as a private property to the main winapp module: Dim dialoginstance As dboxmodalmodeless Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 51
And the Go button Action code changed to construct the dialog instance which then required the instance to be explicitly destructed. dialoginstance = New dboxmodalmodeless If (getmodalityselection() = 1) Then App.winDismiss("dboxModalModeless") dialoginstance = Nil End if End if In fact with modeless dialog box processing, the dialog box is actually closed by the modeless dialog box logic and not by the caller (more on this later ). Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 52
To ensure that there s not the possibility of an endless number of leftover dialoginstance objects, we ll dynamically destruct the last dialoginstance if it remains before constructing another. A leftover dialoginstance will only exist following the modeless dialog. ' Just in case, clean up prior modeless instance If (dialoginstance <> Nil) Then If (dialoginstance.getreturnstatus() = True) Then dialoginstance = Nil End If End If dialoginstance = New dboxmodalmodeless If (getmodalityselection() = 1) Then App.winDismiss("dboxModalModeless") dialoginstance = Nil End if End if The dialog box code s entry method windisplay() will return True if the user has clicked OK or otherwise False will be returned. Because a modeless dialog box status would change to True only if the timer times out or the user mouse clicks anywhere in the dialog box window. For that reason, the caller will need to periodically call getreturnstatus(). Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 53
The modified windisplay() code is: Function windisplay(pintlevel As Integer, pstremail As String, pstrtitle As String, pintmodality As Integer = 1) As Boolean ' Setters to the model mfdialoginitialized = False mintmodality = pintmodality mintlevel = pintlevel mstremail = pstremail dialoginstance = New dboxmodalmodeless if (mintmodality = 1) Then Self.ShowModal Else Self.Show ' Return immediately to the caller End If ' Return status Return mfreturnok The pintmodality is set to 1 for modal and 2 for modeless/splash and is saved to a new private data named mintmodality. This could have been a Boolean True or False, but an integer data variable allows more flexibility in case you want more than two values in the future. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 54
In contrast to just modal dialog box logic, the modeless dialog box code doesn t use Self.ShowModal, but instead uses Self.Show. A modeless dialog box will be properly dismissed later. Note: In our examples, the modal dialog box control returns a False if the user clicks either the close icon or the Cancel button. Control returns a True only if the user clicks the OK button. The modeless dialog box is different. When the dialog box control returns, the dialog box is probably still active and displayed so a False is returned. Once the user dismisses the dialog box (ex: Done button, close icon, timeout, or clicking on the mouse anywhere in the dialog box window), a query to the getreturnstatus() function by the caller returns a True. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 55
Changes in the Dialog Box Code Rather than explain every coding detail, you should be able to review the code changes to see what s involved with including both modal and modeless dialog box processing with a single dialog box set of member functions. Here s a quick summary of those changes in dialog box code: 1. windisplay() has new interfaces (see the previous section). 2. getmodality(), a new public member function, is added so that the internal dialog box code can perform a check as to how the dialog box was initially set up (see #1). It is public so that is can be invoked by the caller (although the caller should remember which mode was used). 3. helperclosemodeless() is a new private member function that is shared by both the timer service and mouse click actions to dismiss the dialog box and to set the dialog box status to True (meaning the dialog box is no longer actively visible). 4. helperreloadsplashcounter() is a new private member function that is called each time the timer is decremented. 5. initui() has massive changes since it needs to initialize the user interface controls differently for modal and modeless use. 6. loadui() also has changed in order to start the timer for modeless operation. 7. Mouse event handling is added for both MouseDown and MouseUp. MouseDown enables mouse up events if returned to Xojo as True and this is only performed for modeless operation. When the user wants to dismiss the modeless dialog box before the timer runs out, the MouseUp event is called. 8. A timer control is added to the dialog box and with that is handling for a single Action. The timer control is initialized to trigger every second and after five seconds has elapsed, Action will dismiss the modeless dialog box. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 56
Bio Ken Whitaker of Leading Software Maniacs (LSM) has more than twenty- five years of software development executive leadership and training experience in a variety of technology roles and industries. He has led commercial software teams at Software Publishing (remember Harvard Graphics?), Data General, embedded systems software companies, and enterprise software suppliers. Ken is an active PMI member, Project Management Professional (PMP) certified, and a Certified ScrumMaster (CSM). Sources for LSM s material come from case studies, personal leadership experience, the PMI Project Management Book of Knowledge (PMBOK Guide), and Ken s project leadership books: Managing Software Maniacs, Principles of Software Development Leadership, and I m Not God, I m Just a Project Manager. Ken is an innovator in instructional design and putting on workshops (Deliver Projects On Time With extreme Project Management, Project Managers Agile Boot Camp, ) and presentations including the popular 7 Deadly Habits of Ineffective Software Managers (comix handout included). PM University, http://www.pmuniversity.com, is a new addition to Leading Software Maniacs online, elearning curriculum focused on pragmatic project management and software leadership courses. Ken is the creator of PM Chalkboard, http://www.pmchalkboard.com the fastest way to learn basic project management principles with entertaining, no- fee tutorial videos. Ken also is the editor for Better Software magazine and a frequent guest writer for http://www.projectmanagement.com (formerly, http://www.gantthead.com). To help project managers and anyone working with digital projects and software development, Ken has developed the Spresso project versioning software, http://www.versionitwithspresso.com, (available for Windows or OS X). Spresso is built using Xojo. Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 57
Dim gstrdialogmessage As String = This is an informational message Dim gstrdialogtitle As String = Informational Status Dim dialog As New dboxinfo dialog.windisplay(gstrdialogmessage, gstrdialogtitle) Copyright 2013-2014 Leading Software Maniacs, LLC. All Rights Reserved. 58