Dialog Base Units and Different Resolutions
Windows programs are meant to run on all monitors connected to PC’s. That means that your program should look good at the minimum supported resolution, and also at very high resolutions. On your own computer, go to
Start | Settings | Control Panel | Display | Settings.
You will get a dialog that allows you to choose various resolutions, and for some resolutions may give you a choice of “Large Fonts” or “Small Fonts”.
Try choosing various resolutions. Always be sure to choose the “test” option unless you are sure your monitor supports the given resolution. See what happens to the appearance of dialog boxes with different choices of resolution and font size. What, by way of comparison, happens to pictures in .bmp format?
The dimensions and layout of dialogs are specified in “dialog base units”. These are meant to be a fixed fraction of the system font size. The idea is that if the system font is legible, then dialogs based on those dimensions should be legible and usable. Thus the same code for a dialog box should produce a usable dialog box at all resolutions (if the system font is legible). The dialog box will NOT be exactly the same size at all resolutions but at least its controls will all fit in the dialog, and probably the dialog will fit in the screen at all but the lowest resolutions.
I have seen term projects demonstrated at a final demo, in which the resolution was lower than at the student’s home, and the buttons needed to control the program were off-screen and hence unusable. The program was effectively hung. The moral is, test at all supported resolutions.
Accessing the controls in a dialog
There are three ways of identifying a control
- by its control ID number, such as IDC_RED.
- by its Win32 handle
- by an MFC object such as an object of type CButton.
It is important to understand the relations between these three so you can use them correctly. Many errors result from misunderstandings about this.
First basic point: The control ID is specified at compile time. You can see it in your source code. The handle and the MFC object are created only at run time. However, you can refer to them by means of variables in your program.
Since you know the control ID while writing code, it is easier if you can use that. However, the functions you want to call are (in MFC) member functions of some MFC class associated with the control , so you will need these objects. The Win32 handle of the control is usually not used in MFC programming, but occasionally you need it. It is usually a member of the MFC class.
The MFC classes that "wrap" controls are all derived from CWnd. For example you have CSliderCtrl, CListBox, CButton, CRadioButton, etc.
There are two ways to get an MFC object wrapping one of your controls:
- declare a local variable and use GetDlgItem
- use Class Wizard to associate a member variable with the control.
We will study both methods and compare them.
Using Class Wizard to associate a member variable with a control.
For some controls Class Wizard allows you to add a variable with category "Control" as illustrated here:
When Class Wizard allows this, it automatically gives you a variable of the MFC class wrapping that kind of control. This variable will be created when you dialog is created and last until it is destroyed. It is the easiest and safest way to get an object to use to manipulate your control. The example above shows that you can use this method to get a Control variable for a pushbutton.
Warning: Many students make mistakes using Class Wizard to add member variables, because they don't pay enough attention to the "category".
There are two categories of member variables you can add using Class Wizard: Value and Control. Value variables are used for "dynamic data exchange" (DDX). We studied this already--they are the member variables that are used to store the values that are also kept in the controls. For example, you could use an integer variable to represent an integer associated with an edit box. The type of a value variable is likely to be int or CString or some other simple data type. There are only a few choices.
A "control" variable instead represents the control itself, not any data connected with the control. You use a control data to communicate with the control, for instance, to add strings to a list box. The type of a control variable is an MFC class suitable for the kind of control whose ID number you selected, for example,
CButton.
Make sure that you select "control" if you want a control variable, or "value" if you want a value variable, and at least be certain in your own mind which one you DO want for a given purpose.
Don't just fail to pay attention to that part of the form in Class Wizard.
You will find that Class Wizard won't let you create control variables for some kinds of controls, e.g. static text and radio buttons. If you need to manipulate these (e.g. changing text at run time) you will need to use another method.
Using GetDlgItem
GetDlgItem is the key to connecting the control ID with the MFC class object. It takes a control ID as argument and returns a pointer to the MFC class object. Since it has to have a fixed return type, it is declared to return a pointer to CWnd. When you use it, you therefore must cast the returned pointer to whatever type you really are getting. For example
(CSliderCtrl *) GetDlgItem (ID_SLIDER);
The return type is only a CWnd. We need the cast in order to use it in code like this:
CSliderCtrl *mySlider =(CSliderCtrl*) GetDlgItem (ID_SLIDER);
Without the cast, there will be a compiler error or warning, as the compiler does not know that the CWnd pointer returned by GetDlgItem really points to a CSliderCtrl. Of course, we could put this cast in even if it didn't point to a CSliderCtrl, which would silence a compiler warning but create a run-time error. Therefore, we have to know what we're doing!
Typically, you would put this code into some message handler, and then manipulate the slider, e.g. to set its range or its tick spacing.
MFC Temporary Objects
When you created mySlider by calling GetDlgItem(MYSLIDER),
do you need to call delete mySlider?
No, you don't. The reason is that the objects created this way are MFC temporary objects. This means that they are cleaned up by MFC, during the processing of WM_IDLE messages. This processing is done by the default OnIdle function. Windows sends the WM_IDLE message when there is no user activity and no other functions are executing. The continual arrival of WM_IDLE messages keeps the main message loop from terminating, as we discussed in How Windows Works.
The reason you have to know about this is that lifetime of objects created by GetDlgItem is thus effectively just the scope of the function where they are created. You can't initialize a variable by GetDlgItem and expect to use it later--it will only last during the processing of this one message.
GetDlgItem in the Win32 API
There is a function GetDlgItem in the Win32 API, which is similar to the member function GetDlgItem of the CWnd class in MFC.
But they are different, and you can make mistakes if you call the Win32 API function by mistake.
1. The Win32 function returns a window handle, not a pointer to a C++ class object.
2. The handle thus returned is not temporary and must be deleted by the programmer. Automatic deletion of temporary objects is a feature of MFC, not of the Win32 API.
Of course, the member function GetDlgItem works like so many MFC functions: it creates a C++ class with a member variable to hold the window handle, then calls the Win32 GetDlgItem to get that window handle. When it is destroyed in OnIdle, MFC will destroy the window handle that is thus "wrapped".