Background, Introduction,
Fundamentals of coded layout design,
Building a dialog class,
Reducing the verbosity of the layout definition with your own macros
Control methods and notifications,
Controlling colors and fonts,
Beyond notifications,
Summary of event handling and more about aesthetic metrics structs,
Painted controls,
A more complex example, Tabbed dialogs,
Display text in multiple languages
Quick reference, How it works,
Points of Interest, History
Code update 5 June 2015 - Language files would cause Link errors with multiple compilation units - now fixed by changes to autodlg.h only.
The Visual Dialog Editor, class wizard and MFC have been the entry point for many people (including myself) to C++ Windows programming. If all the wizards work correctly it is a comfortable way of starting to write code in an event driven and component supplied environment. However the wisdom of remaining with this as the only way to design and define a dialog is questionable. In particular:
I decided that a new approach is needed with the fundamental requirement being that:
Presented here is a way of achieving this encapsulated as a C++ base class for windows dialogs. It is 'Win 32' in that it requires neither MFC nor ATL/WTL. However it will sit comfortable with either class library and supports incorporating thier control class wrappers in its dialog definitions.
The fundamental difference with the approach presented here is that your code does all the work of creating the dialog rather than have Windows create a dialog from a dialog template resource that you then attach code to. There is no need for dialog or control ID's because there is no run-time mapping of code to a dialog template. Instead there is compile time mapping of each control to a C++ variable. Furthermore each of those variables has its own unique data type. Such rich typing is perhaps radical but it brings many benefits and is key to the design. It allows the C++language to resolve many issues at compile time through type resolution and this reduces and simplifies the code that you have to write. In practice this means that for every variable representing a control, say btnCancel, a unique data type will be generated with the same name prefixed by an underscore _btnCancel.
As you will see, there are times when you will need to refer to a control by its data type _btnCancel
rather than its variable name btnCancel.
The unfamiliarity of coding a layout instead of dragging and dropping it is dealt with in the next section and after that, most things are simpler, cleaner and more concise than you are probabaly used to.
There are some other innovations including:
as_text
through which the controls window text may be read and written, and which persists when the dialog is closed.This is what could be described as the hard bit because it replaces the use of a visual dialog editor. By the time you have your dialog working you will have written less code but the coded layout instructions require a bit more mindfullness than dragging boxes until it looks right. There will be rewards for that mindfullness though and you will not get them if you use visual editing. This is how it works:
Each control is fully defined, declared, and positioned with the following macro:
N.B. The verbose AUTODLG moniker prefixes all macros so they don't clash with anything else in the global namespace.
C++
at
- where Dx
, and Dy
are the absolute position from the top left corner of the dialog.
to_right_of<_control>
- where Dx
, and Dy
are offsets from that positionunder<_control>
- where Dx
, and Dy
are offsets from that positionand the label_locator
can be any one of:
label_left
- where 'by' is the amount by which the label extends to the left of the control.label_top_align<_control>
- where the top edge of the label aligns with the top edge of another control.Note that when referring to previously declared controls, we refer to them by their data type name _btnActivate
rather than the the variable name btnActivate
.
The data type name can also be used to access the calculated (at compile time) position and size of the control
e.g. _btnActivate::left
top
, right
, bottom
, width
, and height
are also available. It can be useful to use these with the at
verb where to_right_of
or under
aren't exactly what you want.
For instance; There are no verbs for 'to left of' or 'above'. This is because the size of the newly created control is needed to calculate the position. However you, the programmer, do know the size of the control you are adding and can position a control to the left of another as follows:
AUTODLG_WIDTH_TO
(x_coord)
AUTODLG_HEIGHT_TO
(y_coord)They are useful for instructing a control to use up the available space up to the edge of another control.
control_type, control_styles, extended_styles,
describe the control itself. The control type can be a raw control specified by its windows class name (BUTTON, EDIT, LISTBOX
etc.). or it can be a control wrapper class such as the CButton, CEdit
and CListBox
of MFC and WTL.
Building a dialog class
It is a good idea to start with a visual plan. You can hold it in your head, sketch it with pen and paper, use Paint.exe or even a visual dialog editor (any old one will do). I created this with Paint for a dialog that captures an entry code from the user.
There is always a way of coding any layout, indeed many ways, but what you want is a way that maintains its integrity when parameters such as button width change and also that is amenable to being altered. The key to this is to identify which groupings and alignments you care about and the dependancy chains involved. In this example we only have one group but we do care about some alignments as illustrated in the following diagram.
We want the Cancel button to line up under the Reset button, the OK button to the right of the Cancel button by a standard spacing and we want the right edge of the edit to line up with the right of the OK button with its label left aligning with the Reset button. The more arbritrary exact position of the left edge of the edit can then be adjusted without affecting anything else.
The dialog class definition takes a metrics struct as a template parameter and uses autodlg::def_metrics
as a default. For now you need to know that autodlg::def_metrics defines the following enums
dlg
has been declared as a EnterCodeDlg<>
type with empty braces <>
. This means that it will use the default metrics struct with which it was defined. A different metrics struct (but with the same parameter structure) can be forced on it by passing it in as the template parameter. e.g.
OnInitDialog
is called after the dialog and all its controls have been created. Exactly as you will be accustomed to.
The controls are disabled using their enable
method. This is one of a generic group of methods that can be called on all controls. A full list of generic methods provided for all controls can be found in the quick reference section below.
Now lets deal with the controls as they become enabled starting with the cancel button that is always enabled and always does the same thing. We just create a nofication handler for btnCancel
. There is no need to create a message map entry or any code to call this handler. It will be called automatically simply by being there.
_btnCancel*
. This is what determines that only btnCancel
will call the handler. We aren't interested in the pointer passed in because we know it just points to btnCancel
.
EndDialog()
does what youi are accustomed to but can be used to end both modal and modeless dialogs so there is no need to code them differently. Typically its argument will be IDOK
or IDCANCEL
which are always defined. You will not be able to pass control IDs because you don't have any. You can of course pass your own numbers that mean things to you.
Now the response to text being entered into the edit control:
BannerLabel
is distinguished from PaintedLabel
only by having a different type name and this is simply so that it can be identified for special handling by the OnCtlColorByControlType(BannerLabel*, ...)
handler as defined in the my_app_metrics
struct defined earlier. If you decide to remain with STATIC
controls for labels then you can use STATIC_BANNER
and that also will recieve the same special handling from the OnCtlColorByControlType(STATIC_BANNER*, ...)
handler provided in my_app_metrics
Other painted controls included in the library are SpacerControl
which is a hidden reference frame on which other controls can be hung (it has no code), GroupBox
which is a replacement for the Windows group box and PaintedTabFrame
which you have to use instead of a Windows tab control. The code for these can be found at the foot of autodlg.h. Further anecdotal examples StopButton
, StartupButton
and PaintedSmiler
can be found in misc_painted_controls.h
If a painted control only displays and needs no mouse interaction then it should be derived from painted_control
and needs only to implement bool OnPaint(HDC& hDC, RECT& r, bool bMouseOver)
to handle paint messages. These are the only messages it will recieve unless you send some to it using do_msg
. The RECT r
passed in is in dialog coordiates because the hDC
is that of the dialog.
If mouse interaction is required then it must derive from painted_mouse_control
and will also have to provide the handler void OnNonPaintMessage(UINT message, WPARAM wParam, LPARAM lParam)
. This will recieve mouse messages which can be decoded using the mouse_parms
struct. The x,y coordinates in this case are with respect to your control, not the dialog.
MOUSE_OVER_STYLE
which causes controls to invalidate when the mouse enters and leves it rectangle
EXPAND_X_STYLE
and EXPAND_Y_STYLE
which mark a control for expansion if the dialog is resized to larger than its design sise.
Naming conventions, these names are generated automatically
btnOK
is _btnOK
_label
edtCode
is called edtCode_label
Dialog public members
set_first_ctrl
may be passed a reference to the dialog in whih it is written as in the example above, or it can be passed another dialog
auto_string
is the dynamic text buffer type that is used for the as_text
variable supplied with every control. Its special feature is that writing to it also writes to the control and reading from it reads from the control. Otherwise it is a very basic dynamic text buffer. You can do the following with it:
BUTTON
, CHECKBOX
, RADIOBUTTON
, GROUPBOX
, EDIT
, RICHEDIT
, LISTBOX
, COMBOBOX
, SCROLLBAR
, STATIC
, STATIC_BANNER
, SysMonthCal32
, PROGRESSBAR
, SysListView32
, SysTreeView32
List of painted controls supplied with the library
PaintedLabel
, PaintedBanner
, SpacerControl
, GroupBox
, PaintedTabFrame
For more informatiom about these and also how to design painted controls see the Painted controls section
PaintedTabFrame
A PaintedTabFrame
should be declared in your layout as a control, specifying how many tabs it has as a template parameter.
PaintedTabFrame
public methods in addition the generic control methods are:
autodlg_on_control_query
is a virtual function implementation and does actualy generate run-time code that will be called.
Finally the variable is declared to be of the newly defined type.
An immediate advantage of declaring the layout as static const class info is that having defined and declared one control:
control
is the most developed common enumerable base class. That is it is the only class that says that it is a control without being at all specific about what type of control. control
is in turn derived from dialog_element
which is less than a control
and serves two purposes; to provide the virtual function autodlg_on_control_query
which which can be called by everything deriving from dialog_element
and to provide a null control marker when it stands alone.
Controls are declared as adjacent variables of varying sizes. There is no list or array of pointers to traverse so another way has to be found. The method used is to move from one control to the next by incrementing a pointer by its class size. The only thing is that the full class size is not immediately available to the base class control.
It can only be found by calling a virtual function that is implemented in the complete derived class. The one and only virtual function is autodlg_on_control_query
which is used for all such queries including the class info registered by the AUTODLG_CONTROL
macro. A query is a class with a code, appropriate data member and a function to execute the query. It can request an action or information. The query is initialised, passed to autodlg_on_control_query
and is executed by the autodlg_on_control_query
in the most derived class. The most important query has code INFO
and this fill itself out with all of the class info:
T
is the most derived class passed in by its autodlg_on_control_query implementation. Two methods are called on the dialog. In the general case, they will be handled by the following sinks provided by the AUTODLG_DECLARE_CONTROLS_FOR
macro which do nothing and compile nothing.
template <class C> inline void OnNotificationsByControlType\
(C* pC, UINT NotifyCode, LPARAM lParam){}\
template <class C> inline void OnNotificationsFrom\
(C* pC, UINT NotifyCode, LPARAM lParam){}\
They are indifferent to the type of the first parameter. Any type will be accepted. However if you provide the dialog with a version that types the first parameter as the control you called it from:
C++
void OnNotificationsFrom(_btnCancel*, UINT NotifyCode, LPARAM lParam)
{ ....
then that will get called instead. So all you have to do is provide a handler with the first parameter typed for the control and it will get called. All event handling uses this mechanism.
Those are the main arteries of how it works. To know more you have to examine the code according to your curiosity. Most of it is found inside the class dialog_base
. It has to be encapsulated somwhere and putting control class definitions inside the dialog base class eliminates many problems of precedence between the two and makes for cleaner coding.
Points of Interest
I already knew from working on a previous project Units of measurement types in C++ that rich typing can allow the compiler to check many things and make better decisions without any run-time cost (because it has already been done during compilation) but it still surprised me that it could bring so many seemingly miraculous benefits to dialog design. It really is no miracle. It seems so because as designers we see the dialog layout as something variable because it is our job to vary it. This causes us to overlook the fact that it is a constant fully known at compile time for any given compilation. The use of dialog resource templates reinforces this oversight because it seems (and probably is) a variable loaded up at run-time. Expressing the layout as pure C++ code provides the opportunity for the constness of a dialog layout to be properly recognised by the language and providing an informative type for each control is the way to do it. The apparent miracles result from full information about the layout being available at compile time.
Most of the code has a very clear logic to follow and my hope is that this will work under what ever circumstances you want to throw at it. The least developed part is the resizing and rearranging of controls as a dialog is resized. Nevertheless you will probably find that it works well for you and that it is not difficult to avoid the corner cases that catch it out. I have decided to publish it as is and refine this later at my leisure.
For me the biggest achievement is that coding your layout empowers you as a programmer, the skill you are good and clever with, and it releases you from having to put aside those fine skills to labour as a draughtsman and IDE operative.
I have done this work and published it because I would like to see others using it. I enjoy writing libraries more than using them and in some ways I wrote it as a remedy for the years I have suffered with tradional dialog design. I wanted to put an end to that suffering for everyone. I would be very interested in any feedback on the joys and frustrations experienced by anyone who uses what is offered here.
Finally I must mention the excellent series of articles Custom Controls in Win32 API by Martin Mitáš which I have used as a Bible while working on this project. It provided clarity on many issues that I could find nowhere else.
History
First publication and release 29 May 2015
Code update 3 June 2015 - Added "Display text in multiple languages" and fixed small errors.
Code update 5 June 2015 - Language files would cause Link errors with multiple compilation units - now fixed by changes to autodlg.h only