![]() |
AtOne Application Framework
"A Framework working with you, not against you..." |
|||||||||||||||||||||
|
Features Programming License Downloads Directions Contact
|
Programming WindowsUnlike most other application frameworks AtOne uses an generalised aggregation approach to encapsulating a WIN32 window in a C++ class. How does this differ from conventional application frameworks?Most frameworks use the approach of inheritance (exclusively) to add functionality to a type of window leading to the creation of many different window classes that are a pure artifact of the framework structure and nothing to do with the capabilities or limitations of the underlying WIN32 API. This can be particularly annoying for those familiar with WIN32 when they discover that what they could do in WIN32 and C, they cannot do a particular framework. A concrete example of this is drawing to a window. Some frameworks introduce the concept of a pane window that has the necessary graphics context. This leads to the situation where you must have a window of class pane to draw to the screen whereas in WIN32 no such limitation exists. What if you had a standard control but wanted to modify it's drawing behaviour? You cannot do it unless you resort to WIN32 API directly, clearly pointing to an inappropriate framework. AtOne works around this situation and others by relying on aggregation instead. That is a window can optionally have a graphics context allowing you to draw to the window. This may seem like a trivial distinction but the problem escalates dramatically when more and more variants are required to be supported. Suppose you wanted to support the simple layout management of "fit parent window" and "drawable" for all window classes defined. By inheritance, to cover all combinations you would require four classes for basic window type (ie. the binary combination of the "fit parent window" and "drawable"). By aggregating these features into the base window class and allowing them to be option you require no extra window classes! Clearly the more you add the better the saving in code and confusion when using the aggregation approach. Another key area in the way that AtOne differs from other frameworks is in how it handles window creation and destruction. Typically most other frameworks in the WIN32 arena tie the life cycle of the C++ window to the life cycle of the WIN32 window. The reason for doing so is rather unclear, but it is clear that such an approach can and does cause difficulty. Firstly, in my experience, it is never really clear when a window is created and when it is destroyed so quite often it take years of experience and much care to figure out where exactly is the best place to, for example create and initialise child controls. Quite often doing things in the incorrect order will result in anything from the desired result not occuring to the application crashing, when it is not entirely obvious why it should do so. AtOne does well to avoid most of these problems by clearly seperating the C++ window life cycle from the WIN32 window life cycle. You might ask how does this help? It helps because if you can "consistently" seperate the life cycle of one from the other it then does not matter when certain operations are performed because failure is generally the result of an invalid window handle. AtOne will defer the requested operation until the WIN32 window is created if it is not open, which is not the case for other application frameworks. This is true of all the common properties to windows such as visibility, enable/disable, size and position, minimize/maximize state, and the window caption. The only cases where order of operation becomes an important issue is in the case of the data contained within controls. For instance, a list box has a list of strings that are added to it when the window is open. AtOne will not persist this data after the window is closed. On to the classes themselves. The base services for a Window are provided in the abstract class GuiWindowBase. The class definition is essentially, class GuiWindowBase : public OsiReferenceCount
protected:
virtual void notifyClose(); virtual void
windowProcDispatch(HWND hWnd,
virtual bool
preDispatch(GuiMessage* pMsg) = 0;
virtual const GuiGenericWindowsClass*
windowClass() const = 0;
void
isOpen(bool bIsOpen);
public:
virtual bool
open() = 0;
//Standard windows related style accessors
//Creation property accessors
LRESULT
selfMessage(UINT uMsg,
bool
selfPostMessage(UINT uMsg,
//Identity methods
All windows are reference counted, hence the class is derived from OsiReferenceCount. Most of the services provided by the base class are merely easy access and modification to a windows style and windows message dispatching. There are also a handful of base attributes that are typically use in keyboard navigation. A number of pure virtual methods exist that are implemented in the main windows class GuiWindow and any other descendant classes that require customized behaviour. Most window classes merely derive from GuiWindow and leave the GuiWindow method implementations to do the work. The important points to note are that the WIN32 window is created and destroyed through calls to createWindow() and destroyWindow(); users create and destroy WIN32 windows by calling the open() and close() methods;WIN32 windows messages are processed by the dispatch() method which can be intercepted during the preDispatch() method call; default message processing is handled by the defaultProcessing() method; and each instantiable AtOne window class has a unique class name which you can query using the hasClassName() and className() methods. The hasClassName() method is particularly important as it will allow you to safely down-cast a GuiWindow to some other class by checking that it is of the class type to which you wish to cast it to. The instantiable fully fledge generic window class in AtOne is GuiWindow whose outline is, class GuiWindow : public GuiWindowBase
protected:
public:
virtual bool
applyStyle(bool bRedraw = false);
//General callbacks
//General object ownership
//Child list access
//Standard property accessors
void
childWithFocus(GuiWindow* pChild);
//Class identity methods
Keyboard input (input focus) is automatically managed through the GuiWindow class requiring no further processing from the application developer. A window can either accept input focus or reject it (ie. a static window), with this characteristic being controlled by the acceptsInput() method. If you wish to programatically give focus to a particular window you can do so by calling its focusInput() method. If you then wish to kill its focus and return focus to the previous window then call the revokeFocusInput() method. The enable() and visible() methods control the enable and visibility states of the window. The position and size of a window is controlled with the extent() method. For top level windows and popup windows the extent is specified in screen relative co-ordinates whereas child window extents are relative to the client are of the parent window. The extent() accessors are used to control the window extent but often you wish to know about the window extent relative to different origins. The query methods windowExtent(), clientExtent() and normalExtent() serve this purpose, returning the screen relative window extent, the client extent and the screen relative extent in normal (restored) window state respectively. Menus and accelerators are specified using the menu(), popupMenu() and accelerator() methods respectively. Each setting method accepts a pointer to a reference counted object meaning that GuiWindow will increment the lock count on these objects when set. You should therefore release the reference passed in after calling these methods (or at the point you no longer need the pointer). The popupMenu() method has an additional argument corresponding to the target window for the menu input. Occasionally you may wish to have a popup menu on one window whose command messages goes to another window. In this case you specify the target window with the extra parameter. If set to null (the default) the target is the window that popupMenu() is invoked on. On occasion you may also wish to synthesize command messages and notifications. The methods notifyParent() and notifySelf() do exactly that with the command message being sent to the parent and itself respectively. The window font, the window brush, the window cursor, the window icon and the window caption (title) are set using the font(), brush(), cursor(), icon() and caption() methods respectively. Windows also have the help related properties helpId(), statusHelp() and popupHelp(). helpId() is the context id for a help topic associated with this window. If this parameter is set and the F1 key is pressed whilst the window has input focus then the help topic will be automatically queried (provide that the help has been initialised in GuiApp). statusHelp() is a string property used typically to describe the purpose of a particular window or control. If the cursor is moved over a window in which this parameter has been specified a the status text callback in GuiApp will be invoked, passing statusHelp() as the text message. This is then typically displayed on a status bar. popupHelp() is a string property that is also typically used to describe the purpose of a window or control, however, in this case the text will be displayed as a tool tip. Each GuiWindow manages its own list of child windows. The child window management happens transparently with child windows being registered and un-registered with the parent during construction and destruction of the child window. GuiWindow provides a host of useful methods that provide access to the child list such as childWithId() childWithClassNameAndId() and forAllChildrenDo(). Should you wish to un-register a child window early you can make use of the discardChild() method. You can also register a child through other means by using the adoptChild() method. GuiWindow also manages a list of reference counted objects. This is useful if you have need for an object whose life-cycle is bound to GuiWindow and whose main aim is to hook into GuiWindow, providing additional functionality. This allows for aggregation to GuiWindow freeing you of the need to have to derive new class that aggregates the appropriate pointer. You bind the object with the window by using the own() method and can un-bind it, should you need to, using the disown() method. In ocnjuction with this mechanism GuiWindow also provides the registerPreCallback() and registerPostCallback() message hooking callbacks allowing other objects to intercept and control the flow of messages to the window. These features combined are used to implement dialog box navigation that does no rely upon hacking into the main application message loop (Dialog boxes are not a seperate class in AtOne : a dialog box is merely a GuiWindow with child GuiWindows that all have the same dialog box navigation object hooking into the message stream). Windows can also be made modal (ie. will not release focus to another top level window until closed) by simply invoking the isModal() method. Similarly a window can be made drawable (ie. allowing you to implement customized drawing code) by calling the isDrawable() method. When true the window has its own graphics context that is passed to the paint() method, which is typically over-ridden. Message processing in AtOne windows is conveniently handled through the use of the message map macros found in guimsg.hpp. Message maps must be declared within the class definition of your descendant window class. A message map begins with the macro BEGIN_MSG_MAP and ends with the macro END_MSG_MAP(parent_class, bPropagate) where parent_class is the name of the parent class and bPropagate is true if the message should be passed on to the parent class message handler. Typically you will want to do this, although there may be times when you wish to terminate further message processing. Within a message map messages are processed using the message handler macro MSG_HANDLER(method, wm_msg), where method is the name of the message handler method and wm_msg is the message ID for the message. For example, MSG_HANDLER(onDestroy, WM_DESTROY) will route the WM_DESTROY message to the onDestroy method of your window class. Note that when using this and other macros the message handler must have a single argument corresponding to a pointer to the message structure, which is always of the form GuiWM_XXXX (in this case GuiWM_DESTROY). These message structures give an easy and type safe access to the parameters of Windows messages. Consult guimsg.hpp to determine the attributes of a given windows message structure. At the next level of nesting AtOne also supports WM_COMMAND message mapping. Windows makes extensive use of command messages in processing menu and control messages. Command maps begin and end using the BEGIN_COMMAND_MAP and END_COMMAND_MAP macros in which both have no parameters. Within the map COMMAND_HANDLER(method, nId) macro is used to route a command message of Id = nId to the command handling method = method. All command handling methods have a GuiWM_COMMAND pointer as the method argument. Note that if a command map is used the WM_COMMAND message cannot be processed using a MSG_HANDLER handler. Controls can also issue notification messages which can be routed using a notification map. A notification map is defined within a command map using the BEGIN_NOTIFY_MAP(nId) and END_NOTIFY_MAP macros. In this case nId refers to the Id of the control issuing the notification. Within the map, notifications are routed using the NOTIFY_HANDLER(method, NotifyCode) macro in which the handler method is called for the NotifyCode notification. A typical example might be, class GuiAtSpecPrinterSetupDialog : public
GuiDialog
//Dialog controls
protected:
BEGIN_MSG_MAP
BEGIN_COMMAND_MAP BEGIN_NOTIFY_MAP(ID_PRINTER_SETUP)
BEGIN_NOTIFY_MAP(ID_PRINTER_LIST)
END_COMMAND_MAP END_MSG_MAP(GuiDialog, true); void
initialiseControls();
public:
virtual bool
apply();
The other important feature in GuiWindow not covered here is in regard
to class identity and Windows class registration. This shall be covered
fully when discussing dialog boxes and controls.
"We use Zeus for Windows and Watcom C/C++ 11.0 as our development environment of choice..." Paavo Jumppanen
|
|||||||||||||||||||||