Taquis - PC Base FFT Spectrum Analyzers, Oscilloscopes, Data Analysis, Data Acquisition, and Application Frameworks   AtOne Application Framework
"A Framework working with you, not against you..."

 
Home
Features
Programming
License
Downloads
Directions

Contact
info@taquis.com


 
 

Programming Dialogs

When is a window a dialog and when is a dialog not a window? Since most frameworks rely on high level WIN32 API's such as ::CreateDialog() to create dialog boxes from resource scripts, it is true that dialog boxes are not fundamentally the same as windows. However, this distinction is largely artifical because internally ::CreateDialog() does no more than read and process the resource script using it to create a parent window and instancing the appropriate controls at the appropriate places within the client area of the parent. Essentially then, ::CreateDialog() is nothing more than a convenience function for WIN32 proprammers programming in C. Making use of this and other dialog based convenience API within a WIN32 application framework creates a number of difficulties that complicate the framework markedly. Most troublesome of all the problems created by this approach is the need to provide more than one way in which a window is created. This arises because in a normal situation a window class will create an instance of a window using ::CreateWindowEx() whereas, if we rely on ::CreateDialog(), all the windows will have been created for us by WIN32. Therefore for dialogs and controls in dialogs we can only hook pre-exisiting windows and not create them outright. Another difficulty that arises in this approach is the inability to support completely new custom controls within the framework. To do so transparently you would specify a custom WIN32 window class in the resource script and then ::CreateDialog() would attempt to create an instance of that class when the function is called. However, ::CreateDialog() does not know how to create a C++ class so any control that is to be recognised by WIN32 must have a C wrapper to be able to be created in this way, making custom control authoring messy and time consuming. Yet another problem that arises is in controlling the life cycle of the C++ window class. Using the traditional approach the C++ window class for dialogs will always be bound to the dialog life cycle because the ::CreateDialog() function created the window and its children in the first place. This results in unecessary complication of having to stream information to and from dialog boxes rather than being able to store it local to the dialog box class. 

But if we were to create the dialog box ourselves by reading and processing the dialog box script all these problems simply disappear. Windows need only be constructed by one means and one means only (ie. calling ::CreateWindowEx()), custom control classes written in C++ can automatically be supported in dialog boxes, and the dialog box class life cycle can be seperated from that of the dialog box window, making dialog box data management easier. This is the approach adopted in AtOne, making the authoring of complex dialog box code that much easier and more flexible than is available in other frameworks. 
 

Window Class Registration and Class Identification

To be able to create dialog box controls using nothing but a class name and some style information it is necessary to have some automatic means of creating the corresponding C++ window class. This is the role of the generic windows class registry in AtOne. For each dialog instantiable class in AtOne we must register the class with the registry using the registerWindowsClass() macro (defined in guiclass.hpp). This macro has 13 parameters (in order of declaration),
 
 
ClassType The name of the new C++ window class that you are registering. This is typically always a class derived from GuiWindow
ClassParentType The name of the C++ window class that is the parent class of this one. This is typically GuiWindow.
SubClassType The name (as a string) of the WIN32 class that the new WIN32 class will be based on. This can be 0 if the class is not based on any other WIN32 class. Use this field to sub-class a WIN32 class. For example, to sub-class a button control we would set this field to _T("BUTTON")
SubClassStyle The value of a default window style that should be applied to the creation of an instance of this WIN32 class. Typically this will always be set to 0. However, if sub classing certain controls that share the same WIN32 class this will be non-zero. The BUTTON class fits this catagory as it implements buttons, group boxes, radio buttons and check boxes.  For example, if we were sub-classing a radio button class this field would be set to BS_RADIOBUTTON
SubClassMask The value of the mask to apply to the specified styles in the resource script for a given WIN32 class. Typically this value will be 0 for all new WIN32 classes and sub-classes that only require a 0 value SubClassStyle. However, if sub classing certain controls that share the same WIN32 class this will be non-zero. The BUTTON class fits this catagory as it implements buttons, group boxes, radio buttons and check boxes.  For example, if we were sub-classing buttons, group boxes, radio buttons and check boxes, this value would be set to 0xF for all these controls.
ClassStyle The class style specified when registering this WIN32 window class.
WindowProc The window procedure that will process the messages of this WIN32 windows class. Typically you will always specify GuiWindowBase::WindowProc in this field. Should you require special message processing it is better to use the message hooking functionality available in GuiWindow rather than writing a custom window proc. 
ClassExtra Specifies the number of extra bytes to allocate following the window-class structure. The system initializes the bytes to zero. These bytes are accesible through the WIN32 API ::GetClassLong() and ::SetClassLong().
WindowExtra Specifies the number of extra bytes to allocate following the window instance. The system initializes the bytes to zero. These bytes are accessible through the WIN32 API ::GetWindowLong() and ::SetWindowLong()
HandleIcon Handle to the class icon. This member must be a WIN32 handle of an icon resource. If this member is 0 no icon is specified and WIN32 will use a default. This is over-ridable using the icon() method in GuiWindow. Typically you will set this field to 0.
HandleCursor Handle to the class cursor. This member must be a WIN32 handle of a cursor resource. If this member is 0 no cursor is specified and WIN32 will provide a default cursor. If you want to explicitely specify a cursor using the cursor() method in GuiWindow you must set this parameter to 0. Failure to do so will result in cursor flicker. Typically you will set this field to 0. 
HandleBrush Handle to the WIN32 class background brush. This member can be a handle to the physical brush to be used for painting the background, or it can be a colour value. A colour value must be one of the following standard system colors (the value 1 must be added to the chosen colour). If a colour value is given, you must convert it to one of the following HBRUSH types: 

COLOR_ACTIVEBORDER
COLOR_ACTIVECAPTION
COLOR_APPWORKSPACE
COLOR_BACKGROUND
COLOR_BTNFACE
COLOR_BTNSHADOW
COLOR_BTNTEXT
COLOR_CAPTIONTEXT
COLOR_GRAYTEXT
COLOR_HIGHLIGHT
COLOR_HIGHLIGHTTEXT
COLOR_INACTIVEBORDER
COLOR_INACTIVECAPTION
COLOR_MENU
COLOR_MENUTEXT
COLOR_SCROLLBAR
COLOR_WINDOW
COLOR_WINDOWFRAME
COLOR_WINDOWTEXT 

The system automatically deletes class background brushes when the class is freed. An application should not delete these brushes, because a class may be used by multiple instances of an application. 

When this member is 0, an application must paint its own background whenever it is requested to paint in its client area. This can be done by explicitely specifying a brush using the brush() method of GuiWindow.
 

MenuName Pointer to a null-terminated character string that specifies the resource name of the WIN32 class menu, as the name appears in the resource file. If you use an integer to identify the menu, use the MAKEINTRESOURCE macro. If this member is 0, windows belonging to this class have no default menu.

All window classes that are to be registered must have a constructor of the form : WindowClass(ParentClass* pParent, DWORD dwStyle, DWORD dwExStyle). This standard form constructor is the one that the registration system calls to create an instance of the required window. Note that ParentClass refers to the class name that this class is derived from and will typically be GuiWindow. If you don't define a standard form constructor and attempt to register you new window class your code will have compile errors. For examples demonstrating the use of class registration using this macro refer to guistart.cpp

The registerWindowClass() macro registers a given C++ window class as a WIN32 window class so that AtOne will know how to create the given GuiWindow class. When a dialog is created from a resource script specification the dialog will maintain pointers to all the child windows and will know them as GuiWindows. However, we would like to be able to use the child windows as the classes that they are which will require down-casting. Therefore to allow safe up-classing to the correct class the GuiWindowBase class defines two virtual methods that identify the window class : namely hasClassName() and className(). As an aid to coding the declaration of these methods have been encasulated in the macro declClassIdentity which appears in the public section of the class definition for the descendant GuiWindow class. A corresponding implementation macro, implClassIdentity(ClassType, ClassParentType), is defined to automate coding of identity methods. Note that ClassType and ClassParentType have the same meaning as in the registerWindowClass() macro. The implementation macro should typically only be use in the cpp file implementing the class. These macros are defined in guiwbase.hpp
 

Processing the Resource Script

To create a dialog box from a resource script requires the processing of the resource script into a dialog template and instancing the dialog and controls from that specification. This is the purpose of the GuiDialogTemplate defined in guidlg.hpp. To load and parse the resource script either use the GuiDialogTemplate(const GuiResId& rResId) constructor specifying the appropriate resource or alternatively use the load(const GuiResId& rResId) load method. Once loaded the dialog can be easily created using the create() method. Alternatively you may use a dialog template to create the controls on any GuiWindow using the createOn() methods. This process will create a dialog box that has all the controls but lacks dialog box navigation. In other words, you will not be able to navigate the dialog using the keyboard (tab key etc.). 
 

Keyboard Navigation

Standard dialog keyboard navigation (including tab dialogs) is implemented as an aggregate feature of GuiWindow class instances. It works through hooking appropriate messages sent to the dialog and all its parent controls. This is the function of the GuiNavigationFeature class defined in guinav.hpp. Adding dialog navigation to any window is as simple as constructing an instance of GuiNavigationFeature, specifying the pointer to the dialog (GuiWindow pointer) as the argument in the constructor. For example, 

OsiReleaseReference(new GuiNavigationFeature(pDialog));

Adds keyboard navigation to the dialog pDialog. Another feature that this class also automatically takes care of is dialog dismissal. A dialog can be dismissed in two possible ways : pressing the OK button or pressing the Cancel button. When pressing OK the data entered in a dialog should be accepted after being validated. In AtOne the process of data validation is integrated into the architecture. Whenever the OK or apply buttons are depressed in a dialog box with standard navigation, all child controls are tested by calling their canApply() methods. In the event of a failure (ie. returns false) the apply or OK operation is cancelled and focus is set to the control that failed. Only when canApply() returns true from all controls with the operation succeed. This provides a convenient means of data validation which is best handled at the control level. 
 

The Dialog Class

To simplify the process of creating a dialog box a dialog box class has been added to AtOne. The class definition is,

class GuiDialog : public GuiWindow
{
private:
  bool                                  Applied;
  bool                                  IsValid;

protected:
  GuiDialog(const GuiDialog& rCopy);

public:
  GuiDialog(GuiWindow* pParent, const GuiResId& rResId, bool bIsModal, bool bAddNavigation = true);
  virtual ~GuiDialog();

  virtual bool                          open();
  virtual bool                          apply();
  bool                                  applied() const;
};

The construtor does all the not so hard work with,

GuiDialog::GuiDialog(GuiWindow* pParent, const GuiResId& rResId, bool bIsModal, bool bAddNavigation)
 : GuiWindow(pParent, 0, WS_EX_DLGMODALFRAME)
{
  GuiDialogTemplate Template(rResId);

  isModal(bIsModal);
  discardOnClose(true);
  IsValid = Template.createOn(this);
  Applied = false;

  if (bAddNavigation)
  {
    OsiReleaseReference(new GuiNavigationFeature(this));
  }
}

This cleary demonstrates the usage of GuiDialogTemplate and GuiNavigationFeature. It should also be clear that a GuiDialog is still essentially just a GuiWindow. Tab dialogs are a little more complicated as we shall see.
 

The TabDialogClass

To simplify the process of creating tab dialogs a tab dialog class was added to AtOne. The class definition is,

class GuiTabDialog : public GuiDialog
{
private:
  GuiTabControl*  TabControl;

protected:
  GuiTabDialog(const GuiTabDialog& rCopy);

public:
  GuiTabDialog(GuiWindow* pParent, 
               const GuiResId& rResId, 
               int nTabId,
               bool bIsModal, 
               const GuiTabDialogTabs* pTabs, 
               int nTabs, 
               GuiImageList* pImageList = 0);

  virtual ~GuiTabDialog();

  void            associateStorage(int& rStorage);
  GuiTabControl*  tabControl() const;
};

As with the GuiDialog class, creating a tab dialog is simply a matter of creating an instance of the GuiTabDialog class. However, in this case we must also supply an array of GuiTabDialogTabs structures specifying the tabs to be added to the dialog, and optionally specify an image list for the image to display on the tabs. The structure GuiTabDialogTabs is defined as, 

struct GuiTabDialogTabs
{
public: 
  const string  TabName;
  const string  TabResName;
  int           TabResOffset;
  const string  TabPopupHelp;
  int           TabImageNum;

public: 
  GuiTabDialogTabs(const string pTabName,
                   const string pTabResName,
                   int TabResOffset,
                   const string pTabPopupHelp,
                   int nTabImageNum);
};

The TabName parameter specifies the text on the tab, TabResName specifies the name of the resource template defining the dialog tab, TabPopupHelp defines the tooltip to display for the tab and TabImageNum specifies the image list image to draw on the tab. The TebResOffset parameter is a number that is added to the resource identifiers of the tab dialog resource script to create the actual control identifiers. Quite often you will create tab dialogs in which the tabs contain an identical layout and collection of controls that only differ by Id. rather than having to create multiple dialog templates you can use the same dialog template for all the tabs and create unique Id's for each tab by specifying a different TabResOffset value for each tab. An example of this can be found in the AtSpec sample supplied with this application framework. The GuiAtSpecGlobalGraphOptionsDialog, for instance, defines,

const GuiTabDialogTabs GuiAtSpecGlobalGraphOptionsDialog::GlobalGraphOptionsTabs[] = 
{GuiTabDialogTabs(_T("Normal colours"),   _T("GLOBALPROPERTIES_PALETTE"), ID_COLOURS_NORMAL,  0, -1),
 GuiTabDialogTabs(_T("Printer colours"),  _T("GLOBALPROPERTIES_PALETTE"), ID_COLOURS_PRINTER, 0, -1),
 GuiTabDialogTabs(_T("Title font"),       _T("GLOBALPROPERTIES_FONT"),    ID_FONT_TITLE,      0, -1),
 GuiTabDialogTabs(_T("Unit font"),        _T("GLOBALPROPERTIES_FONT"),    ID_FONT_UNIT,       0, -1),
 GuiTabDialogTabs(_T("Marker font"),      _T("GLOBALPROPERTIES_FONT"),    ID_FONT_MARKER,     0, -1)};
 

Finding a Control

Given a dialog box (tabbed or otherwise) in which all the controls have been instanced for me, how do I obtain a pointer to a specific control as the appropriate class? If you only have one control that you want a pointer to you can use the childWithClassNameAndId() method to find that control and then down-cast to the control class. If, on the other hand, you have many controls to down-cast it is more efficient to use the GuiChildWindowFinder class. This class builds a hash table of child controls keyed on control id whereas the childWithClassNameAndId() method will do a linear search through all children each time the function is called. Therefore it is better to use the GuiChildWindowFinder class if finding more than one control. The following code fragment shows a typical usage.

void GuiAtSpecNewGraphDialog::initialiseControls()
{
  GuiChildWindowFinder  ChildWindowFinder(this, true);

  GuiCheckBox* pAsMeanSquareCheckBox     = GuiCheckBoxWithId(&ChildWindowFinder, ID_GRAPH_ASMEANSQUARE);
  GuiCheckBox* pFD_WithPeakPowerCheckBox = GuiCheckBoxWithId(&ChildWindowFinder, ID_GRAPH_FREQ_WITHPEAKPS);
  GuiCheckBox* pFD_WithReferenceCheckBox = GuiCheckBoxWithId(&ChildWindowFinder, ID_GRAPH_FREQ_WITHREF);
  GuiCheckBox* pTD_WithReferenceCheckBox = GuiCheckBoxWithId(&ChildWindowFinder, ID_GRAPH_TIME_WITHREF);
  .
  .
  .

The process is simplified by the addition of finder macros defined for each new window class. For every new window class that you design you should add a corresponding macro to help in these situations. GuiCheckBoxWinthId() is one such macro that is defined in guictrl.hpp.  The dialog class is defined in guidlg.hpp and the tab dialog class in guitbdlg.hpp

As can be seen the creation of dialogs and tab dialogs is a simple and flexible process that allows developers to use custom controls in dialogs with a minimum of fuss. 
 

Programming Graphics


"We use Zeus for Windows and Watcom C/C++ 11.0 as our development environment of choice..."

Paavo Jumppanen
Creator of AtOne Application Framework


This document was last modified on 1st September, 2001
Copyright (C) 2001, Paavo Jumppanen
All rights reserved.