With this modification, all four buttons will behave like check boxes. Similarly, if we want them to behave like push buttons, we just need to use style flag
TBBS_BUTTON.
The state of a button can be retrieved by another member function of CToolBar. It lets us find out a button’s command ID, and current state (checked or unchecked, the associate image):
void CToolBar::GetButtonInfo(int nIndex, UINT& nID, UINT& nStyle, int& iImage);
At any time, we can call this function to obtain the information about buttons. No additional variable is needed to remember their current states.
The method discussed here can also be used to create radio buttons. In order to do so, we need to use TBBS_CHECKGROUP instead of TBBS_CHECKBOX flag when calling function CToolBar::SetButtonInfo(…).
1.4. Message Mapping for a Contiguous Range of Command IDs
Contiguous IDs
In the previous sections, we implemented message handler for every control. If we want to handle both WM_COMMAND and UPDATE_COMMAND_UI messages, we need to add two message handlers for each control. Although Class Wizard can help us with function declaration and adding mapping macros, we still need to type in code for every member function. If we have 20 buttons on the tool bar, we may need to implement 40 message handlers. So here the question is, is there a more efficient way to implement message mapping?
The answer is yes. As long as the button IDs are contiguous, we can write a single message handler and direct all the messages to it. To implement this, we need to use two new macros: ON_COMMAND_RANGE and ON_UPDATE_COMMAND_UI_RANGE, which correspond to ON_COMMAND and ON_UPDATE_COMMAND_UI respectively. The formats of the two macros are:
ON_COMMAND_RANGE(start ID, end ID, member function name)
ON_UPDATE_COMMAND_UI_RANGE(start ID, end ID, member function name)
The formats of the corresponding member functions are:
afx_msg void FunctionName(UINT uID);
afx_msg void FunctionName(CCmdUI *pCmdUI);
When we create tool bar resource, the control IDs are generated contiguously according to the sequence of creation. For example, if we first create the blue button, then the green button, the two IDs will have the following relationship:
ID_BUTTON_GREEN = ID_BUTTON_BLUE+1
Modifying an ID
Sometimes we don’t know if the IDs of the tool bar buttons have contiguous values, because most of the time we use only symbolic IDs and seldom care about the actual values of them. If the IDs do not meet our requirement and we still want to use the above message mapping macros, we need to modify the ID values by ourselves.
By default, all the resource IDs are defined in file "resource.h". Although we could open it with a text editor and make changes, there is a better way to do so. First, an ID value could be viewed in the Developer Studio by executing View |
Resource symbols… command. This command will bring up a dialog box that contains all the resource IDs used by the application (Figure 1-3).
If we want to make change to any ID value, first we need to highlight that ID, then click the button labeled "Change…". After that, a "Change Symbol" dialog box will pop up, if the ID is used for more than one purpose, we need to select the resource type in "Used by" window (This happens when this ID is used for both command ID and string ID, in which case the string ID may be used to implement flyby and tool tip. See Figure 1.9). In our sample, there is only one type of resource that uses the button IDs, so we do not need to make any choice.
Now click "View Use" button (Figure 1-4), which will bring up "Toolbar Button Properties" property sheet. Within "General" page, we can change the ID’s value by typing in a new number in the window labeled "ID". For example, if we want to change the value of ID_BUTTON_RED to 32770, we just need to type in an equal sign and a number after the symbolic ID so that this edit window has the
following contents (Figure 1-5):
ID_BUTTON_RED=32770
With this method, we can easily change the values of four resource IDs
(ID_BUTTON_RED, ID_BUTTON_GREEN, ID_BUTTON_BLUE, ID_BUTTON_YELLOW) and make them contiguous. After this we can map all of them to a single member function instead of implementing message handlers for each ID.
Unfortunately, Class Wizard doesn’t do range mappings, so we have to implement it by ourselves. Sample 1.4\Bar demonstrates how to implement this kind of
mapping. It is based upon sample 1.2\Bar, which already contains the default message mapping macros:
BEGIN_MESSAGE_MAP(CBarDoc, CDocument) //{{AFX_MSG_MAP(CBarDoc)
ON_COMMAND(ID_BUTTON_BLUE, OnButtonBlue) ON_COMMAND(ID_BUTTON_GREEN, OnButtonGreen) ON_COMMAND(ID_BUTTON_RED, OnButtonRed)
ON_COMMAND(ID_BUTTON_YELLOW, OnButtonYellow)
ON_UPDATE_COMMAND_UI(ID_BUTTON_BLUE, OnUpdateButtonBlue) ON_UPDATE_COMMAND_UI(ID_BUTTON_GREEN, OnUpdateButtonGreen) ON_UPDATE_COMMAND_UI(ID_BUTTON_RED, OnUpdateButtonRed)
ON_UPDATE_COMMAND_UI(ID_BUTTON_YELLOW, OnUpdateButtonYellow) //}}AFX_MSG_MAP
END_MESSAGE_MAP()
The following lists the necessary steps of changing message mapping from the
original implementation to using contiguous IDs:
1. Delete the above eight message handlers along with the message mapping macros added by the Class Wizard.
2. Declare two new functions that will be used to process WM_COMMAND and
ON_COMMAND_RANGE messages in class CBarDlg as follows:
class CBarDoc : public CDocument {
……
protected:
UINT m_uCurrentBtn;
//{{AFX_MSG(CBarDoc) //}}AFX_MSG
afx_msg void OnButtons(UINT);
afx_msg void OnUpdateButtons(CCmdUI* pCmdUI);
……
}
3. Open file "BarDoc.cpp", find BEGIN_MESSAGE_MAP and END_MESSAGE_MAP
macros of class CBarDoc, add the message mappings as follows:
BEGIN_MESSAGE_MAP(CBarDoc, CDocument) //{{AFX_MSG_MAP(CBarDoc)
//}}AFX_MSG_MAP
ON_COMMAND_RANGE(ID_BUTTON_RED, ID_BUTTON_YELLOW, OnButtons) ON_UPDATE_COMMAND_UI_RANGE(ID_BUTTON_RED, ID_BUTTON_YELLOW, OnUpdateButtons)
END_MESSAGE_MAP()
In the sample application, the values of ID_BUTTON_RED, ID_BUTTON_GREEN,
ID_BUTTON_BLUE, and ID_BUTTON_YELLOW are 32770, 32771, 32772, and 32773 respectively.
4. Implement the two message handlers as follows:
void CBarDoc::OnButtons(UINT uID) {
m_uCurrentBtn=uID;
}
void CBarDoc::OnUpdateButtons(CCmdUI* pCmdUI) {
pCmdUI->SetRadio(pCmdUI->m_nID == m_uCurrentBtn);
}
Please compare the above code with the implementation in section 1.2. When we ask Class Wizard to add message mapping macros, it always adds them between
//{{AFX_MSG comments. Actually, these comments are used by the Class Wizard to locate macros. To distinguish between the work done by ourselves and that done by Class Wizard, we can add the statements outside the two comments.
1.5. Fixing the Size of Tool Bar
Remember in section 1.1, when creating the color bar, we used
CBRS_SIZE_DYNAMIC style:
m_wndToolBar.SetBarStyle (
m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC );
This allows the size of a tool bar to change dynamically. When the bar is floating or docked to top or bottom border of the client area, the buttons will have a horizontal layout. If the bar is docked to left or right border, they will have a vertical layout.
Sometimes we may want to fix the size of the tool bar, and disable the dynamic layout feature. This can be achieved through specifying CBRS_SIZE_FIXED flag instead of CBRS_SIZE_DYNAMIC flag when calling function CToolBar::SetBarStyle(…). By default, the buttons on the tool bar will have a horizontal layout. If we fix the size of the tool bar, its initial layout will not change throughout application’s
lifetime. This will cause the tool bar to take up too much area when it is docked to either left or right border of the client area (Figure 1-6).
Instead of fixing the layout this way, we may want to wrap the tool bar from the second button, so the width and height of the tool bar will be roughly the same at any time (Figure 1-7).
We can call function CToolBar::SetButtonStyle(…) to implement the wrapping. This function has been discussed in section 1.3. However, there we didn’t discuss the flag that can be used to wrap the tool bar from a specific button. This style is
TBBS_WRAPPED, which is not documented in MFC.
Sample 1.5\Bar is based on sample 1.4\Bar that demonstrates this feature. The following shows the changes made to the original CMainFrame::OnCreate(…)
function:
1. Replace CBRS_SIZE_DYNAMIC with CBRS_SIZE_FIXED when setting the tool bar style. The following statement shows this change:
m_wndColorButton.SetBarStyle (
m_wndColorButton.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED );
2. Add the following statement after this:
m_wndColorButton.SetButtonStyle (
1, m_wndColorButton.GetButtonStyle(1) | TBBS_WRAPPED
);
To avoid losing default styles, in the second step, function
CToolBar::GetButtonStyle(…) is first called to retrieve the original styles, which are bit-wise ORed with the new style before calling function CToolBar::SetButtonStyle(…).
1.6. Adding Combo Box to Tool Bar
By default, a tool bar can have only buttons and separators, and all the buttons must have the same size. This prevents us from adding other types of controls to the tool bar. However, by using some special properties of tool bar, we can still manage to add other types of common controls such as combo box to it.
Remember that all controls are actually different type of windows in essence.
When we design a dialog template and add different common controls, we are given an impression that these controls are implemented "Statically". In fact, we can create any type of common controls by calling function CWnd::Create(…) at any time. This member function is supported by all the classes that are derived from
CWnd. We can use it to create a control and put it anywhere on the tool bar.
Dynamically creating window is rarely used in normal programming because in this case the programmer has to calculate the size and position of the window carefully. If we implement this from a dialog template, we can see the visual effect immediately after a new control is added. If we implement this through function calling, we have to compile the project before we can see the final result.
However, because tool bar resource does not let us add controls other than
buttons, dynamic method is the only way we can pursue to implement combo box on the tool bar. The question here is: because the buttons are positioned side by side, where can we put a combo box that will definitely take up a larger area?
To prevent the controls from interfering with each other, one control should not overlap another. So first, we must find an inactive area on the tool bar where we could create the combo box.
On the tool bar, separator is an inactive control. If we click mouse on it, there will be no response. We already know that we can call function
CToolBar::SetButtonInfo(…) to change a button to a separator. Also, when doing this, we can specify the width of the separator by using iImage parameter (when we pass TBBS_SEPARATOR to nStyle parameter, the meaning of iImage parameter becomes the width of the separator).
On top of the separator, we can create any controls using dynamic method.
Sample 1.6\Bar demonstrates how to add combo box to the tool bar. This sample is based upon sample 1.4\Bar. In the new sample, the third button (blue button) is changed to a combo box. The following lists necessary steps of implementing this:
1. Change the blue button to a separator with a width of 150 after the tool bar is created. For this purpose, the following statement is added to function
CMainFrame::OnCreate(…):
m_wndColorButton.SetButtonInfo(2, ID_BUTTON_BLUE, TBBS_SEPARATOR, 150);
Here the first parameter indicates that we want to modify the third button. The second parameter is the blue button’s resource ID. The fourth parameter specifies the width of the separator. If we compile and execute the sample at this point, we will see that the blue button does not exist anymore. Instead, a blank space with width of 150 is added between the third and fourth button. This is the place where we will create the combo box.
2. Use CComboBox to declare a variable m_wndComboBox in class CMainFrame as follows:
class CMainFrame : public CFrameWnd {
……
protected:
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CToolBar m_wndColorButton;
CComboBox m_wndComboBox;
……
}
3. Use the newly declared variable to call function CComboBox::Create(…) in
CMainFrame::OnCreate(…) after the blue button has been changed to separator.
Function CComboBox::Create(…) has four parameters. We must specify combo box’s style, size & position, parent window, along with the control ID in order to call this function. The following is the format of this function:
BOOL CComboBox::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
We can use the first parameter to set the styles of a combo box. A combo box can have different styles, in our sample, we just want to create a very basic drop
down combo box (For other types of combo boxes, see Chapter 5). The following code fragment shows how this function is called within CMainFrame:: OnCreate(…):
……
m_wndColorButton.SetButtonInfo(2, ID_BUTTON_BLUE, TBBS_SEPARATOR, 150);
m_wndColorButton.GetItemRect(2, rect);
rect.bottom=rect.top+150;
if(!m_wndComboBox.Create(WS_CHILD | CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | CBS_HASSTRINGS, rect, &m_wndColorButton, ID_BUTTON_BLUE))
{ return -1;
}
m_wndComboBox.ShowWindow(SW_SHOW);
……
Function CToolBar::GetItemRect(…) is called in the second statement of above code to retrieve the size and position of the separator. After calling this function, the information is stored in variable rect, which is declared using class CRect.
A drop down combo box contains two controls: an edit box and a list box.
Normally the list box is not shown. When the user clicks on the drop down button of the combo box, the list box will be shown. Because the size of the combo box represents its total size when the list box is dropped down (Figure 1-8), we need to extend the vertical dimension of the separator before using it to set the size of the combo box. The third statement of above code sets the rectangle’s vertical size to 150. So when our combo box is dropped down, its width and the height will be roughly the same.
The fourth statement of above code creates the combo box. Here a lot of styles are specified, whose meanings are listed below:
Style Flag Meanings
WS_CHILD Indicates that the window (combo box) being created is a child window. We must specify this flag in order to embed the combo box in another window
CBS_DROPDOWN The combo box has a list control that can be dropped down by clicking its drop down button
CBS_AUTOHSCROLL When the user types text into the edit control, the text will be automatically scrolled to the left if it is too long to be fully displayed
WS_VSCROLL If too many items are added to the list controls and not all of them can be displayed at the same time, a vertical scroll bar will be added to the list control
CBS_HASSTRINGS The items in the list control contains strings
The above styles are the most commonly used ones for a combo box. For details about this control, please refer to chapter 5.
Because the blue button will not be pressed to execute command anymore, we use ID_BUTTON_BLUE as the ID of the combo box. Actually, we can specify any other number so long as it is not used by other controls.
Finally, we must call function CWnd::ShowWindow(…) and pass SW_SHOW flag to it to show any window created dynamically.
By compiling and executing the sample at this point, we will see that the blue button has been changed to a combo box.
1.7. Modifying the Default Styles of Tool Bar
A tool bar with combo box is more useful than a normal one contains only bitmap buttons. However, this feature makes it difficult to implement dynamic layout. If we execute the sample created in the previous section and dock the tool bar to the left or right border, we will see that its layout becomes very awkward (Figure 1-9). This is because the layout feature of CTooBar is designed for a tool bar that contains only buttons with the same size. If we want to change this feature, we need to override the default layout implementation.
Because the combo box does not fit well when the tool bar has a vertical layout, we may want to change it back to the blue button when the tool bar is docked to the left or right border, and change the button back to combo box again when the bar is floated or docked to the top or bottom border.
This can be easily implemented by calling function CToolBar::SetButtonStyle(…) back and forth and setting the button’s style according to the current layout. The
problem here is that we must be notified when the tool bar’s layout is about to change so that we can call the above function before the layout of tool bar actually changes.
When a tool bar’s layout is about to change, function
CToolBar::CalcDynamicLayout(…)will be called to retrieve the dimension of the tool bar. The default implementation of this function calculates the tool bar layout
according to the sizes of the controls contained in the tool bar and tries to arrange them to let the tool bar have a balanced appearance. What we could do here is changing the combo box back to the button when this function is called for
calculating tool bar’s vertical layout size, and changing the button back to combo box when it is called for calculating the horizontal layout size.
We could override function CToolBar::CalcDynamicLayout(…)to make this change. The new function should be implemented as follows:
Overridden CalcDynamicLayout(…) {
Change the combo box to button or vice versa if necessary;
CToolBar::CalcDynamicLayout(…);
}
The default implementation of this function is called after the button information is set correctly. By doing this way, the tool bar can always have the best layout appearance.
We need to know when the tool bar will change from horizontal layout to vertical layout, or vice versa. This can be judged from the parameters passed to function
CControlBar::CalcDynamicLayout(…). Let’s take a look at the function prototype first:
CSize CControlBar::CalcDynamicLayout(int nLength, DWORD dwMode);
The function has two parameters, the second parameter dwMode indicates what kind of size is being retrieved. It can be the combination of many flags, in this section, we need to know only two of them:
Flag Meanings
LM_HORZDOCK The horizontal dock dimension is being retrieved
LM_VERTDOCK The vertical dock dimension is being retrieved
What we need to do in the overridden function is examining the LM_HORZDOCK bit and LM_VERTDOCK bit of dwMode parameter and setting the button information correspondingly.
To override the member function of CToolBar, we must first derive a new class from it, then implement a new version of this function in the newly created class.
Sample 1.7\Bar demonstrates how to change the button’s style dynamically, it is based on sample 1.6\Bar.
First, we need to declare the new class, in the sample, this class is named
First, we need to declare the new class, in the sample, this class is named