![]() |
AtOne Application Framework
"A Framework working with you, not against you..." |
|||||||||||||||||||||||||||||||||||||||||||||||
|
Features Programming License Downloads Directions Contact
|
Programming DialogsWhen 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 IdentificationTo 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),
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 ScriptTo 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 NavigationStandard 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 ClassTo 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
protected:
public:
virtual bool
open();
The construtor does all the not so hard work with, GuiDialog::GuiDialog(GuiWindow* pParent, const
GuiResId& rResId, bool bIsModal, bool bAddNavigation)
isModal(bIsModal);
if (bAddNavigation)
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 TabDialogClassTo simplify the process of creating tab dialogs a tab dialog class was added to AtOne. The class definition is,class GuiTabDialog : public GuiDialog
protected:
public:
virtual ~GuiTabDialog(); void
associateStorage(int& rStorage);
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:
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[]
=
Finding a ControlGiven 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()
GuiCheckBox* pAsMeanSquareCheckBox
= GuiCheckBoxWithId(&ChildWindowFinder, ID_GRAPH_ASMEANSQUARE);
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.
"We use Zeus for Windows and Watcom C/C++ 11.0 as our development environment of choice..." Paavo Jumppanen
|
|||||||||||||||||||||||||||||||||||||||||||||||