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 Windows

Unlike 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:
  GuiWindowBase(DWORD nDefaultStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS, DWORD nDefaultExStyle = 0);
  GuiWindowBase(const GuiWindowBase& aCopy);
  virtual ~GuiWindowBase();

  virtual void                          notifyClose();

  virtual void                          windowProcDispatch(HWND hWnd,
                                                           UINT uMsg,
                                                           WPARAM wParam,
                                                           LPARAM lParam,
                                                           LRESULT& Result) = 0;

  virtual bool                          preDispatch(GuiMessage* pMsg) = 0;
  virtual void                          dispatch(GuiMessage* pMsg, bool bDoDefaultProcessing);
  virtual void                          defaultProcessing(GuiMessage* pMsg);

  virtual const GuiGenericWindowsClass* windowClass() const = 0;
  virtual bool                          createWindow(const GuiGenericWindowsClass* pWindowClass) = 0;
  virtual bool                          destroyWindow() = 0;

  void                                  isOpen(bool bIsOpen);
  void                                  isDefault(bool bIsDefault);
  void                                  handleWindow(HWND aWindowHandle);
  void                                  subclassProc(WNDPROC aSubclassProc);

public:
  static LRESULT CALLBACK               WindowProc(HWND hWnd,
                                                   UINT uMsg,
                                                   WPARAM wParam,
                                                   LPARAM lParam);

  virtual bool                          open() = 0;
  virtual bool                          close(bool bCheckForCanClose = false) = 0;
  bool                                  isOpen() const;
  bool                                  isDefault() const;
  bool                                  isInGroup() const;
  void                                  isInGroup(bool bIsInGroup);
  bool                                  discardOnClose() const;
  void                                  discardOnClose(bool bDiscardOnClose);
  HWND                                  handleWindow() const;
  HINSTANCE                             handleInstance() const;
  WNDPROC                               subclassProc() const;

  //Standard windows related style accessors
  void                                  popupStyle(bool bPopupStyle);
  void                                  childStyle(bool bChildStyle);
  void                                  borderStyle(bool bBorder);
  void                                  captionStyle(bool bCaption);
  void                                  thickFrameStyle(bool bThickFrame);
  void                                  dialogFrameStyle(bool bDialogFrame);
  void                                  systemMenuStyle(bool bSystemMenu);
  void                                  minimiseBoxStyle(bool bMinimiseBox);
  void                                  maximiseBoxStyle(bool bMaximiseBox);
  void                                  contextHelpBoxStyle(bool bContextHelp);
  void                                  verticalScrollerStyle(bool bVerticalScroller);
  void                                  horizontalScrollerStyle(bool bHorizontalScroller);
  void                                  acceptDroppedFilesStyle(bool bAccept);
  void                                  topmostStyle(bool bTopmost);
  void                                  groupStyle(bool bGroup);
  void                                  tabStopStyle(bool bTabStop);
  bool                                  hasPopupStyle() const;
  bool                                  hasChildStyle() const;
  bool                                  hasBorderStyle() const;
  bool                                  hasCaptionStyle() const;
  bool                                  hasThickFrameStyle() const;
  bool                                  hasDialogFrameStyle() const;
  bool                                  hasSystemMenuStyle() const;
  bool                                  hasMinimiseBoxStyle() const;
  bool                                  hasMaximiseBoxStyle() const;
  bool                                  hasContextHelpBoxStyle() const;
  bool                                  hasVerticalScrollerStyle() const;
  bool                                  hasHorizontalScrollerStyle() const;
  bool                                  hasAcceptDroppedFilesStyle() const;
  bool                                  hasTopmostStyle() const;
  bool                                  hasGroupStyle() const;
  bool                                  hasTabStopStyle() const;
  bool                                  hasDisabledStyle() const;

  //Creation property accessors
  DWORD                                 style() const;
  DWORD                                 exStyle() const;
  virtual void                          style(DWORD nStyle);
  virtual void                          exStyle(DWORD nExStyle);
  virtual bool                          applyStyle(bool bRedraw = false) = 0;

  LRESULT                               selfMessage(UINT uMsg,
                                                    WPARAM wParam,
                                                    LPARAM lParam) const;

  bool                                  selfPostMessage(UINT uMsg,
                                                        WPARAM wParam,
                                                        LPARAM lParam) const;

  //Identity methods
  virtual bool                          hasClassName(const string pClassName) const;
  virtual const string                  className() const;
};

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:
  virtual const GuiGenericWindowsClass* windowClass() const;
  virtual bool                          createWindow(const GuiGenericWindowsClass* pWindowClass);
  virtual bool                          destroyWindow();
  virtual void                          refreshSystemColours();
  virtual void                          setMenu(GuiMenu* pMenu);
  virtual bool                          synchronisePalette();
  virtual bool                          paint(GuiWindowGC* pGC);

public:
  GuiWindow(GuiWindow* pParent, DWORD nDefaultStyle = 0, DWORD nDefaultExStyle = 0);
  virtual ~GuiWindow();

  virtual bool                          applyStyle(bool bRedraw = false);
  virtual bool                          open();
  virtual bool                          close(bool bCheckForCanClose = false);
  virtual bool                          canClose(GuiWindow*& pFailedWindow);
  virtual bool                          canApply(GuiWindow*& pFailedWindow);
  virtual void                          showError() const;
  virtual bool                          apply();
  virtual bool                          help();
  virtual bool                          defaultAction();
  virtual bool                          tabFocusInput() const;
  virtual void                          check(bool bChecked);
  void                                  show(GuiShowCommand aShowCommand = ShowCommandNormal);
  bool                                  centreInParent();
  bool                                  activateInput() const;
  bool                                  focusInput() const;
  bool                                  revokeFocusInput() const;
  bool                                  forceRePaint() const;

  //General callbacks
  void                                  registerPreCallback(const GuiMessageCallbackInstance& rCallbackIntance);
  void                                  unRegisterPreCallback(const GuiMessageCallbackInstance& rCallbackIntance);
  void                                  registerPostCallback(const GuiMessageCallbackInstance& rCallbackIntance);
  void                                  unRegisterPostCallback(const GuiMessageCallbackInstance& rCallbackIntance);

  //General object ownership
  void                                  own(OsiReferenceCount* pObject);
  bool                                  disown(OsiReferenceCount* pObject);

  //Child list access
  const GuiWindowList&                  childList() const;
  GuiWindow*                            childWithId(int nId) const;
  GuiWindow*                            childWithClassNameAndId(const string pClassName, int nId) const;
  GuiWindow*                            forAllChildrenDo(GuiWindowListForAllDoCallback pCallback, void* pData = 0) const;
  GuiWindow*                            forAllChildrenWithClassNameDo(const string pClassName, GuiWindowListForAllDoCallback pCallback, void* pData = 0) const;
  void                                  discardAll();
  bool                                  discardChild(int nId);
  bool                                  discardChild(GuiWindow* pChild);
  bool                                  adoptChild(GuiWindow* pChild);

  //Standard property accessors
  void                                  isModal(bool bIsModal);
  void                                  isDrawable(bool bIsDrawable);
  void                                  hasLayoutManagement(bool bHasLayoutManagement);
  void                                  id(int nId);
  void                                  helpId(int nHelpId);
  void                                  statusHelp(const string pStatusHelp);
  void                                  popupHelp(const string pPopupHelp);
  void                                  hotKey(int nHotKey);
  void                                  caption(const string pCaption);
  void                                  font(const GuiLogFont& rFont);
  void                                  brush(const GuiLogBrush& rBrush);
  void                                  icon(const GuiLogIcon& rIcon);
  void                                  cursor(const GuiLogCursor& rCursor);
  void                                  origin(const GuiPoint& rOrigin, GuiDeferWindowMove* pDeferWindowMove = 0);
  void                                  extent(const GuiRect& rRect, GuiDeferWindowMove* pDeferWindowMove = 0);
  void                                  acceptsInput(bool nAcceptsInput);
  void                                  enable(bool bEnable);
  void                                  visible(bool bVisible);
  void                                  maximise();
  void                                  restore();
  void                                  iconise();
  void                                  windowState(GuiWindowState nWindowState);
  bool                                  isModal() const;
  bool                                  isDrawable() const;
  bool                                  hasLayoutManagement() const;
  int                                   id() const;
  int                                   helpId() const;
  unsigned                              threadId() const;
  const OsiString&                      statusHelp() const;
  const OsiString&                      popupHelp() const;
  int                                   hotKey() const;
  const OsiString&                      caption() const;
  const GuiLogFont&                     font() const;
  const GuiLogBrush&                    brush() const;
  const GuiLogIcon&                     icon() const;
  const GuiLogCursor&                   cursor() const;
  const GuiPoint&                       origin() const;
  const GuiRect&                        extent() const;
  const GuiRect&                        windowExtent() const;
  const GuiRect&                        clientExtent() const;
  const GuiRect&                        normalExtent() const;
  GuiRect                               invalidExtent() const;
  bool                                  acceptsInput() const;
  bool                                  isEnabled() const;
  bool                                  isVisible() const;
  bool                                  isEnabledProper() const;
  bool                                  isVisibleProper() const;
  bool                                  isMaximised() const;
  bool                                  isIconised() const;
  bool                                  isRestored() const;
  GuiWindowState                        windowState() const;

  void                                  childWithFocus(GuiWindow* pChild);
  GuiWindow*                            childWithFocus() const;
  void                                  layout(GuiLayout* pLayout);
  GuiLayout*                            layout() const;
  GuiLayoutManager*                     layoutManager() const;
  void                                  stateManager(GuiStateManager* pStateManager);
  GuiStateManager*                      stateManager() const;
  void                                  menu(GuiMenu* pMenu);
  GuiMenu*                              menu() const;
  void                                  popupMenu(GuiMenu* pPopupMenu, GuiWindow* pPopupMenuTarget = 0);
  GuiMenu*                              popupMenu() const;
  void                                  accelerators(GuiAccelerators* pAccelerators);
  GuiAccelerators*                      accelerators() const;
  GuiWindow*                            parent() const;
  LRESULT                               notifyParent(int nId, int nNotify) const;
  LRESULT                               notifyParent(int nNotify) const;
  LRESULT                               notifySelf(int nId, int nNotify) const;
  LRESULT                               notifySelf(int nNotify) const;
  GuiWindowGC*                          graphicsContext() const;

  //Class identity methods
  declClassIdentity;
};

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
{
private:
  GuiAvailablePrinters  AvailablePrinters;
  GuiPrinter*           SelectedPrinter;

  //Dialog controls
  GuiComboBox*          PrinterList;

protected:
  bool                  onClose(GuiWM_CLOSE* pMsg);
  bool                  onPrinterSetup(GuiWM_COMMAND* pMsg);
  bool                  onSelectedPrinterChanged(GuiWM_COMMAND* pMsg);
  static bool           callbackAddAvailablePrinters(GuiPrinter* pPrinter, void* pVoidThis);

  BEGIN_MSG_MAP
  MSG_HANDLER(onClose, WM_CLOSE);

  BEGIN_COMMAND_MAP

  BEGIN_NOTIFY_MAP(ID_PRINTER_SETUP)
  NOTIFY_HANDLER(onPrinterSetup, BN_CLICKED)
  END_NOTIFY_MAP

  BEGIN_NOTIFY_MAP(ID_PRINTER_LIST)
  NOTIFY_HANDLER(onSelectedPrinterChanged, CBN_SELCHANGE)
  END_NOTIFY_MAP

  END_COMMAND_MAP

  END_MSG_MAP(GuiDialog, true);

  void                  initialiseControls();
  void                  invokeChanges();

public:
  GuiAtSpecPrinterSetupDialog(GuiWindow* pParent);
  virtual ~GuiAtSpecPrinterSetupDialog();

  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. 
 

Programming Dialogs


"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.