Form and widgets

This article contains detailed description of a form, different widget kinds and their states, and explains details of interaction between form and widgets. After reading it you will know all needed details about internal behaviour of the framework core related to forms and widgets and better understand its architecture and advantages.

About form

As you already know a form is a container for all widgets. You may place widgets on a form or on another widget on a form and thus create a nice graphical user interface for your application. A form is described by IForm interface and is implemented in CForm class. Form receives user and system events(via assigned platform window) and manages them acting as a conductor and distributor. It is responsible for decoding basic events and converting them into messages widgets can understand. It is also responsible of conducting events to proper widgets. For example, when you click on a form, it searches for the widget under the mouse pointer and, if found, sends the message to that particular widget.

Form is also stores and manages some useful objects that may be used by all widgets on a form. This objects are:

  • The renderer is responsible for all drawing operations. All widgets use it when they draw themselves on a form. Widget may get access to it by using form's method getRenderer(). Renderer is described by IRenderer interface.
  • The hint is responsible for showing tooltips when mouse pointer appears over a widget. Current hint manager can be accessed by getHint() method and new custom hint manager can be set by setHint() method. Usually widgets do not use this object. Form itself uses it intensively to display tooltips. Ability to change hint manager can be used to create widgets which will customize tooltips. They could set its own hint manager when are being placed on a form and restore old one when are being detached from the form. Hint manager is described by IHint interface.
  • The style is responsible for changing makeup of all or some widgets placed on a form. By default there is no style assigned to a form so all widgets are being drawn with default layout they were built with. Access to the currently assigned style can be made by getStyle() method and new style can be assigned by setStyle() method. When new style has just been assigned to a form all its widgets, which property UseParentStyle equals to true receive notification NotifyOnParentStyleChange() via widget's service. Style is described by IStyle interface.
  • The font property of a form contains object which is responsible for managing form's font properties. Currently active font can be accessed via getFont() method and new one can be set by setFont() method. When widget doesn't have its own font, it uses form's one. When form's font has just been changed all form's widgets which have property UseParentFont equals to true receive notification about the change via NotifyOnParentFontChange() method of widget's service. Font is described by IFont interface.
  • The caret form's object is responsible for drawing caret in different widgets which support entering data using keyboard. Current caret manager can be accessed via getCaret() method and new custom one can be set by setCaret() method. Caret manager is described by ICaret interface.
  • The transform is responsible for position of form on a screen. It can be get by getTransform() method. Form transformation(positioning) controlling object cannot be changed.
  • The transform controls is responsible for global transformation applied to all widgets on a form. It can be get by getTransformControls() method and new custom one can be set by setTransformControls() method. It can be useful when some change should be applied to entire form. It can be zoom or scroll or something more exotic. Both transforms are described by the same ITransform interface.

Besides objects described above form also has many properties which customizes its appearance or control in some way interaction with widgets. Form also has many events you can use to get notification about almost every change on a form. You can learn all about the form's properties, events, and methods on reference page IForm describing form interface.

Widget kinds

There are 3 different widget kinds defined in the framework. We describe them here in the simplicity order.

The first one widget kind is the component. Widgets of this kind implements IComponent interface and usually derived from CComponent class which contains default implementation. Widgets of this kind are invisible themselves on a form and cannot be placed onto another widgets. The purpose of such widgets is to provide some helper functionality. For example, component may store a list of images or call some job periodically. Components may also be very complex and powerful. For example, it may implement web server. Despite of components are not visible on a form, they can have visible representation sometimes. For example, a component may show a form or dialog box for selecting color or file on the hard drive.

The second kind of widgets is the control. This is a common, most widely used, widget type. It can be placed on a form directly and onto another widgets which are controls themselves(well, there are of course some widgets which forbid place another widgets on themselves and there are some widgets which forbid placing themselves on another widgets or on a form or on a widgets of particular implementation, but in general the idea is correct). These widgets have visible representation on a form. Here is some examples of widgets of type control: label, text input, panel, month calendar, table, clock. All controls implement interface IControl and are usually derived from class CControl. Control is derived from component and has all its properties and methods.

And finally there is a third kind of widgets which was first introduced in release 9.0.0. This is the dialog box. This widget kind is derived from the control and has all its properties and methods. The difference between dialog box and control lays in the handling it by a form. Dialog box is treated as a widget which contains another widgets and acts as a form inside a form. It means that when user activates any widget placed on a dialog box or click on dialog box itself the form activates the dialog box. When user closes the dialog box a previously active dialog box becomes active again(if there is one). So to a user it looks like working with different child forms on the main one. All the dialog box widgets implement interface IDialogBox and are usually derived from class CDialogBox. When dialog box has just been activated it receives notification NotifyOnSetActive() via its service and event OnSetActive is being triggered. At the same time all widgets, except activated dialog box, on a form receive notification NotifyOnDialogBoxActivate() via their services and their events OnDialogBoxActivate are triggered. The same thing happens when dialog box becomes inactive, the notifications NotifyOnKillActive(), NotifyOnDialogBoxDeactivate() and events OnKillActive and OnDialogBoxDeactivate triggered on dialog box and another widgets respectfully.

Since the release of 9.0.0 the Form Builder can generate source code file of a form in form of dialog box. So you can create form as usual and use it either as standalone form or dialog box in you application at your choice.

Widget states

Widgets of type control and dialog box can have several different states which affects their interaction with user and form.

First of all widget can be visible or hidden. When it is hidden it is not being drawn on a form and cannot interact with user. All widgets which are on the invisible one are also considered invisible. State of visibility is absolute which means child widget can do nothing to change behaviour or make itself drawable on a form if one of its parents is invisible. State of visibility is controlled by setVisible() method of IControl and can be checked by isVisible() method. Some widgets may forbid hiding themselves by overwriting setVisible() method. Such a change is allowed by the framework and is properly handled by its objects. When widget becomes invisible by changing its visibility state(not by changing visibility state of parents) its service receives notification NotifyOnHide() and its event OnHide is triggered. Additionally the direct widget's parent(either another widget or form) receive notification NotifyOnChildHide() via a service and parent's event OnChildHide is triggered. In the same way there are notifications NotifyOnShow() and NotifyOnChildShow() and events OnShow and OnChildShow to inform about making widget visible again.

Widget can be enabled or disabled. When it is disabled it is still being drawn on a form(probably in slightly different style) but it cannot interact with user. Also when widget is disabled it still receives all notifications and it is completely under the widget control what to do with them - ignore in respect to disabled state or process as usual. It is important not to forget this when creating your own widgets. Due to previous statement the state is not absolute. It is relative and depends on widget implementation. Despite of this some widget states, which will be described a little bit later, cannot be applied for disabled widget. You may check whether widget is enabled or not by its method isEnabled() and set new state by method setEnabled() of the IControl interface. As with the visibility state some widgets may overwrite setEnabled() method to prevent changing the state. When widget becomes disabled by changing its state(not by changing state of parents) its service receive notification NotifyOnDisable() and its event OnDisable is triggered. Additionally the direct widget's parent(either another widget or form) receive notification NotifyOnChildDisable() via a service and parent's event OnChildDisable is triggered. In the same way there are notifications NotifyOnEnable() and NotifyOnChildEnable() and events OnEnable and OnChildEnable to inform about making widget enabled again.

The next state is focused state. Control which is in focused state receive all keyboard messages. This state is usually applicable to widgets which allow to do something using keyboard(like entering texts or switching between rows or tabs). Widget is responsible for informing whether is can be focused or not by its method isAcceptFocus(). If you create your widget derived from CControl(which is preferable and most often used case) you just pass corresponding flag into its constructor. Disabled widgets cannot be focused. Only one widget on a form can be focused at the same moment. When you focus another widget currently focused one loses focus automatically. The widget can be focused by IForm method setFocusedControl() or by IControl method setFocus() which uses the previous one. Also a form automatically set widget focused when user clicks on it(if it is focusable and enabled of course) or switch between widgets by Tab or Shift+Tab keys(can also be disabled by changing form's FocusByTab property to false). The last behaviour is also depends on widget's TabStop and WantTabs properties. The first property if equals to false will prevent setting the widget focused while the second one will prevent losing focused state by mentioned key combinations. You may check whether widget is focused by its isFocused() method or by comparing widget with the one which form's getFocusedControl() method returns. It is not recommended to overwrite any of the methods described above. You just need to put correct flags into CControl constructor instead. When widget becomes focused its service receive notification NotifyOnSetFocus() and its event OnSetFocus is triggered. Additionally all widgets on the form(except for the just focused one) receive notification NotifyOnControlSetFocus() via a service and their events OnControlSetFocus is triggered. In the same way there are notifications NotifyOnKillFocus() and NotifyOnControlKillFocus() and events OnKillFocus and OnControlKillFocus to inform about widget losing focused state.

The next two states are special. They are similar so we tell about them in the same paragraph. The states called capture states. The first one is the capture keyboard state and the second one is the capture mouse state. These states allow widget to receive messages from keyboard and mouse even if they are not focused on not below mouse pointer. It is some sort of message interception. Only one widget can capture keyboard input and only one widget can capture mouse input. To start capture keyboard input method CaptureKeyboard() of IForm should be called with specifying the widget which will capture keyboard input. The same is true for starting capture mouse input only the method is CaptureMouse(). Which control captures keyboard or mouse input may be identified by form's getCaptureKeyboardControl() and getCaptureMouseControl() methods. Also you may check whether widget captures keyboard or mouse input by IControl methods isCaptureKeyboard() and isCaptureMouse(). When widget no longer needs keyboard or mouse input the methods ReleaseCaptureKeyboard() or ReleaseCaptureMouse() of the form should be called. In this case capture states will be transferred to widgets which were previously capturing input(if there were any). Starting and finishing capturing input is usually initiated by widget and completely depends on its implementation. For example, if you create widget which is some sort of image editor, you will probably need to capture mouse input at some conditions. As an example of such condition may be drawing a line. Usually when mouse pointer leaves widget area the widget does not receive mouse move notifications anymore. In case of user start drawing a line(down mouse button at some point of widget) and moves mouse pointer he can accidentally(or on purpose) move pointer out of the widget area and line will not be updated when user moves pointer outside of widget. Moreover widget will not know when user release mouse button to finish drawing a line. This is incorrect behaviour. Proper one could be achieved by starting capture mouse input right when user downs on a mouse button indicating line starting point. In this case mouse events will be sent to the widget all the time even if mouse pointer is out of its area. When widget starts capture keyboard or mouse its service receive notification NotifyOnSetCaptureKeyboard() or NotifyOnSetCaptureMouse() and its event OnSetCaptureKeyboard or OnSetCaptureMouse is triggered. Additionally all widgets on the form(except for the widget which just started to capture input) receive notification NotifyOnControlSetCaptureKeyboard() or NotifyOnControlSetCaptureMouse() via a service and their events OnControlSetCaptureKeyboard or OnControlSetCaptureMouse is triggered. In the same way there are notifications NotifyOnKillCaptureKeyboard(), NotifyOnKillCaptureMouse(), NotifyOnControlKillCaptureKeyboard(), and NotifyOnControlKillCaptureMouse() and events OnKillCaptureKeyboard, OnKillCaptureMouse, OnControlKillCaptureKeyboard, and OnControlKillCaptureMouse to inform about widget losing input capture.

Each widget also can be hovered. It simply means that widget is below mouse pointer. Form has absolute control of this property and finds widget under mouse pointer automatically when required. Which control is under a mouse pointer you can find by getHoveredControl() method of IForm. You also may check whether control is under mouse pointer or not by method isHovered() of IControl. When widget becomes hovered its service receive notification NotifyOnMouseHover() and its event OnMouseHover is triggered. In the same way there is notification NotifyOnMouseLeave() and event OnMouseLeave to inform widget about mouse leaving its area. These notifications and events are triggered independently of capture mouse state. If for some reason you need to update hovered state, you may call method UpdateHoveredControl() of a form.

The next state is an active state. This state is just an indicator which widget may posses and which will be lost at any user action(like clicking anywhere, or switching focus to next widget). It can be useful for such widgets as dropdowns. When user clicks on its arrow to see a list of options, list appears and widget tells form it wants to be active. When user perform any action, form removes active state from the widget and sends notification to it. Widget closes the list. To set active state to a widget use method setActiveControl() of IForm. To get currently active widget use getActiveControl() method. And to check whether a widget is active or not you may use method isActive() of IControl. When widget becomes active its service receive notification NotifyOnActivate() and its event OnActivate is triggered. In the same way there is notification NotifyOnDeactivate() and event OnDeactivate to inform widget about losing active state.

Modal state indicates limitations to messages passed to widgets. If widget is in modal state only this widget and its children can receive input messages. Only one widget can be modal on a form at the same moment. When passing modal state to another widget, currently modal one loses modal state but it remains in modal widgets history of a form and when new modal widget releases modal state it becomes modal again. Widget can be modal only if its method isAcceptModal() returns true. It is not recommended to overwrite this method. Use CControl constructor corresponding argument instead. Modal state is actively used with dialog boxes. Making dialog box modal prevent from automatically switching to another dialog box(if there is one) or deactivating it by clicking somewhere outside dialog box area). To make widget modal there is method setModalControl() at IForm interface. Form also has getModalControl() which returns widget currently in modal state. You can also check if widget is in modal state by the method isModal() of IControl. To release modal state and pass it to previous modal widget(if there is one) use form's method ReleaseModal(). To release modal state and prevent passing it to any previously modal widget use form's setModalControl() method with nullptr argument. This will release modal state from current modal widget and clear history of modal widgets. When widget becomes modal its service receive notification NotifyOnSetModal() and its event OnSetModal is triggered. Additionally all widgets on the form(except for the one received modal state) receive notification NotifyOnControlSetModal() via a service and event OnControlSetModal is triggered. In the same way there are notifications NotifyOnKillModal() and NotifyOnControlKillModal() and events OnKillModal and OnControlKillModal to inform about losing modal state.

Widgets which are dialog boxes can have their own active state indicating which dialog box is currently activated(do not confuse with ordinary active state; this active state is for dialog boxes only and it works in different way). As was described in previous chapter form activates and deactivates dialog boxes automatically. But it is also possible to do it programmatically by method setActiveDialogBox() of IForm to activate another dialog box or deactivate all of them and clear activation history by passing nullptr as parameter. Form's method getActiveDialogBox() can help with getting currently active dialog box. You also can check if dialog box is active by method isActiveDialogBox() of IDialogBox interface. When dialog box becomes active its service receive notification NotifyOnSetActive() and its event OnSetActive is triggered. Additionally all widgets on the form(except for this dialog box) receive notification NotifyOnDialogBoxActivate() via a service and event OnDialogBoxActivate is triggered. In the same way there are notifications NotifyOnKillActive() and NotifyOnDialogBoxDeactivate() and events OnKillActive and OnDialogBoxDeactivate to inform about losing active state.

And finally there is so called input state. In this state widget receives all unhandled input from keyboard and mouse. It can be useful for creating widgets which modify form borders and caption area. To set input widget use setInputControl() of IForm. Method getInputControl() of a form returns current input widget. Only one widget can have input state at the same moment on a form.

Not all combinations of states described above can be used together. Below is a list of restrictions applied to state combinations. If one of the criteria is not applicable a widget loses corresponding state. If state is not listed below it means there are no restrictions for that state.

  • Focused: should be visible, enabled and be on modal widget if there is one.
  • Capture keyboard: should be visible, enabled and be on modal widget if there is one.
  • Capture mouse: should be visible, enabled and be on modal widget if there is one.
  • Hovered: should be visible.
  • Active: should be visible, enabled and be on modal widget if there is one.
  • Modal: should be visible.
  • Active dialog box: should be visible and be on modal widget if there is one.

Also all states from list above will be lost for widget when it is being detached(removed) from a form.

Dialog boxes

Since release 9.0.0 Form Builder can generate form prototype header file in form of dialog box. It means that instead of having standard standalone form you will have a dialog box widget. You have to create a class for your dialog box derived from the class in exported header file and implement its notification methods as usual you do for a form. Implementation is almost identical to the process of implementation of a regular form. The difference is only in constructor which is more like CControl constructor then form's one. Later you may create your dialog box in the same way you create another widgets(just create it and place either on your form directly or on another widget: m_pMyDlg = new CDialogBoxMyDlg(form)); // In case you have added constructor which creates dialog box and places it on a form by calling setForm(form) method). When you have your dialog box created you may show it as ordinary form by Show() or ShowModal() methods. How to get result in the latest case we will explain a little bit later.

Additionally to common widget properties and methods dialog boxes can be maximized(take entire client area of parent widget or form) and minimized(display only a caption with control buttons) but this can be disabled by particular widget implementation by overwriting corresponding methods. Also there are two very helpful methods in dialog boxes. The first one is Show() which shows dialog box and makes it active. And the second one is ShowModal() which additionally makes dialog box modal one. The best practice is to start your dialog box widget by deriving it from CDialogBox class which implement some dialog box logic. Respectfully the dialog box service should be derived from CDialogBoxService class.

Like was mentioned before dialog box acts like form. As you can see it has similar to form properties and methods. There is but one important difference in ShowModal() methods of widget and form. In case of form the method waits until form closes but in case of dialog box it does not. In case of dialog box it shows dialog box, makes it modal and return immediately. It is very often result of showing dialog box is required. So how to get it? The answer is using listener. Listener is a simple interface which instance can be assigned to dialog box via setListener() method of IDialogBox. When action corresponding to action described in a listener happens in dialog box the latest call listener's method. If you look at IDialogBoxListener interface, you will see 4 methods-events corresponding to closing, minimizing, maximizing, and restoring of dialog box. In case of getting the result of showing modal dialog box we are particularly interested in OnClose() method of a listener. So, to get the result we have to implement IDialogBoxListener interface in some class(it even can be a widget class itself but more often it is implemented separately as small child class inside widget's class which have access to widget). In OnClose() method of it we can check ModalResult property of dialog box which called the method(sender argument) and if it equals to ModalResult::Ok then perform the same action as we would have done if similar form's ShowModal() method would have returned ModalResult::Ok. ModalResult property can be get via getModalResult() method of dialog box and can be set via its setModalResult() method. The last one by default closes the dialog box and is preferred method for finishing dealing with dialog boxes. By the way, ShowModal() method of dialog box sets ModalResult property value to ModalResult::None and if dialog box was closed by close icon it remains the same.

As dialog boxes are generated from form being created in the Form Builder its layout can be slightly different from the case of using regular standalone form. It usually makes dialog box widgets appear a little bit shifted and client area a little bit wider then required. To fix this issue we have added method AutoResize() to IDialogBoxEx interface which is the interface for DialogBoxEx widget mainly used as base class for generation of your dialog box in the Form Builder. Calling this method automatically adjust dialog box to content(child widgets) and optionally may set its correct position. This method should be called right after calling Show() or ShowModal() of dialog box and can be called only once. Additionally you may set proper layout properties(like background color) if default ones differ from the style of your application.

Some standard widgets can be used in dialog box mode instead of regular form one. How to use this feature and also create your own dialog boxes and properly use them you can find in the Tutorial: dialog boxes. Part 1 and Tutorial: dialog boxes. Part 2.