100% found this document useful (1 vote)
531 views509 pages

Advanced MFC Programming Guide

This document contains summaries of chapters in a book about advanced MFC programming. Chapter 1 discusses tool bars, dialog bars, adding controls to tool bars, and modifying tool bar styles. Chapter 2 covers menus, right click pop up menus, updating menus dynamically, and owner-draw menus. Chapter 3 presents static and dynamic splitter windows and customizing splitter window appearance and behavior. Chapter 4 describes implementing different types of bitmap buttons. Chapter 5 summarizes using common controls such as spin controls, list boxes, combo boxes, tree controls, and list controls. Chapter 6 summarizes dialog boxes, modeless dialogs, customizing dialog box backgrounds, and adding toolbars and status bars to dialogs. Chapter 7 covers common dialog boxes such

Uploaded by

Ethan Black
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
531 views509 pages

Advanced MFC Programming Guide

This document contains summaries of chapters in a book about advanced MFC programming. Chapter 1 discusses tool bars, dialog bars, adding controls to tool bars, and modifying tool bar styles. Chapter 2 covers menus, right click pop up menus, updating menus dynamically, and owner-draw menus. Chapter 3 presents static and dynamic splitter windows and customizing splitter window appearance and behavior. Chapter 4 describes implementing different types of bitmap buttons. Chapter 5 summarizes using common controls such as spin controls, list boxes, combo boxes, tree controls, and list controls. Chapter 6 summarizes dialog boxes, modeless dialogs, customizing dialog box backgrounds, and adding toolbars and status bars to dialogs. Chapter 7 covers common dialog boxes such

Uploaded by

Ethan Black
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 509

Advanced MFC Programming

Supporting Document
Table of Contents

CHAPTER 1. TOOL BAR AND DIALOG BAR


1.1. Adding an Extra Docking Tool Bar
Default Tool Bar
Tool Bar Implementation
Message Mapping
Adding New Tool Bar Resource
Declaring New Member Variable
Creating New Tool Bar
Command Message Mapping

1.2. Imitating the Behavior of Radio Buttons


Radio Button & Check Box
Sample

1.3. Check Box Implementation


Using Boolean Type Variables
Function CButton::SetButtonInfo(…)

1.4. Message Mapping for a Contiguous Range of Command IDs


Contiguous IDs
Modifying an ID

1.5. Fixing the Size of Tool Bar

1.6. Adding Combo Box to Tool Bar

1.7. Modifying the Default Styles of Tool Bar

1.8. Dialog Bar

1.9. Resizable Dialog Bar


Deriving New Class from CDialogBar
Resizing Edit Control
Dynamic Layout
Using the New Class

1.10. Adding Flyby and Tool Tip

1.11. Toggling Control Bars On/Off

Summary:

CHAPTER 2. MENU
2.1 Message WM_COMMAND and UPDATE_COMMAND_UI
Handling WM_COMMAND Command
Enabling & Disabling a Command
Changing Menu Text

ii
Checking a Menu Item

2.2 Right Click Pop Up Menu


Adding Menu Resource
Trapping Right Button Clicking Event
Using Class CMenu
Implementing Right-Click Menu
Message Mapping for Right-Click Menu

2.3 Updating Menu Dynamically


Menu Struture
Inserting and Removing Menu Item
Sample Implementation

2.4 Bitmap Check

2.5 System Menu and Bitmap Menu Item


System Menu
Bitmap Menu Item
New Functions
Menu Modification
Message Mapping for System Command

2.6 Owner-Draw Menu


Overriding Two Functions
Drawing a Bitmap
Deriving a New Class from CMenu
Overriding Function CMenu::MeasureItem(…)
Overriding Function CMenu::DrawItem(…)
Using the New Class

2.7 Changing the Whole Menu Dynamically

Summary

CHAPTER 3. SPLITTER WINDOW


3.1 Implementing Static Splitter Windows

3.2 Dynamic Splitter Window

3.3 Customizing the Behavior of Split Bar


Splitter Window Layout
Overriding CSplitterWnd::DeleteRow(…) and CSplitterWnd:: DeleteColumn(…)
Using the New Class

3.4 Customizing the Default Appearance


Drawing Functions
Sample

3.5 Splitter Window That Can’t be Resized by Tracking

Summary

iii
CHAPTER 4. BUTTONS
4.1 Bitmap Button: Automatic Method
Button States
Owner-Draw Bitmap Button
Automatic Method
Sample

4.2 Bitmap Check Box and Radio Button: Method 1

4.3 Subclass
Implementing Subclass
Bitmap Button

4.4 Bitmap Check Box and Radio Button: Method 2

4.5 Irregular Shape Bitmap Button


Transparent Background
New Class
Overriding Function CBitmapButton::LoadBitmaps(…)
Overriding Function CBitmapButton::AutoLoad(…)
Overriding Function CBitmapButton::DrawItem(…)
Using Class MCBitmapButton

4.6 Making Button Aware of Mouse Position


Trapping Message WM_LBUTTONUP within Button
User-Defined Message
Sample

4.7 Mouse Sensitive Button


Setting Capture
New Class
Implementation

Summary

CHAPTER 5. COMMON CONTROLS


5.1 Spin Control
Using Spin Control with Edit Box

5.2 Customizing the Properties of Spin Control

5.3 Displaying Text Strings in the Buddy Window

5.4 Bitmap Button Buddy

5.5 Slider
Including Slider Control in the Application
Handling Slider Related Messages

5.6 List Box

5.7 Handling List Box Messages

iv
Trapping Double Clicking Message
Retrieving the Contents of an Item
Message WM_DESTROY

5.8 Combo Box


Implementing Combo Boxes
Handling Messages CBN_CLOSEUP and CBN_SELCHANGE

5.9 Trapping RETURN key strokes for the Combo Box


Problem & Workaround
Function CWnd::PreTranslateMessage(…)
Accessing the Edit Box of a Combo Box

5.10 Implementing Subclass for the Edit Box of a Combo Box


Designing New Classes
Implementing Subclass

5.11 Owner Draw List Box and Combo Box


Owner-Draw Styles
Preparing Bitmaps
Identifying Item Types
Handling Message WM_MEASUREITEM
Handling Message WM_DRAWITEM

5.12 Tree Control


Image List
Adding Nodes
Sample

5.13 Handling Tree Control Messages


Handling TVN_ITEMEXPANDING to Change a Node’s Associated Image
Handling TVN_ENDLABELEDIT to Enable Label Editing
Using the New Class

5.14 Drag-n-Drop
Handling New Messages
New Member Variables and Functions
Node Copy
TVN_BEGINDRAG
WM_MOUSEMOVE
WM_LBUTTONUP

5.15 List Control


LV_COLUMN and LV_ITEM
Sample
Creating Image Lists
Creating Columns
Creating Sub-items
Changing List Style Dynamically

5.16 Tab Control


Using Tab Control
Handling Tab Control Message

5.17 Animate Control and Progress Control

v
Using Animate Control and Progress Control
Timer
Custom Resource
Sample Implementation

Summary:

CHAPTER 6. DIALOG BOX


6.1 Modeless Dialog Box
Modal and Modeless Dialog Box
Sample

6.2 Property Sheet

6.3 Modeless Property Sheet

6.4 Sizes
Initial Size
Dialog Box Unit
Tracking Size and Maximized Size
Sample

6.5 Customizing Dialog Box Background


Background Drawing
Sample
Changing the Background of Common Controls
Stock Objects
Text Foreground and Background

6.6 Resizing the Form View


Coordinates Conversion
Sample

6.7 Tool Tips


Tool Tip Implementation
Sample

6.8 Tool Bar and Status Bar in Dialog Box


Frame Window
Flyby Related Messages
Tool Bar Resource
Status Bar
Adding Control Bars to Dialog Box
Resizing the Client Area to Accommodate Control Bars
Tool Tip and Flyby Implementation
Implementing Control Bars for Dialog Boxes Implemented in SDI or MDI Applications
Problem
Work Around
Overriding CToolBar::OnTimer(…)
An Alternate Solution

Summary:

vi
CHAPTER 7. COMMON DIALOG BOXES
7.1 File Open and Save Dialog Box
Implementing a Standard File Open Dialog Box
Structure OPENFILENAME
File Extension Filter
Retrieving File Names
File Open
File Save

7.2 More Customizations


New Style and Old Style File Dialog Boxes
Other Bits of Flags
Dialog Box Title
Retrieving Multiple Path Names and File Names
Sample

7.3 Selecting Only Directory


New Style
Old Style

7.4 Adding File Preview


Adding Extra Controls
Notification CDN_SELCHANGE
Sample

7.5 Color Dialog Box


Introduction
Initializing Selected Color and Custom Colors
Sample
Full Open

7.6 Custom Dialog Box Template


Custom Dialog Template
Commands Implementation

7.7 Font Dialog Box


Basics
Structure LOGFONT
Retrieving Selected Font
Sample

7.8 Customizing Dialog Box Template

7.9 Modeless Common Dialog Boxes


Tricks
Hook Function
Using MFC Classes together with API Functions
Obtaining Handle
Accessing Member Variable from Static or Global Function
Sample Implementation
Applying Selected Color Instantly

Summary:

vii
CHAPTER 8. DC, PEN, BRUSH AND PALETTE
8.0 Device Context & GDI Objects
Situation
Device Context
GDI Objects
Obtaining DC
Using DC with GDI Objects

8.1 Line
Creating Pen
Drawing Mode
Storing Data
Recording One Line

8.2 Rectangle and Ellipse

8.3 Curve

8.4 Other Shapes

8.5 Flood Fill

8.6 Pattern Brush

8.7 Color Approximation


Palette Device vs. Non-Palette Device
Color Approximation
Sample
Adjusting Display Settings
Results

8.8 Logical Palette


Palette
Color Mapping
Foreground and Background Palette
Creating Logical Palette
Using Logical Palette
Realizing Palette
Macro PALETTEINDEX
Sample

8.9 Monitoring System Palette

8.10 Palette Animation


Flag PC_RESERVED
Animation
Sample

8.11 Find Out Device Capability

Summary:

CHAPTER 9. FONT

viii
9.1 Outputting Text Using Different Fonts

9.2 Enumerating Fonts in the System


Font Types
Enumerating Font Family
Enumerating Font

9.3 Output Text Using CDC::ExtTextOut(…)


Function CDC::ExtTextOut(…)
New Class
Implementing Percentage Bar

9.4 One-Line Text Editor, Step 1: Displaying a Static String

9.5 One Line Text Editor, Step 2: Adding Caret


Caret Functions
Sample

9.6 One Line Text Editor, Step 3: Enabling Input


New Member Functions
Message WM_CHAR

9.7 One Line Text Editor, Step 4: Caret Moving & Cursor Shape
New Functions
Moving Caret Using Keyboard
Moving Caret Using Mouse
Cursor Shape
Handling WM_LBUTTONDOWN to Move Caret

9.8 One Line Text Editor, Step 5: Selection


Highlighting the Selected Text
Setting Selection Indices
Handling Mouse Events

9.9 One Line Text Editor, Step 6: Cut, Copy and Paste
Global Memory
Clipboard Funcitons
Deleting Selected Text
Message Handlers for Cut, Copy Paste Commands

9.10 One Line Text Editor, Step 7: Getting Rid of Flickering


Function CDocument::UpdateAllViews(…)
Defining Hints
Calling Function CDocument::UpdateAllViews(…)
Overriding CView::OnUpdate(…)

Summary:

CHAPTER 10. BITMAP


10.1 BitBlt and StretchBlt
DIB & DDB
Drawing DDB
Creating Memory DC
Retrieving the Dimension of Bitmap Image

ix
Sample 10.1\GDI
Sample 10.1-2\GDI

10.2 Extracting Palette from DIB


DIB Format
DIB Example
Creating DDB from DIB
Loading Resource
Sample

10.3 Loading DIB from File


File Format
Supporting File Type
Loading DIB through Serialization
Creating DDB

10.4 Saving DDB to File


Converting DDB to DIB
New Functions
Using New Functions

10.5 Drawing DIB Directly


New Functions
Modifications Made to Document
Modifications Made to View

10.6 Bitmap Format Conversion: 256-color to 24-bit


Conversion
Current Format
Function Implementation

10.7 Converting 24-bit Format to 256-color Format


Two Cases
Sample

10.8 Pixel Manipulation

10.9 DIB Section: Using Both DIB and DDB


Importance of DDB
DIB Section
New Variables
Cleaning Up
Loading Bitmap & Creating Mask Bitmap
Drawing Bitmap with Transparancy
Adding Background

10.10 Creating Chiseled Effect


Algorithm
Creating Binary Bitmap Image
Raster Operation Mode
Chiselled Effect
Highlighted and Shadowed Colors
New Function
Function CGDIView::LoadBitmap(…) & CGDIView::OnDraw(…)

x
Summary

CHAPTER 11. SAMPLE: SIMPLE PAINT


11.0 Preparation

11.1 Ratio and Grid


Zoom In & Zoom Out
Grid
Pattern Brush and Its Origin

11.2 Color Selection


Color Selection Control
Color Bar
Color Selection
Integrate the Color Bar into the Program

11.3 Simple Drawing


New Tool Bar
New Functions
Mouse Cursor

11.4 Tracker
Implementing Tracker
Moving and Resizing Tracker
Customizing Cursor Shape
New Tool
If Mouse Clicking Doesn’t Hit the Tracker
If Mouse Clicking Hits the Tracker

11.5 Moving the Selected Image


Normalizing Tracker
Moving and Resizing the Selected Image
When Left Button is Up

11.6 Region
Basics
Region Creation
Using Region
Sample

11.7 Path
Basics
Path & Region
Sample 11.7-1\GDI
Obtaining Path
Sample 11.7-2\GDI

11.8 Freeform Selection


Implementation
Scaling Region
New Tool
Resizing and Moving the Freeform Selection

11.9 Cut, Copy and Paste

xi
Clipboard DIB Format
Preparing DIB Data
Cut & Copy
Paste

11.10 Palette Change & Flickering


Problems
Message WM_PALETTECHANGED
Flickering

Summary

CHAPTER 12. SCREEN CAPTURING & PRINTING


12.1 Capturing the Whole Screen
Capture
Converting DDB to DIB
New Command

12.2 Capturing a Specified Window


Picking Up a Window
Dialog Box IDD_DIALOG_SELECT
New Command

12.3 Simple Printing


Mapping Mode
Converting between Logical and Device Units
Implementing Print
Scaling the Image before Printing
Displaying or Printing?
Function CGDIView::OnDraw(…)

12.4 Fixed Scale Printing


Printing Related Functions
Sample 12.4\GDI

12.5 Printing on Separate Pages


Number of required Pages is Known Beforehand
Setting Number of Pages Just Before Printing Starts
Calculating the Number of Pages when the Printing Is Undergoing

12.6 Customizing Print Dialog Box


Customizing Common Controls
Using Custom Dialog Template

Summary

CHAPTER 13. ADDING SPECIAL FEATURES TO APPLICATION


13.1 One Instance Application
Window Creation
Function CWnd::PreCreateWindow(…)
One-Instance Application in MFC
Sample 13.1\Once

xii
13.2 Creating Applications without Using Document/View Structure
How Application, Document and View Are Bound Together
Creating Window
Sample 13.2\Gen
Excluding Classes from Build

13.3 Implementing Multiple Views


Simple View Implementation
Attaching Multiple Views to One Document
Sample 13.3\Chart
Window Origin and View Port Origin
MM_ANISOTROPIC & MM_ISOTROPIC
Window Extents and View Port Extents
Pie Chart Drawing

13.4 Multiple Documents Implementation

13.5 Painting Caption Bar


Non-client Area and Related Messages
Caption Text Area
Sample 13.5\Cap

13.6 Irregular Shape Window


Problem
Style WS_EX_TRANSPARENT
Using Dialog Box
Disabling Default Background Painting
Disabling Non-client Area Painting
Moving the Window with Mouse

13.7 Saving Initial States


Where to Save the Information
Functions Used to Write and Read Information
Format of “.ini” File
Sample 13.7\Ini

13.8 Exchanging User-Defined Messages Among Applications


Registering User Defined Messages
Sample
Finding Window & Sending Message

13.9 Z-Order

13.10 Hook
Hook Installation
System Wide Hook
Variables in DLL
Defining Data Segment
DLL Implementation
Sample 13.6\Hook

13.11 Journal Record and Journal Playback Hooks


Analyzing Events
Playing back the Recorded Events
Using Functions Contained in DLL

xiii
13.12 Memory Sharing Among Processes
Problem with Global Memory
File Mapping
File Mapping Functions
Samples

Summary

CHAPTER 14. VIEWS


14.1 Edit View
Generating the Application
Search Related Commands
Other Commands

14.2 Rich Edit View


Customizing File Open Dialog Box
Customizing “Save As” Dialog Box
Formatting Text

14.3 Simple Explorer, Step 1: Preparation

14.4 Simple Explorer, Step 2: List Drives


Creating Image List
Alternative Ways of Creating Image List
Selecting Image List into Tree Control
Setting Styles of Tree Control
Adding Root Node
Finding out Available Drives in the System

14.5 Simple Explorer, Step 3: Listing Directories


Enumerating Files and Directories
Adding Directory Nodes

14.6 Simple Explorer, Step 4: Displaying Files


Image Lists
Adding Columns
Listing Files
Destroying the Old List
Using Function CExplorerView::ChangeDir()

14.7 Simple Explorer, Step 5: Displaying Registered Icons


Which Icon to Use
Sample

14.8 Simple Explorer, Step 6: Clicking and Double Clicking


Tree Control Messages
Obtaining Full Path
Finding out the Clicked Item
When an Item Is Clicked
When a Node Expands

14.9 Simple Explorer, Step 7: File Sort


Sort Related Functions
Adding Parameters to Items

xiv
Functions Implementing Comparisons
Using Parameter to Find an Item
Comparing Two Items by File Names
Notification LVN_COLUMNCLICK

14.10 Using Form View


New Class and Dialog Template
Implementing New Member Functions
Resizing Tree Control
Mouse Cursor Coordinates
Replacing CDirView with CDirFormView

Summary

CHAPTER 15. DDE


15.1 DDE Registration
DDE Initialization, Uninitialization, Service Registration, Unregistration
DDE Callback Function
Server
Monitoring DDE Activities

15.2 Connecting to Server


DDE Connection: Client Side
DDE Connection: Server Side
Client Implementation
Confirm Connection
DDE Disconnection
Test

15.3 Transaction: Data Request


Data Request Transaction: Client Side
Data Request Transaction: Server Side
Preparing Data
Receiving Data
Samples

15.4 Transaction: Advise


Basics
Initiating Advise Transaction
Advise Transaction Responding
Upon Receiving Advise
Terminating Advise Transaction

15.5 Transactions: Poke and Execute


Poke Transaction: Client Side
Poke Transaction: Server Side
Transaction: Executing Commands

15.6 Asynchronous Transaction


Synchronous vs. Asynchronous
Implementing Asynchronous Transaction
Samples

15.7 Program Manager: A DDE Server

xv
Program Manager

Summary

CHAPTER 16. CONTEXT SENSITIVE HELP


16.1 Context Sensitive Help for Menu Commands
Context Sensitive Help
Context Sensitive Help for Menu Commands
Sample
New Commands
Editing “AfxCore.rtf”
ID Mapping
Help Topics Dialog Box

16.2 Context Sensitive Help for Common Controls


Supporting Context Sensitive Help in Dialog Box
ID Naming Rules
Enabling Context Sensitive Help for Common Controls
Function CWnd::OnHelpInfo(…)
Displaying Help in a Pop up Window

Summary

INDEX

xvi
Chapter 1. Tool Bar and Dialog Bar

Chapter 1

Tool Bar and Dialog Bar

T
ool bar and dialog bar are used extensively in all types of applications. They provide users with a
better way of executing application commands. Generally a tool bar comprises a series of buttons;
each button represents a specific command. A command implemented by the tool bar can be linked
directly to a menu command, in which case the two items share a same command ID. Both menu
and tool bar handle WM_COMMAND message for executing commands. Besides, they also handle
UPDATE_COMMAND_UI message to set the state of a button or a menu item. In fact, this message is very
effective in enabling, disabling, and setting checked or unchecked state for a command.
While tool bar usually contains bitmap buttons, dialog bar can include many other type of controls that
can be used in a dialog box, such as edit control, spin control, etc. Both tool bar and dialog bar can be
implemented either as floated or docked, this gives users more choices in customizing the user interface of
an application.
In MFC, classes that can be used to implement the tool bar and dialog bar are CToolBar and
CDialogBar respectively. Both of them are derived from class CControlBar, which implements bar
creation, command message mapping, control bar docking and floating (both tool bar and dialog bar are
called control bar). Besides the default attributes, class CToolBar further supports bitmap button creation,
automatic size adjustment for different states (docked or floated). A dialog bar can be treated as a dialog
box (There is one difference here: a dialog bar can be either docked or floated, a dialog box does not have
this feature): its implementation is based on a dialog template; all the common controls supported by dialog
box can also be used in a dialog bar; their message mapping implementations are exactly the same.
A standard SDI or MDI application created by Application Wizard will have a default dockable tool
bar. From now on we will discuss how to add extra tool bars and dialog bars, how to implement message
mapping for the controls contained in a control bar, and how to customize their default behavior.

1.1. Adding an Extra Docking Tool Bar

Default Tool Bar


When using Application Wizard to generate SDI or MDI application skeleton, we can ask it to create a
default docking tool bar for us. This can be done in the step 4 (see Figure 1-1). The default tool bar shares
the same ID with the mainframe menu. It has eight bitmap buttons, which are all shortcuts to the menu
commands. After executing this application, we will see a tool bar docked to the top border of the
mainframe window. By using the mouse, we can easily either float it or dock it to other borders.
The tool bar resource can be opened in the Developer Studio. If we click “ResourceView” tab at the
bottom of “Workspace” window, all the resources being used by the application will be listed within the
window. If we asked Application Wizard to add a default tool bar for us, we will see a “Toolbar” resource
node. By expanding this node (clicking on “+” node button or double clicking on the label), we will see all
tool bar resources used by the application. If we double click on “IDR_MAINFRAME” ID, the tool bar bitmap
will be displayed in “Tool Bar Edit” window. We can edit or delete an existing bitmap (each bitmap will be
used to create a bitmap button). We can also add new bitmaps and assign them command IDs. When doing
this, we can either use an existing menu command ID or a newly created one. In the latter case, we need to
implement message mapping afterwards.

1
Chapter 1. Tool Bar and Dialog Bar

The Application Wizard does an excellent job in adding a very powerful tool bar. Nevertheless, as a
programmer, we are kept from knowing what makes all these happen. If we need to make changes to the
default tool bar (for example, we want it to be docked to the bottom border instead of top border at the
beginning), which part of the source code should we modify? Obviously, we need to understand the
essentials of tool bar implementation in order to customize it.
Like menu, generally tool bar is implemented in the mainframe window. When creating a mainframe
menu, we need to prepare a menu resource, use class CMenu to declare a variable, then use it to load the
menu resource. Creating a tool bar takes similar steps: we need to prepare a tool bar resource, use class
CToolBar to declare a variable, which can be used to load the tool bar resource. After the tool bar resource
is loaded successfully, we can call a series of member functions of CToolBar to create the tool bar and
customize its styles.
After creating a standard SDI or MDI application using Application Wizard (with “Docking toolbar”
check box checked in step 4, see Figure 1-1), we will find that a CToolBar type variable is declared in class
CMainFrame:

Check here to let


Application Wizard
add a default
dockable tool bar
for us

Figure 1-1: Let Application Wizard add a default dockable tool bar

class CMainFrame : public CFrameWnd


{
……
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
……
}

The newly declared variable is m_wndToolBar. By tracing this variable, we will find out how the tool
bar is implemented.

Tool Bar Implementation


Tool bar creation occurs in function CMainFrame::OnCreate(…), where the mainframe window is
being created. Tool bar is created after function CFrameWnd::OnCreate(…) is called, which creates the
default mainframe window. Creating a tool bar takes following steps:

1) Call CToolBar::Create(…) to create tool bar window.


1) Call CToolBar::LoadToolBar(…) to load the tool bar resource. We need to pass a tool bar resource ID
to this function.
1) Call CToolBar::SetBarStyle(…) to set the attributes of the tool bar. For example, by setting different
flags, we can let the tool bar have fixed or dynamic size. Besides this, we can also enable tool tips and
flybys for tool bar buttons.

2
Chapter 1. Tool Bar and Dialog Bar

1) To make the tool bar dockable, we need to call function CToolBar::EnableDocking(…) and pass
appropriate flags to it indicating which borders the tool bar could be docked (We can make the tool bar
dockable to all four borders, or only top border, bottom border, etc.)
1) To dock the tool bar, we need to call function CMainFrame::DockControlBar(…). If we have more than
one tool bar or dialog bar, this function should be called for each of them.

We need above five steps to implement a tool bar and set its attributes.

Message Mapping
Since tool bars are used to provide an alternate way of executing commands, we need to implement
message mapping for the controls contained in a tool bar. This will allow the message handlers to be called
automatically as the user clicks a tool bar button. The procedure of implementing message mapping for a
tool bar control is exactly the same with that of a menu item. In MFC, this is done through declaring an
afx_msg type member function and adding macros such as ON_COMMAND and ON_UPDATE_COMMAND_UI.
The message mapping could be implemented in any of the four default classes derived from CWinApp,
CFrameWnd, CView and CDocument. Throughout this book, we will implement most of the message
mappings in document. This is because for document/view structure, document is the center of the
application and should be used to store data. By executing commands within the document, we don’t bother
to obtain data from other classes from time to time.
The following lists necessary steps of implementing message mapping:

1) Declare afx_msg type member functions.


1) Implement these member functions for message handling.
1) Add message mapping macros between BEGIN_MESSAGE_MAP and END_MESSAGE_MAP (which are
generated by Application Wizard). We need to use ON_COMMAND macro to map WM_COMMAND message,
and use ON_UPDATE_COMMAND_UI macro to implement user interface updating. The message mapping
should have the following format:

BEGIN_MESSAGE_MAP(class name, base class name)


//{{AFX_MSG_MAP(class name)
ON_COMMAND(command ID, member function name)
ON_UPDATE_COMMAND_UI(command ID, member function name)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Most of the time message mapping could be implemented through using Class Wizard. In this case we
only need to select a command ID and confirm the name of message handler. Although Class Wizard does
an excellent job in implementing message mapping, sometimes we still need to add it manually because
Class Wizard is not powerful enough to handle all cases.

Adding New Tool Bar Resource


Now that we understand how the default tool bar is implemented, it is easy for us to add extra tool
bars. We can declare CToolBar type variables in class CMainFrame, create tool bars and set their styles in
function CMainFrame::OnCreate(…). Then we can map tool bar command IDs to member functions so that
the commands can be executed by mouse clicking.
Sample 1.1-1\Bar and 1.1-2\Bar demonstrate the above procedure. In the two applications, apart from
the default tool bar, an extra tool bar that has four different buttons is added. Each button is painted with a
different color: red, green, blue and yellow. If we click on one of them, a message box will pop up telling
us the color of the button.
First we need to use Application Wizard to create a standard SDI application named “Bar”, leaving all
the settings as default. This will generate an application with a mainframe menu, a dockable tool bar and a
status bar. The default four class names are CBarApp, CMainFrame, CBarDoc, CBarView.
Before modifying source code to add the second tool bar, we need to prepare the tool bar resource. In
order to do this, we need the following steps to create a tool bar resource that contains four bitmap buttons:

1) Load the application project into Developer Studio.

3
Chapter 1. Tool Bar and Dialog Bar

1) Execute Insert | Resource… command from the menu (or press CTRL+R keys). We will be prompted
to select resource type from a dialog box. If we highlight “toolbar” node and click the button labeled
“New”, a new blank tool bar resource “IDR_TOOLBAR1” will be added to the project. Since default ID
doesn’t provide us much implication, usually we need to modify it so that it can be easily understood.
In the samples, the newly added tool bar resource ID is changed to IDR_COLOR_BUTTON. This can be
implemented by right clicking on “IDR_TOOLBAR1” node in WorkSpace window, and selecting
“Properties” item from the popped up menu. Now a property sheet whose caption is “Toolbar
properties” will pop up, which contains an edit box that allows us to modify the resource ID of the tool
bar.
1) Using the edit tools supplied by the Developer Studio, add four buttons to the tool bar, paint bitmaps
with red, green, blue and yellow colors, change their IDs to ID_BUTTON_RED, ID_BUTTON_GREEN,
ID_BUTTON_BLUE, ID_BUTTON_YELLOW. The tool bar bitmap window could be activated by double
clicking on the tool bar IDs contained in the WorkSpace window, the graphic tools and color can be
picked from “Graphics” and “Colors” windows. If they are not available, we can enable them by
customizing the Developer Studio environment by executing Tools | Customize… command from the
menu.

Declaring New Member Variable


After the resource is ready, we can add code to implement the tool bar.
The first step is to declare a new variable using CToolBar in class CMainFrame:

class CMainFrame : public CFrameWnd


{
……
protected:
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CToolBar m_wndColorButton;
……
};

The new variable is m_wndColorButton, it is added right after other two variables that are used to
implement the default tool bar and status bar.
Next, we can open file “MainFrm.cpp” and go to function CMainFrame::OnCreateClient(…). In
Developer Studio, the easiest way to locate a member function is to right click on the function name in
“WorkSpace” window, then select “Go to Definition” menu item from the popped up menu. Let’s see how
the default tool bar is created:
……
if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1;
}
……

Function CToolBar::Create(…) is called first to create the tool bar window. Then
CToolBar::LoadToolBar(…) is called to load the bitmaps (contained in tool bar resource IDR_MAINFRAME).
When calling function CToolBar::Create(…), we need to specify the parent window of the tool bar by
providing a CWnd type pointer (Generally, a tool bar must be owned by another window). Because this
function is called within the member function of class CMainFrame, we can use “this” as the pointer of
parent window.
The following code fragment shows how the styles of the default tool bar are set:

m_wndToolBar.SetBarStyle
(
m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC
);

Function CToolBar::SetBarStyle(…) sets tool bar’s styles, which can be a combination of different
style flags using bit-wise OR operation. Because we do not want to lose the default styles, first function

4
Chapter 1. Tool Bar and Dialog Bar

CToolBar::GetBarStyle() is called to retrieve the default tool bar styles, then new styles are combined
with the old ones using bit-wise OR operation. In the above code fragment, three new styles are added to
the tool bar: first, flag CBRS_TOOLTIPS will enable tool tips to be displayed when the mouse cursor passes
over a tool bar button and stay there for a few seconds; second, flag CBRS_FLYBY will cause the status bar to
display a flyby about this button (For details of tool tip and flyby, see section 1.11); third, flag
CBRS_SIZE_DYNAMIC will allow the user to dynamically resize the tool bar, if we do not specify this style,
the dimension of the tool bar will be fixed.
The following statement enables a dockable tool bar:

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

Function CToolBar::EnableDocking(…) makes the tool bar dockable. Here, flag CBRS_ALIGN_ANY
indicates that the tool bar may be docked to any of the four boarders of the frame window. We may change
it to CBRS_ALIGN_TOP, CBRS_ALIGN_BOTTOM, CBRS_ALIGN_LEFT, or different combinations of these flags,
whose meanings are self-explanatory.
The dockable tool bar still can’t be docked if the frame window does not support this feature. We must
call function CFrameWnd::EnableDocking(…) to support docking in the frame window and call
CFrameWnd::DockControlBar(…) for each control bar to really dock it. The following code fragment shows
how the two functions are called for the default tool bar:

EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

Like function CCtrlBar::EnableDocking(…), CFrameWnd::EnableDocking(…) uses the same


parameters to specify where a control bar is allowed to be docked.

Creating New Tool Bar


We need to do the same thing for the newly declared variable m_wndColorButton. We can call the
above-mentioned functions to create tool bar window, set its styles, enable docking, and dock it. The
following code fragment shows the updated function CMainFrame::OnCreate(…):

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)


{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1;
}

if (!m_wndColorButton.Create(this) ||
!m_wndColorButton.LoadToolBar(IDR_COLOR_BUTTON))
{

TRACE0("Failed to create toolbar\n");


return -1;
}

if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1;
}

m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndColorButton.SetBarStyle(m_wndColorButton.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_wndColorButton.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

5
Chapter 1. Tool Bar and Dialog Bar

DockControlBar(&m_wndColorButton);
return 0;
}

By compiling and executing the sample application at this point, we can see that the tool bar has been
created. The tool bar can be docked to one of the four borders of the mainframe window or be floated. If we
dock the tool bar to either left or right border, we will see that the tool bar will automatically have a vertical
layout. This feature is supported by class CToolBar, we don’t need to add any line of code in order to have
it.

Command Message Mapping


The new tool bar looks very disappointing. Although we made much effort to add it, none of its
buttons can be used to execute command. This is because we still haven’t implemented any message
handler for the new commands, therefore the buttons will be disabled all the time.
In Windows applications, commands are executed through sending WM_COMMAND message. As the user
clicks a menu command or a tool bar button, the system will send a WM_COMMAND message to the application.
All the Windows messages have two parameters, WPARAM and LPARAM (They are nothing but two integers,
as an application receives a message, it will also receive the message parameters). For WM_COMMAND
message, its WPARAM parameter is used to store the control ID (Command ID, such as ID_BUTTON_RED in
our samples), which can be examined by the application to make appropriate response.
In a general Windows application, message is received and processed by a callback function. When
an application is initialized, it stores the address of the callback function in the system. When a message is
generated, the system uses this address to call the callback function and pass the message as well as the
associated parameters to the application. Besides processing the message, the application can also choose to
pass the message to other applications in the system.
If an application has callback function, we can process message WM_COMMAND within it. A general
callback function for this purpose looks like the following:

LONG APIENTRY CallBackProc


(
HWND hwnd,
UINT message,
DWORD wParam,
LONG lParam)
{
switch (message)
{
case WM_CREATE:
{
……
break;
}
case WM_COMMAND:
{
switch(wParam)
{
case ID_BUTTON_RED:
{
……
}
case ID_BUTTON_GREEN:
{
……
}
……

}
break;
}
{
……
}

There are many types of messages, so parameter message (second parameter of the above function)
could be any of the predefined values. If we want to trap mouse clicking events on the tool bar buttons, we
need to handle WM_COMMAND message. We can see that within the WM_COMMAND case of switch statement in

6
Chapter 1. Tool Bar and Dialog Bar

the above example, parameter wParam is checked (It holds WPARAM parameter of the message). By
comparing it with the IDs of our buttons, we are able to find out which command is being executed by the
user.
MFC handles Windows message in a different way. Because MFC applications are built upon
classes, it is more convenient to handle messages within class member functions instead of one big callback
function. In MFC, this is achieved through message mapping: we can implement the functions that will be
used to execute commands, and use macros defined in MFC to direct the messages into these member
functions.
As mentioned before, doing message mapping generally takes three steps: declaring afx_msg type
member functions, using ON_COMMAND and ON_UPDATE_COMMAND_UI macros to implement mappings, and
implementing the member functions.
For WM_COMMAND type message, the message handling functions do not have any parameters and should
return void type value (For other type of messages, the format of the functions may be different). The
message mapping can be implemented by using ON_COMMAND macro, which has the following format:

ON_COMMAND(control ID, member function name)

For example, if we have a member function OnButtonRed() in class CBarDoc, and we want to map
WM_COMMAND message to this function when the user clicks red button (whose ID is ID_BUTTON_RED), we can
implement message mapping as follows:

BEGIN_MESSAGE_MAP(CBarDoc, CDocument)
ON_COMMAND(ID_BUTTON_RED, OnButtonRed)
END_MESSAGE_MAP()

Message mapping macros must be done between BEGIN_MESSAGE_MAP and END_MESSAGE_MAP. Please
note that if we want member functions of other classes to receive the same message, we must implement
message mapping for each class separately.
Class Wizard is designed to help us deal with message mapping. It provides us with a way of adding
message handlers very easily: all we need to do is picking up messages and confirming the names of the
member functions. The following descriptions list necessary steps of adding a message handler for button
ID_BUTTON_RED in class CBarDoc through using Class Wizard (also see Figure 1-2):

1) In the Developer Studio, execute command View | ClassWizard… (or press CTRL+W keys).
1) From the popped up property sheet, click “Message Maps” tab (if the current page is not “Message
Maps”).
1) From “Class name” combo box, select “CBarDoc” if it is not the default class name (If the file being
edited is “BarDoc.cpp”, the default class name should be “CBarDoc”).
4) From “Object Ids” window, highlight “ID_BUTTON_RED”.
1) From “Messages” window, highlight “COMMAND”.
1) Click “Add Function” button.
1) From the popped up dialog box, confirm the function name that will be used as the message handler
(we may change the name according to our preference).
1) The function will be added to window “Member functions”. Now we can repeat steps 4 through 7 to
add message handlers for other IDs. When finished, we need to click “OK” button.

After dismissing the Class Wizard, the functions just added will be shown in the Developer Studio. By
default, the message handlers are empty at the beginning, and we can add anything we want. For example,
if we want a message box to pop up telling the color of the button when the user clicks it with mouse, we
may implement the ID_BUTTON_RED message hander as follows:
void CBarDoc::OnButtonRed()
{
AfxMessageBox("Red");
}

7
Chapter 1. Tool Bar and Dialog Bar

Step 3: Select
CBarDoc class

Step 2: Click on
Message Maps
Step 6: Click
tab
Add Function
button

Step 5: Select
Step 4: Highlight COMMAND
ID_BUTTON_RED

When finished,
click OK button

Figure 1-2. Implement message mapping through using Class Wizard

Similarly, we can write message handlers for other three buttons. In sample application 1.1-2\Bar,
message handlers for all the four buttons on tool bar IDR_COLOR_BUTTON are implemented. If the user clicks
any of the four buttons, a message box will pop up telling its color.

1.2. Imitating the Behavior of Radio Buttons


The buttons can be used more than just executing commands. Actually, they can serve other purposes
such as status indication. We can use the buttons contained in a tool bar to imitate other two types of
buttons used in dialog box: check box and radio button. When a check box is clicked, it will toggle between
Checked and Unchecked states. For a radio button, only the unchecked button will toggle to checked state
after being clicked. Also, at any time, only one radio button within a group can be checked.

Radio Button & Check Box


Although check box, radio button and push button look very differently from one another, they are
essentially same type of controls. All of them can handle command messages, and their status can be set
using the same function. The only difference among them is how they behave after being clicked by mouse.
For a push button, after being clicked, it will go to checked state, as the mouse releases, it will
automatically return to its normal state; for a check box, it toggles between checked and unchecked states
after being clicked (it does not respond to mouse button release events); for a radio button, checking any
button in a group will uncheck the previously checked button, so that at any time, only one button within a
group could be checked.
On a tool bar, implementing normal check box and radio button does not make much sense, so they are
not included as default features. If we want to add these controls, we can use dialog bar rather than tool bar.
However, if we want the features of radio button and check box, we can use normal push buttons to imitate
the behaviors of two types of controls.
The key of letting a push button behave like radio button and check box is to find a way of setting the
states of buttons contained in the tool bar. In MFC, the states of tool bar buttons are managed in the same
way with that of menu items. We can set a button’s state by trapping user-interface update command
message (UPDATE_COMMAND_UI), then calling member functions of class CCmdUI to change a button’s state.
In order to map this massage to a member function, we need to use ON_UPDATE_COMMAND_UI macro. The
following is the format of this message mapping:

ON_UPDATE_COMMAND_UI(command ID, member function name)

8
Chapter 1. Tool Bar and Dialog Bar

The format of the corresponding member function is:

afx_msg void FunctionName(CCmdUI *pCmdUI);

The function has only one parameter, which is the pointer to a CCmdUI object. Class CCmdUI handles
user-interface updating for tool bar buttons and menu items. Some most commonly used member functions
are listed in the following table:

Function Usage
CCmdUI::Enable(…) Enable or disable a control
CCmdUI::SetCheck(… Set or remove the check state of a control
)
CCmdUI::SetRadio(… Set check state of a control, remove check state of all other controls in the
)
group

From time to time, the operating system will send user-interface update command messages to the
application, if there exists macros implementing the above-mentioned message mapping for any control
contained in the tool bar, the control’s state can be set within the corresponding message handler.
For a concrete example, if we want to disable ID_BUTTON_RED button under certain situations, we can
declare a member function OnUpdateButtonRed(…) in class CBarDoc as follows (of course, we can also
handle this message in other three classes):

class CBarDoc : public CDocument


{
……
protected:
afx_msg void OnUpdateButtonRed(CCmdUI *pCmdUI);
……
};

The message mapping can be implemented in file “BarDoc.cpp”:

BEGIN_MESSAGE_MAP(CBarDoc, CDocument)
ON_UPDATE_COMMAND_UI(ID_BUTTON_RED, OnUpdateButtonRed)
END_MESSAGE_MAP()

The member function can be implemented as follows:

void CBarDoc::OnUpdateButtonRed(CCmdUI *pCmdUI)


{
if(under certain situations)pCmdUI->Enable(FALSE);
else pCmdUI->Enable(TRUE);
}

Usually we use a Boolean type variable as the flag, which represents “certain situations” in the above
if statement. We can toggle this flag in other functions, this will cause the button state to be changed
automatically. By doing this, the button’s state is synchronized with the variable.
To set check state for a button, all we need to do is calling function CCmdUI::SetCheck(…) instead of
CCmdUI::Enable(…) in the message handler.

Sample
Sample 1.2\Bar demonstrates how to make the four color buttons behave like radio buttons. At any
time, one and only one button will be set to checked state (it will recess and give the user an impression
that it is being held down).
To implement this feature, a member variable m_uCurrentBtn is declared in class CBarDoc. The value
of this variable could be set to any ID of the four buttons in the member functions (other values are not
allowed). In the user-interface update command message handler of each button, we check if the value of
m_nCurrentBtn is the same with the corresponding button’s ID. If so, we need to set check for this button,
otherwise, we remove its check.

9
Chapter 1. Tool Bar and Dialog Bar

The following lists the steps of how to implement these message handlers:

1) Open file “BarDoc.h”, declare a protected member variable m_uCurrentBtn in class CBarDoc:

class CBarDoc : public CDocument


{
……
protected:
UINT m_uCurrentBtn;
……
};

2) Go to file “BarDoc.cpp”, in CBarDoc’s constructor, initialize m_uCurrentBtn red button’s resource ID:

CBarDoc::CBarDoc()
{
m_uCurrentBtn=ID_BUTTON_RED;
}

This step is necessary because we want one of the buttons to be checked at the beginning.
3) Implement UPDATE_COMMAND_UI message mapping for four button IDs. This is almost the same with
adding ON_COMMAND macros, which could be done through using Class Wizard. The only difference
between two implementations is that they select different message types from “Message” window (see
step 5 previous section). Here we should select “UPDATE_COMMAND_UI” instead of
“COMMAND”.
1) Implement the four message handlers as follows:

void CBarDoc::OnUpdateButtonBlue(CCmdUI* pCmdUI)


{
pCmdUI->SetCheck(pCmdUI->m_nID == m_uCurrentBtn);
}

void CBarDoc::OnUpdateButtonGreen(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(pCmdUI->m_nID == m_uCurrentBtn);
}

void CBarDoc::OnUpdateButtonRed(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(pCmdUI->m_nID == m_uCurrentBtn);
}

void CBarDoc::OnUpdateButtonYellow(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(pCmdUI->m_nID == m_uCurrentBtn);
}

One thing to mention here is that CCmdUI has a public member variable m_nID, which stores the ID of
the control that is about to be updated. We can compare it with variable CBarDoc::m_uCurrentBtn and set
the appropriate state of the control.
With the above implementation, the red button will be checked from the beginning. We need to change
the value of variable m_uCurrentBtn in order to check another button. This should happen when the user
clicks on any of the four buttons, which will cause the application to receive a WM_COMMAND message. In the
sample, this will cause the message handlers CBarDoc::OnButtonRed(), CBarDoc::OnButtonBlue()… to be
called. Within these member functions, we can change the value of m_uCurrentBtn to the coresponding
command ID in order to check that button:

void CBarDoc::OnButtonBlue()
{
m_uCurrentBtn=ID_BUTTON_BLUE;
}

void CBarDoc::OnButtonGreen()
{
m_uCurrentBtn=ID_BUTTON_GREEN;
}

void CBarDoc::OnButtonRed()
{

10
Chapter 1. Tool Bar and Dialog Bar

m_uCurrentBtn=ID_BUTTON_RED;
}

void CBarDoc::OnButtonYellow()
{
m_uCurrentBtn=ID_BUTTON_YELLOW;
}

The message box implementation is removed here. By executing the sample application and clicking
on any of the four color buttons, we will see that at any time, one and only one button will be in the
checked state.

1.3. Check Box Implementation

Using Boolean Type Variables


Using the method discussed in section 1.2, it is very easy to implement check-box-like buttons. We can
declare Boolean type variables for each control, and toggle their values between FALSE and TRUE in the
WM_COMMAND message handlers. Within UPDATE_COMMAND_UI message handlers, we can set check for any
button according to the corresponding value of the Boolean type variable.
Sample 1.3-1\Bar is implemented in this way. In this sample, first variable m_uCurrentBtn is removed,
then WM_COMMAND and UPDATE_COMMAND_UI message handlers are made empty. Four new Boolean type
variables are declared in class CBarDoc:

class CBarDoc : public CDocument


{
……
protected:
BOOL m_bBtnRed;
BOOL m_bBtnGreen;
BOOL m_bBtnBlue;
BOOL m_bBtnYellow;
……
}

Their values are initialized in the constructor:

CBarDoc::CBarDoc()
{
m_bBtnRed=FALSE;
m_bBtnGreen=FALSE;
m_bBtnBlue=FALSE;
m_bBtnYellow=FALSE;
}

Two types of message handlers (altogether eight member functions) are rewritten. The following
shows the implementation of two member functions for button ID_BUTTON_RED:

void CBarDoc::OnButtonRed()
{
m_bBtnRed=!m_bBtnRed;
}

void CBarDoc::OnUpdateButtonRed(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(m_bBtnRed);
}

If we execute the application at this point, we will see that the four color buttons behave like check
boxes.

11
Chapter 1. Tool Bar and Dialog Bar

Function CButton::SetButtonInfo(…)
Although this is a simple way to implement “check box” buttons, sometimes it is not efficient. Suppose
we have ten buttons that we expect to behave like check boxes, for every button we need to add a Boolean
type variable and implement a UPDATE_COMMAND_UI message handler. Although this is nothing difficult, it is
not the most efficient way of doing it.
Class CToolBar has a member function that can be used to set the button styles. The function allows us
to set button as a push button, separator, check box, or the start of a group of check boxes. We can also use
it to associate an image with a button contained in the tool bar. The following is the format of this function:

void CToolBar::SetButtonInfo(int nIndex, UINT nID, UINT nStyle, int iImage);

To use this function, we need to provide the information about the button, the style flags, and the
image information. Parameter nIndex indicates which button we are gong to customize. It is a zero-based
index, and button 0 is the left most button or separator on the tool bar (a separator is also considered a
button). Parameter nID specifies which command ID we want to associate with this button. Parameter
nStyle could be one of the following values, which indicates button’s style:

Flag Meaning
TBBS_BUTTON push button
TBBS_SEPARATOR separator
TBBS_CHECKBOX check box
TBBS_GROUP start of a group
TBBS_CHECKGROUP start of a check box group

The last parameter iImage indicates which image will be used to create the bitmap button. This is also
a zero-based number, which indicates the image index of the tool bar resource. In our case, the tool bar
resource contains four images, which are simply painted red, green, blue and yellow. The images are
indexed according to their sequence, which means the red image is image 0, the green image is image 1,
and so on.
When we create a tool bar resource, it seems that a button’s command ID and the associated image are
fixed from the beginning. Actually both of them can be modified through calling the above function. We
can assign any command ID and image to any button. Also, we can change a button to a separator. In a
normal application, there is no need to call this function, so the button’s command ID and image are set
according to the tool bar resource.
We have no intention of changing the default arrangement of the buttons. What we need to do here is
modifying the button’s style, which is set to TBBS_BUTTON by default. Sample 1.3-2\Bar demonstrates how
to modify this style. It is based on sample 1.3-1\Bar.
To implement the new sample, first we need to delete four old UPDATE_COMMAND_UI message handlers.
This can be done through using Class Wizard, which will delete the declaration of message handlers and
the message mapping macros. We need to remove the implementation of the functions by ourselves.
We can set the button’s style after the tool bar is created. This can be implemented in function
CMainFrame::OnCreate(…). The following portion of this function shows what is added in the sample
application:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)


{
……
m_wndColorButton.SetButtonInfo(0, ID_BUTTON_RED, TBBS_CHECKBOX, 0);
m_wndColorButton.SetButtonInfo(1, ID_BUTTON_GREEN, TBBS_CHECKBOX, 1);
m_wndColorButton.SetButtonInfo(2, ID_BUTTON_BLUE, TBBS_CHECKBOX, 2);
m_wndColorButton.SetButtonInfo(3, ID_BUTTON_YELLOW, TBBS_CHECKBOX, 3);
……
}

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):

12
Chapter 1. Tool Bar and Dialog Bar

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

13
Chapter 1. Tool Bar and Dialog Bar

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

R e so u rc e ID

ID v a lu e

F ig u r e 1 -3 . V ie w re so u rc e ID s a n d th e ir v a lu e s

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.
1) 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)

14
Chapter 1. Tool Bar and Dialog Bar

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.

Figure 1-4. Click “View Use” button to change the ID value

Figure 1-5. Change the value of resource ID

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(…).

15
Chapter 1. Tool Bar and Dialog Bar

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

Figure 1-6: Docking a tool bar with horizontal layout to the


left or right border will take up too much window area

Figure 1-7. Fixing the layout this way will let a tool bar with
fixed size take less area when it is docked to any border

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
);

1) Add the following statement after this:

m_wndColorButton.SetButtonStyle
(

16
Chapter 1. Tool Bar and Dialog Bar

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;
……
}

17
Chapter 1. Tool Bar and Dialog Bar

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.

18
Chapter 1. Tool Bar and Dialog Bar

Figure 1-8. The size of a combo box is the


total size when the list box is dropped down

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.

19
Chapter 1. Tool Bar and Dialog Bar

Figure 1-9. The default vertical layout of the


custom tool bar looks awkward

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 CColorBar:

class CColorBar : public CToolBar


{
public:
CColorBar();
BOOL AddComboBox();
BOOL ShowComboBox();
BOOL HideComboBox();
virtual ~CColorBar();
virtual CSize CalcDynamicLayout(int , DWORD);
//{{AFX_VIRTUAL(CColorBar)
//}}AFX_VIRTUAL

protected:
CComboBox m_wndComboBox;

//{{AFX_MSG(CColorBar)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

Instead of declaring a CComboBox type variable in class CMainFrame, here we implement the declaration
in the derived class. This is a better implementation because the combo box should be made the child
window of the tool bar. By embedding the variable here, we can make it a protected variable so that it is not
accessible from outside the class.

20
Chapter 1. Tool Bar and Dialog Bar

Three functions are added to change the tool bar’s style. Function CColorBar::AddComboBox()
changes the blue button to a separator and creates the combo box window:

BOOL CColorBar::AddComboBox()
{
CRect rect;

GetItemRect(2, rect);
rect.bottom=rect.top+150;
if
(
!m_wndComboBox.Create
(
WS_CHILD | CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | CBS_HASSTRINGS,
rect,
this,
ID_BUTTON_BLUE
)
)return FALSE;
else return TRUE;
}

This is the same with what we did in function CMainFrame::OnCreate(…) in the previous section. The
only difference is that when creating the combo box within the member function of CMainFrame, the combo
box’s parent window needs to be set to m_wndColorButton. Here, since the combo box variable is
embedded in the parent window’s class, we need to use this pointer to indicate the parent window.
Function CColorBar::ShowComboBox() and CColorBar::HideComboBox() change the combo box to
the button and vice versa. They should be called just before the default layout is about to be carried out:

BOOL CColorBar::ShowComboBox()
{
CRect rect;
SetButtonInfo(2, ID_BUTTON_BLUE, TBBS_SEPARATOR, 150);
if(m_wndComboBox.m_hWnd != NULL)
{
m_wndComboBox.ShowWindow(SW_SHOW);
}

return TRUE;
}

BOOL CColorBar::HideComboBox()
{
SetButtonInfo(2, ID_BUTTON_BLUE, TBBS_BUTTON, 2);
if(m_wndComboBox.m_hWnd != NULL)m_wndComboBox.ShowWindow(SW_HIDE);

return TRUE;
}

Finally, function CalcDynamicLayout(…) is overridden as follows:

CSize CColorBar::CalcDynamicLayout(int nLength, DWORD dwMode)


{
if(dwMode & LM_HORZDOCK)ShowComboBox();
else HideComboBox();

return CToolBar::CalcDynamicLayout(nLength, dwMode);


}

Before calling the base class version of this function, we examine LM_HORZDOCK bit of dwMode
parameter, if it is set, we call function CColorBar::ShowComboBox() to change the button to the combo
box. If not, we call function CColorBar::HideComboBox() to change the combo box back to the default
button.
It is relatively easy to use this class: we just need to include the header file of CColorBar class in file
“MainFrm.h”, then change the prototype of m_wndColorBar from CToolBar to CColorBar. Because the
combo box is embedded in CColorBar class, we need to remove variable wndComboBox declared in the
previous section. In function CMainFrame::OnCreate(), instead of creating the combo box by ourselves,
we can just call the member function of CColorBar. Here is how the combo box is created using this new
method:

21
Chapter 1. Tool Bar and Dialog Bar

……
m_wndColorButton.AddComboBox();
m_wndColorButton.ShowComboBox();
……

We can see that the original five statements have been reduced to two statements.
Now we can compile and execute the sample again to see the behavior of the combo box.

1.8. Dialog Bar


As we have noticed, the limitation of the tool bar is that when we design a tool bar from resource, only
bitmap buttons with the same size can be included. If we try to modify the size of one bitmap button, the
size of all other buttons will be automatically adjusted. If we want to include controls other than buttons,
we need to write code to add them dynamically.
If we want to implement a control bar that contains other types of common controls starting from
resource editing, we need to use another type of control bar: dialog bar. In MFC, the class that can be used
to implement this type of control bar is CDialogBar.
Like tool bar, dialog bar is also derived from control bar. Both of them share some common features.
For example, both types of control bars can be either docked or floated, and they all support tool tip and
flyby implementation. The difference between tool bar and dialog bar is that they are designed to contain
different types of controls: while tool bar is more suitable for containing a row of bitmap buttons with the
same size, dialog bar can be implemented to contain any type of controls that can be used in a dialog box.
Implementing dialog bar is similar to that of dialog box. The first step is to design a dialog-template
resource. We can add buttons, edit boxes, combo boxes, even animate controls to a dialog bar.
Dialog bar shares the same type of resource with dialog box. So when starting to create resource for
dialog bar, we first need to add a “dialog” type resource to the application (In order to do this, we can
execute Insert | Resource… command, then select “Dialog” from the popped up “Insert Resource” dialog
box). When specifying the dialog properties, we must set “child” and “no border” styles. This can be
customized in “Dialog Properties” property sheet (Figure 1-10).
Sample 1.8\Bar demonstrates how to use dialog bar, it is based on sample 1.7\Bar. In this sample,
besides the extra tool bar added in the previous sections, a new dialog bar that contains some common
controls is added to the application. The resource ID of this dialog bar is ID_DIALOG_BAR. It contains two
push buttons (ID_BUTTON_A, ID_BUTTON_B), one edit box (IDC_EDIT) and one combo box (IDC_COMBO).
Also, there are other three static text controls (Figure 1-11).
Adding a dialog bar to the application is similar to that of a tool bar. First we need to declare a variable
in class CMainFrame. Then within function CMainFrame::OnCreate(…), we can call the member functions
of CDialogBar and CFrameWnd to create the dialog bar, set its styles and dock it.
The following lists necessary steps of adding this dialog bar:

1) Use CDialogBar to declare a new variable m_wndDialogBar in CMainFrame class:

class CMainFrame : public CFrameWnd


{
……
protected:
……
CDialogBar m_wndDialogBar;
……
};

2) In function CMainFrame::Create(…), call CDialogBar::Create(…) to create the dialog bar window.


When doing this, we need to provide the pointer of its parent window, the dialog template ID, styles
and the control ID of the dialog bar. Here, the control ID could be a different number from its template
ID, so long as it is not being occupied by other resources. The following code fragment shows how this
function is called in the sample:

……
if

22
Chapter 1. Tool Bar and Dialog Bar

(
!m_wndDialogBar.Create
(
this,
IDD_DIALOG_COLORBAR,
CBRS_BOTTOM | CBRS_TOOLTIPS | CBRS_FLYBY,
IDD_DIALOG_COLORBAR
)
)
{
TRACE0("Failed to create toolbar\n");
return -1;
}
……

Click here to
set styles

Change window
style to “Child”
Don’t let a dialog
bar have border
Figure 1-10. Set dialog bar styles

Combo box
IDC_COMBO
Edit box
Button IDC_BUTTON_A and IDC_EDIT
IDC_BUTTON_B

Figure 1-11. Dialog bar template resource

3) Enable docking by calling function CDialogBar::EnableDocking(…), dock the dialog bar by calling
function CMainFrame::DockControlBar(…):

……
m_wndDialogBar.EnableDocking(CBRS_ALIGN_ANY);
……
DockControlBar(&m_wndDialogBar);
……

Because class CDialogBar is derived from CControlBar, in step 3, when we call function
CDialogBar::EnableDocking(…) to enable docking for the dialog bar, we are actually calling function
CControlBar::EnableDocking(…). This is the same with that of tool bar. Because of this, both tool bar and
dialog bar have the same docking properties.
By compiling and executing the sample at this point, we can see that the dialog bar is implemented,
which is docked to the bottom border at the beginning. We can drag the dialog bar and dock it to other
borders. As we do this, we may notice the difference between dialog bar and tool bar: while the size of the
tool bar will be automatically adjusted when it is docked differently (horizontally or vertically), the size of
dialog will not change under any condition. The reason for this is that a dialog bar usually contains
irregular controls, so it is relatively difficult to adjust its size automatically. By default, dynamic size
adjustment is not supported by class CDialogBar. If we want our dialog bar to support this feature, we need
to override function CDialogBar::CalcDynamicLayout(…).
To prevent a dialog bar from taking up too much area when it is docked to left or right border, we can
put restriction on the dialog bar so that it can only be docked to top or bottom border. To implement this,

23
Chapter 1. Tool Bar and Dialog Bar

we can change the style flag from CBRS_ALIGN_ANY to CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM when
calling function CDialogBar::EnableDocking(…) in step 3 discussed above.

1.9. Resizable Dialog Bar


Because dialog bar can contain more powerful controls than tool bar, we could use it to implement
control bars with more flexibility. To make user-friendly interface, sometimes we may really want to
dynamically change a dialog bar’s size.
Class CControlBar has two member functions dealing with control bar layout: CControlBar::
CalcFixedLayout(…) and CControlBar::CalcDynamicLayout(…). The first function returns the fixed size
of a control bar, which is determined from the resource of a control bar. The second function is designed
for implementing dynamic layout, however in class CControlBar, this function does noting but calling
CControlBar::CalcFixedLayout(…). So actually CControlBar does not have dynamic layout feature.
Class CToolBar overrides function CControlBar::CalcDynamicLayout(…), which adjusts the size of
control bar according to its current docking state and the size of buttons. Whenever its docking state is
changed, this function will be called to obtain the information of a tool bar before its layout is changed
accordingly. With this implementation, a tool bar can always have a balanced appearance.
Unlike CToolBar, CDialogBar does not override this function, so when the docking state of a dialog
bar is changed, the default CControlBar::CalcDynamicLayout(…) is called, which of course, will not
perform any dynamic layout for the dialog bar. If we want to add any dynamic layout feature, we must
override this member function.
Sample 1.9\Bar demonstrates how to build a dialog bar that can be resized dynamically. The
application is a standard SDI application generated from Application Wizard, with four classes named
CBarApp, CMainFrame, CBarDoc, and CBarView. In the sample, a dialog bar with an edit control is added to
the application. This dialog bar will support dynamic layout feature: when we float or dock it to different
borders of the mainframe window, the size of the dialog bar will change accordingly.

Deriving New Class from CDialogBar


To implement dialog bar, first we need to add a dialog-template resource. In the sample, the ID of the
new resource is IDD_DIALOGBAR, which contains an edit box. The ID of this edit box is IDC_EDIT, it
supports multiple-line editing. To enable this style, first we can open “Edit Properties” property sheet of the
edit control (To invoke this property sheet, when editing the dialog template, we can double click on the
edit control or right click on it and move to “Properties” menu item), then we need to click “Styles” tab and
check “Multiline” check box.
To override the default function, first we need to derive a new class from CDialogBar. The following
code fragment shows the derived class MCDialogBar, which resides in file “MDlgBar.h”:

class MCDialogBar : public CDialogBar


{
public:
MCDialogBar();
//{{AFX_DATA(MCDialogBar)
//}}AFX_DATA
virtual CSize CalcDynamicLayout(int, DWORD);
//{{AFX_VIRTUAL(MCDialogBar)
//}}AFX_VIRTUAL

protected:
//{{AFX_MSG(MCDialogBar)
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

Besides constructor, the only things included in this class are two functions. As we already know,
MCDialogBar::CalcDynamicLayout(…) will be used to support dynamic layout. Another afx_msg type
function MCDialogBar::OnSize(…) is a message handler, which will be used to resize the edit control

24
Chapter 1. Tool Bar and Dialog Bar

contained in the dialog bar. By doing this, we can see that whenever the size of the dialog bar is adjusted,
the size of the edit box will also change accordingly. This will let the edit box fit well within the dialog bar.
The new class can be added by opening new files (“.h” and “.cpp” files) and typing in the new class
and function implementations. Then we can execute Project | Add To Project | Files… command to add
the newly created files to the project. However, if we do so, we can not use Class Wizard to add member
variables and functions to the class. In this case, we need to implement message mapping manually. If this
is our choice, we must make sure that DECLARE_MESSAGE_MAP() macro is included in the class, and
BEGIN_MESSAGE_MAP, END_MESSAGE_MAP macros are included in the implementation file (“.cpp” file) so that
the class will support message mapping.
We can also use Class Wizard to add new class. In order to do this, after invoking the Class Wizard,
we can click button labeled “Add Class…” then select “New…” from the popup menu. This will invoke a
dialog box that lets us add a new class to the project. We can type in the new class name, select the header
and implementation file names, and designate base class name. Unfortunately, CDialogBar is not in the list
of base classes. A workaround is that we can select CDialog as the base class, after the class is generated,
delete the unnecessary functions, and change all CDialog keywords to CDialogBar in both header and
implementation files.

Resizing Edit Control


The edit control should be resized whenever the size of its parent window changes. In order to do this,
we can trap WM_SIZE message, which is sent to a window when its size is about to change. We need to
declare an afx_msg type member function as the message handler, and implement the message mapping
using ON_WM_SIZE macro. The message handler of WM_SIZE should have the following format:

afx_msg void OnSize(UINT nType, int cx, int cy);

Here nType indicates how the window’s size will be changed (is it maximized, minimized…), cx and
cyindicate the new window size.
It is not very difficult to add message mapping macro, we can either add it manually, or ask Class
Wizard to do it for us:

BEGIN_MESSAGE_MAP(MCDialogBar, CDialogBar)
//{{AFX_MSG_MAP(MCDialogBar)
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Please note that we do not need to specify function name when using macro ON_WM_SIZE. Instead, we
must use OnSize to name the message handler of WM_SIZE.
To change a window’s size, we can call function CWnd::MoveWindow(…):

void CWnd::MoveWindow(int x, int y, int nWidth, int nHeight, BOOL bRepaint=TRUE);

We need to provide new position and size in order to call this function. Because the function is a
member of CWnd, we need to first obtain a pointer to the edit window then use it to call this function.
For controls contained in a dialog box, their window pointers can be obtained by calling function
CWnd::GetDlgItem(…). This function requires a valid control ID:

CWnd *CWnd::GetDlgItem(int nID);

The function returns a CWnd type pointer. With this pointer, we can call any member functions of CWnd
to retrieve its information or change the properties of the control.
Because we want to set the edit control’s size according to the parent window’s size (dialog bar), we
need to find a way of retrieving a window’s dimension. This can be implemented by calling another
member function of CWnd:

void CWnd::GetClientRect(LPRECT lpRect);

25
Chapter 1. Tool Bar and Dialog Bar

It is very easy to use this function. We can just declare a CRect type variable, and pass its pointer to the
above function when calling it. After this, the position and size of the window will be stored in the variable.
The following shows how message WM_SIZE is handled in the sample:

void MCDialogBar::OnSize(UINT nType, int cx, int cy)


{
CWnd *ptrWnd;
CRect rectWnd;

CDialogBar::OnSize(nType, cx, cy);

GetClientRect(rectWnd);
ptrWnd=GetDlgItem(IDC_EDIT);
if(ptrWnd != NULL)
{
ptrWnd->MoveWindow
(
rectWnd.left+15,
rectWnd.top+15,
rectWnd.Width()-30,
rectWnd.Height()-30
);
}
}

We can not use parameter cx and cy to resize the edit control directly because after the dialog bar gets
this information, its layout may change again. The ultimate dimension of the dialog bar depends on both cx,
cy and the layout algorithm. So before adjusting the size of edit control, we have to call
CDialogBar::OnSize(…) first to let the dialog bar adjust its own size, then call CWnd::GetClientRect(…)
to retrieve the final dimension of the dialog bar.
The rest part of this function can be easily understood: we first obtain a window pointer to the edit
control and store it in pointer ptrWnd, then use it to call function CWnd::MoveWindow(…) to resize the edit
control.

Dynamic Layout
Now we need to implement function MCDialogBar::CalcDynamicLayout(…). Like what we did in
section 1.7, here we need to return a custom layout size when the function is called for retrieving either
horizontal or vertical docking size. The following is our layout algorithm: when the bar is docked
horizontally, we set its width to the horizontal size of the mainframe window’s client area, and set its height
to the dialog bar’s floating vertical size; when it is docked vertically, we set its height to the vertical size of
the mainframe window’s client area, and set its width to the dialog bar’s floating horizontal size.
Parameter dwMode of this function is used to tell what types of dimension is be inquired. If either
LM_VERTDOCK or LM_HORZDOCK bit is set, we need to return a custom docking size. In this case, we can use
another bit LM_HORZ to check if the dialog bar is docked horizontally or vertically. If this bit is set, the
horizontal docking size is being inquired, otherwise the vertically docking size is being inquired.
The floating size can be obtained from a public variable: CDialogBar::m_sizeDefault. By default,
this variable is initialized to the dialog template size, and is updated when the user changes the size of the
dialog bar when it is floating. So this variable always represents the floating size of the dialog bar.
The following is the implementation of this function:

CSize MCDialogBar::CalcDynamicLayout(int nLength, DWORD dwMode)


{
CSize size;
CMainFrame *ptrWnd;
CRect rect;

ptrWnd=(CMainFrame *)(AfxGetApp()->m_pMainWnd);
ptrWnd->GetClientRect(rect);
if((dwMode & LM_VERTDOCK) || (dwMode & LM_HORZDOCK))
{
size.cx=(dwMode & LM_HORZ) ? rect.Width():m_sizeDefault.cx;
size.cy=(dwMode & LM_HORZ) ? m_sizeDefault.cy:rect.Height();

return size;
}
return CDialogBar::CalcDynamicLayout(nLength, dwMode);

26
Chapter 1. Tool Bar and Dialog Bar

First, we obtain the dimension of mainframe window’s client area. For this purpose, first a window
pointer to the mainframe window is obtained, then function CMainFrame::GetClientRect(…) is called to
retrieve its dimension. Here the pointer to the mainframe window is obtained from a public member
variable of class CWinApp. In MFC, every application has a CWinApp derived class, which contains a pointer
m_pMainWnd pointing to the mainframe window. For any application, the pointer to the CWinApp object can
be obtained anywhere in the program by calling function AfxGetApp(). Using this method, we can easily
find the mainframe window of any MFC application.
Because the application supports status bar and tool bar, part of its client area may be covered by the
control bar. So we need to deduct the covered area when calculating the dimension of the client area. For
this purpose, in CMainFrame, function CWnd::GetClientRect(…) is overridden. Within the overridden
function, the client area is adjusted if either the status bar or the tool bar is present:

void CMainFrame::GetClientRect(LPRECT lpRect)


{
CRect rect;

CFrameWnd::GetClientRect(lpRect);
if(m_wndToolBar.IsWindowVisible())
{
m_wndToolBar.GetClientRect(rect);
lpRect->bottom-=rect.Height();
}
if(m_wndStatusBar.IsWindowVisible())
{
m_wndStatusBar.GetClientRect(rect);
lpRect->bottom-=rect.Height();
}
}

Now back to MCDialogBar::CalcDynamicLayout(…) implementation. After obtaining the size of


mainframe window’s client area, we examine LM_VERTDOCK and LM_HORZDOCK bits of dwMode parameter to
see if the docking size is being inquired. If so, we further examine LM_HORZ bit to see if we should return
horizontally docked size or vertically docked size. We return different sizes for different cases. For all other
conditions, we just return the default implementation of the base class.

Using the New Class


To use this class, first we need to declare a MCDialogBar type variable in class CMainFrame. We also
need to make sure that the header file of this class is included. In the sample application, this new variable
is m_wndDialogBar. Then, as we have experienced many times, we need to create the window of the dialog
bar in function CMainFrame::OnCreate(…). When doing this, we need to specify CBRS_SIZE_DYNAMIC flag
in order to let the dialog bar be resized dynamically. Then we can call CDialogBar::EnableDocking(…),
CDialogBar::SetBarStyle(…) and CMainFrame::DockControlBar(…) to set styles and implement
docking.
Now we can compile and execute the new project. Originally, the dialog bar is docked at the bottom
border of the frame window. We may drag and dock it to any other border, or make it floating. As we do
this, the dimension of the dialog bar will be adjusted automatically to suit different docking styles. Also,
the edit control contained in the dialog bar will be resized dynamically according to the change on the
dialog bar.

1.10. Adding Flyby and Tool Tip


Flyby and tool tip are two very nice features that can be added to both tool bar and dialog bar. If we
enable these features, when the user moves mouse cursor over a control contained in tool bar or dialog bar
and stay there for a while, a describing text about this control will appear on the status bar (It is called
Flyby). At the same time, a small window with a short description will pop up (It is called Tool Tip. See
Figure 1-12 for two types of controls).

27
Chapter 1. Tool Bar and Dialog Bar

Tool tip

Flyby

Figure 1-12. Tool tip and flyby

Both features can be enabled by calling function CControlBar::SetBarStyle(…) using the


corresponding flags. To enable tool tip, we need to set CBRS_TOOLTIP flag bit, to enable flyby, we need to
set CBRS_FLYBY flag bit. Actually, in the previous sections, whenever we create a tool bar or dialog bar, the
two flags are always set.
Just setting the above flags can not activate the tool tip and flyby. We need to provide the text that will
be used by tool tip and flyby. The text string must be implemented as application resources, and the ID of
the string must be the same with the command ID of the control. For example, if we want to add flyby and
tool tip for button ID_BUTTON_RED, we must create a string resource using ID_BUTTON_RED as its ID. This
string will be used for both flyby and tool tip implementation. Within the string, the text is separated into
two parts by an ‘\n’ character, with the sub-string before ‘\n’ used for flyby, and the sub-string after ‘\n’
used for tool tip. For example, if we want the flyby and tool tips for the red button to be “This is the red
button” and “Red Button” respectively, the resource string should be “This is the red button\nRed Button”.
If we do not provide a string resource for this command ID, the flyby and the tool tip will not be displayed
even we enable CBRS_TOOLTIP and CBRS_FLYBY flags. If string resource does not have a second sub-string
(In this case, there is no ‘\n’ character contained in the string), the whole string will be used for flyby, and
no tool tip will be implemented.
Adding this string for tool bar buttons is very easy. By opening the property sheet “Toolbar Button
Properties”, we will find an edit box labeled “Prompt”. Inputting a string into this edit box will add the
string resource automatically (Figure 1-13).

Input the string


that will be used
for flyby and
tool tip here

Figure 1-13. Add flyby and tool tip string for tool bar control

For dialog bar, we don’t have the place to input this string in the property sheet. So we need to edit
string resource directly. This can also be implemented very easily. In the Developer Studio, if we execute
command Insert | Resource…(or press CTRL+R keys), an “Insert Resource” dialog box will pop up. To
add a string resource, we need to highlight node “String Table” and press “New” button. After this, a new
window with the string table will pop up. By scrolling to the bottom of the window and double clicking an
empty entry, a “String Properties” property sheet will pop up, which can be used to add a new string
resource (Figure 1-14).

28
Chapter 1. Tool Bar and Dialog Bar

Input string that


will be used for
flyby and tool tip
here

Figure 1-14. Edit string resource directly to


implement flyby and tool tip

Sample 1.10\Bar demonstrates how to implement flybys and tool tips. It is based on sample 1.8\Bar.
Actually, the only difference between the two projects is that some new string resources are added to
sample 1.10\Bar. In sample 1.10\Bar, following string resources are added:

String ID String Contents


IDC_COMBO This is combo box\nCombo Box
IDC_EDIT This is edit box\nEdit Box
IDC_BUTTONA This is button A\nButton A
IDC_BUTTONB This is button B\nButton B
ID_BUTTON_RED This is the red button\nRed Button
ID_BUTTON_GREEN This is the green button\nGreen Button
ID_BUTTON_BLUE This is the blue button\nBlue Button
ID_BUTTON_YELLOW This is the yellow button\nYellow Button

After executing this sample, we can put the mouse cursor over the controls contained in the tool bar or
dialog bar. By doing this, the flyby and tool tip will pop up after the cursor stays there for a short while.

1.11. Toggling Control Bars On/Off


Compared to the default tool bar (IDR_MAINFRAME) created by the Application Wizard, our custom
control bar can not be turned on and off freely. Although we can float it then click the “Χ” button located at
the top-right corner of the window to dismiss it, once we do this, there is no way to get it back. The default
tool bar (also the status bar) has a much better feature: there is a corresponding command in the mainframe
menu, when the tool bar is off, we could execute command View | Toolbar to turn it on.
When a control bar is turned off, its window actually becomes hidden instead of being destroyed. Thus
when we turn it on again, the control bar can still retain its old states (Its original size, position, and
docking state will not change). To turn on or off a control bar, we can call function CFrameWnd::
ShowControlBar(…), which has the following format:

void CFrameWnd::ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay);

This function has three parameters: the first one is a pointer to the control bar that we want to turn on
or off. The second is a Boolean type variable. If it is TRUE, the control bar will be turned on; if it is
FALSE, the control bar will be turned off. The third parameter is also Boolean type, it specifies if this
action should be taken immediately.
Because we need to know the current state of the control bar (on or off) to determine whether we
should hide or show it, we need to call another member function of CWnd to see if the control bar is
currently hidden:

BOOL CWnd::IsWindowVisible( );

This function returns a TRUE or FALSE value, from which we know the control bar’s current state.

29
Chapter 1. Tool Bar and Dialog Bar

Sample 1.11\Bar supports this new feature, it is based on sample 1.10\Bar. For both tool bar and dialog
bar, a new command is added to the main menu, which can be used to toggle the control bar between on
and off state.
The following shows necessary steps for implementing the new commands:

1) Add two menu items View | Color Bar and View | Dialog Bar to the mainframe menu IDR_MAINFRAME,
whose IDs are ID_VIEW_COLORBAR and ID_VIEW_DIALOGBAR respectively.
1) Use Class Wizard to add WM_COMMAND and UPDATE_COMMAND_UI type message handlers for the above
IDs in class CMainFrame. The newly added functions are CMainFrame::OnViewColorBar(),
CMainFrame:: OnViewDialogBar(), CMainFrame::OnUpdateViewColorBar(…) and CMainFrame::
OnUpdateViewDialogBar(…).
1) Implement four WM_COMMAND type message handlers. The function used to handle WM_COMMAND message
for command ID_VIEW_COLORBAR is implemented as follows:

void CMainFrame::OnViewColorBar()
{
BOOL bShow;

bShow=m_wndColorButton.IsWindowVisible() ? FALSE:TRUE;
ShowControlBar(&m_wndColorButton, bShow, FALSE);
}

To indicate the status of control bars, it is desirable to check the corresponding menu item when the
control bar is available, and remove the check when it becomes hidden. This is exactly the same with the
behavior of the default tool bar IDR_MAINFRAME. In the sample, the menu item states are handled by
trapping message UPDATE_COMMAND_UI and the check is set or removed by calling function
CCmdUI::SetCheck(…). The following is the implementation of one of the above message handlers (see
Chapter 2 for more about menu customization):

void CMainFrame::OnUpdateViewColorBar(CCmdUI* pCmdUI)


{
pCmdUI->SetCheck(m_wndColorButton.IsWindowVisible());
}

It is exactly the same with setting or removing check for a tool bar button.
With the above implementations, the application can be executed again. We can dismiss the control bar
either by executing menu command or by clicking “X” button located at the upper-right corner of the
control bar when it is floating. In both cases, the control bar can be turned on again by executing
corresponding menu command. We can also dock or float the control bar and turn it off, and see if the
original state will remain unchanged after it is turned on later.

Summary:
1. To add an extra tool bar, first we need to add a tool bar resource, then declare a CToolBar type variable
in CMainFrame class. Within function CMainFrame::OnCreate(…), we can call the member functions of
CToolBar and CMainFrame to create the tool bar window and set docking styles.
1. The dialog bar can be added in the same way, however, we need to use dialog-template resource and
class CDialogBar to implement it.
1. We can trap WM_COMMAND message for executing command and trap UPDATE_COMMAND_UI for updating
button state. Use ON_COMMAND and ON_UPDATE_COMMAND_UI macros to implement message mapping.
1. We can use ON_COMMAND_RANGE and ON_UPDATE_COMMAND_UI_RANGE macros to map a contiguous range
of command IDs to one member function.
1. When the size of a tool bar is fixed, we can set TBBS_WRAPPED flag for a button to let the tool bar wrap
after that button.
1. To customize the dynamic layout feature of tool bar and dialog bar, we need to override function
CalcDynamicLayout(…).
1. To add a combo box to a tool bar, first we need to set a button to separator with specified width, then
we need to create the combo box dynamically.

30
Chapter 1. Tool Bar and Dialog Bar

1. Flyby and tool tip can be activated by setting CBRS_TOOLTIP and CBRS_FLYBY flags then preparing a
string resource using the exact same ID with the control.
1. To toggle control bar on and off, we can call function CFrameWnd::ShowControlBar(). We need to use
function CWnd::IsWindowVisible() to check if the control bar is currently available.

31
Chapter 2. Menu

Chapter 2

Menu

M
enu is very important for all types of applications, it provides a primary way of letting user
execute application commands. If we create applications using Application Wizard, mainframe
menus will be implemented automatically for all SDI and MDI applications. For dialog-based
applications, system menus will also be implemented, which can be used to execute system
commands (Move the application window, resize it, minimize, maximize and close the application). Some
user-friendly applications also include right-click pop up menus.
This chapter discusses in-depth topics on using and customizing menus, which include: how to
customize the behavior of standard menus, how to make change to standard menu interface, how to
implement owner-draw menu, how to create right-click menu, how to customize the system menu and
implement message mapping for system menu items.

2.1 Message WM_COMMAND and UPDATE_COMMAND_UI


When creating an SDI application using Application Wizard, we will have a default menu added to the
application. This menu has four sub-menus: File, Edit, View and Help, which contain the most commonly
used commands for a typical application. At the beginning some of these commands are functional (such as
View | Tool bar and File | Exit) but some are disabled (such as Edit | Copy). We have to add our own
code in order to make them usable.
To activate a command, we need to add message handlers for it. For a general Windows application,
we need to pay attention to two messages: WM_COMMAND and UPDATE_COMMOND_UI.
Sample 2.1\Menu demonstrates how to handle two types of messages through simulating a cut, copy
and paste procedure. Here, we make use of three default menu commands added by the Application
Wizard: View | Cut, View | Copy, View | Paste. The application will enable View | Paste menu item only
after View | Cut or View | Copy has been executed at least once (For demonstration purpose, command
View | Copy and View | Cut do not actually copy data to the clipboard). Also, item View | Paste will be
changed dynamically indicating if the newly copied data has been pasted.
The sample is started by generating standard SDI application using Application Wizard. The project is
named “Menu” and four standard classes are CMenuApp, CMainFrame, CMenuDoc, and CMenuView
respectively. All other settings are default. After compiling and executing the skeleton application, we will
see a standard SDI application with a menu implemented in the mainframe window. By examining this
menu, we can find that it has the following structure:

File
New…
Open…
Save…
Save As…
Separator
Recent File
Separator
Exit
Edit

32
Chapter 2. Menu

Undo
Separator
Cut
Copy
Paste
View
Toolbar
Status Bar
Help
About…

By clicking “Edit” sub-menu, we will see that all the commands contained there are disabled. If we
edit the menu resource and add additional commands, they will not become functional until we add
message handlers for them.

Handling WM_COMMAND Command


The first step of enabling a menu command is to implement a WM_COMMAND message handler for it. This
is exactly the same with what we did for a tool bar command in Chapter 1. Just as buttons contained in a
tool bar, each menu command has a unique command ID. When the user clicks the menu item, the system
detects the mouse events and sends a WM_COMMAND message to the application, with the command ID passed
through WPARAM parameter. In MFC, as the application receives this message, a corresponding message
handler will be called to execute the command. Again, the message mapping should be implemented by
using ON_COMMAND macro.
The message mapping could be implemented either manually or through using Class Wizard. This
procedure is also the same with adding message handlers for tool bar buttons as we did in Chapter 1. If we
use Class Wizard, after invoking it, first we need to go to “Messages Maps” page. Then we need to select a
class name from “Class name” combo box (In the sample, all the commands are handled in the document
class, so we need to select “CMenuDoc” if it is not selected). Next, we need to find the command ID to
which we want to add handlers in “Object IDs” window, and highlight “Command” item in “Messages”
window. Now click “Add Function” button and confirm the member function name. After this, a new
member function and the corresponding message mapping macros will be added to the application.
There is no difference between adding message handlers manually and adding them using Class
Wizard. However, doing it manually will let us understand message mapping mechanism, which will make
it easier for us to further customize the menu behavior.
In the sample application we will implement commands View | Cut, View | Copy and View |Paste. So
at first WM_COMMAND type message handlers need to be added for commands ID_EDIT_COPY, ID_EDIT_CUT
and ID_EDIT_PASTE in CMenuDoc class. The following shows the steps of adding them through using Class
Wizard:

1) In file “MenuDoc.h”, three member functions are declared in the class, they will be used to handle
ID_EDIT_COPY, ID_EDIT_CUT and ID_EDIT_PASTE command execution:

class CMenuDoc : public CDocument


{
……
//{{AFX_MSG(CMenuDoc)
afx_msg void OnEditCopy();
afx_msg void OnEditCut();
afx_msg void OnEditPaste();
//}}AFX_MSG
……
}

2) In file “MenuDoc.cpp”, message mapping macros are added to associate the member functions with
the command IDs:

BEGIN_MESSAGE_MAP(CMenuDoc, CDocument)
//{{AFX_MSG_MAP(CMenuDoc)
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
ON_COMMAND(ID_EDIT_CUT, OnEditCut)

33
Chapter 2. Menu

ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

3) Three blank message handlers are added in file “MenuDoc.cpp”:

void CMenuDoc::OnEditCopy()
{
}

void CMenuDoc::OnEditCut()
{
}

void CMenuDoc::OnEditPaste()
{
}

When first added, these functions are empty. We have to add our own code in order to support
command execution.
By compiling and executing the sample application at this point, we will see that Edit | Copy, Edit |
Cut and Edit | Paste menu items are all enabled. This is because three blank message handlers have just
been added.

Enabling & Disabling a Command


The sample application will not actually cut, copy or paste data. The three commands will be
implemented just to simulate data copy and paste procedure. Before going on to implement it, we need to
make following assumptions.
Suppose the application supports only internal data copy, cut and paste (it does not accept data from
other applications through using system clipboard). Before command Edit | Copy or Edit | Cut is
executed, there should be no data stored in the “local clipboard”. Therefore, if we execute Edit | Paste
command at this time, there will be an error. To avoid this, we need to disable Edit | Paste command
before data has been copied to the clipboard.
The state of menu item can be set thought handling UPDATE_COMMAND_UI message. The parameter
comes along with this message is a pointer to CCmdUI type object, which can be used to enable or disable a
command, set or remove check for a menu item. Handling this message for menu items is the same with
that of tool bar controls.
So it is easy to find out a mechanism for updating command Edit | Paste: we need to declare a
Boolean type variable in class CMenuDoc and initialize it to FALSE. We can set this flag to TRUE when
Edit | Cut or Edit | Copy command is executed, and enable Edit | Paste command only if this flag is set.
In the sample application, this Boolean variable is CMenuDoc::m_bPasteAvailable. The following
code fragment shows how it is declared and initialized in the constructor of class CMenuDoc:

class CMenuDoc : public CDocument


{
……
protected:
BOOL m_bPasteAvailable;
……
}

CMenuDoc::CMenuDoc()
{
m_bPasteAvailable=FALSE;
}

The value of CmenuDoc::m_bPasteAvailable is set to TRUE when user executes either Edit | Copy or
Edit | Cut command:

void CMenuDoc::OnEditCopy()
{
m_bPasteAvailable=TRUE;
}

void CMenuDoc::OnEditCut()

34
Chapter 2. Menu

{
m_bPasteAvailable=TRUE;
}

Now we will use CMenuDoc::m_bPasteAvailable to enable Edit | Paste menu item when it becomes
TRUE. In MFC, menu items are updated through handling UPDATE_COMMAND_UI messages. When the state
of a menu command needs to be updated, UPDATE_COMMAND_UI message will be automatically sent to the
application. If there exists a corresponding message handler, it will be called for updating the
corresponding menu item. Otherwise, the menu item will remain unchanged.
Adding an UPDATE_COMMAND_UI message handler is the same with adding a WM_COMMAND message
handler. After invoking the Class Wizard, we need to select the class name, the command ID, and highlight
“UPDATE_COMMAND_UI” instead of “WM_COMMAND” in “Messages” window. Finally, we need to
click button “Add Function”.
After adding the message handler for ID_EDIT_PASTE command, we will have a new member function
declared in class CMenuDoc, and a new message mapping macro added to the class implementation file. In
addition, we will have an empty function that could be modified to implement message handling:

void CMenuDoc::OnUpdateEditPaste(CCmdUI *pCmdUI)


{
}

The only parameter to this function is a pointer to CCmdUI type object. Here, class CCmdUI has several
member functions, which can be used to set the state of the menu item. To enable or disable the menu item,
we can call function CCmdUI::Enable(…). The function has only one Boolean type parameter, we can pass
TRUE to enable the menu item and pass FALSE to disable it. Because the state of Edit | Paste command
depends upon variable CMenuDoc::m_bPasteAvailable, we can use it to set the state of menu item:

void CMenuDoc::OnUpdateEditPaste(CCmdUI* pCmdUI)


{
pCmdUI->Enable(m_bPasteAvailable);
}

By compiling and executing the sample application at this point, we will see that Edit | Paste
command is disabled at the beginning, and after we execute either Edit | Cut or Edit | Copy command, it
will be enabled.

Changing Menu Text


We will go on to add more features to the mainframe menu. Class CCmdUI has another useful member
function CCmdUI::SetText(…), which allows us to change the text of a menu item dynamically. By using
this function, we can change the text of Edit | Paste menu item so that it can convey more information to
the user. For example, we could set text to “Do not paste” when data is not available, and to “Please paste”
when data is available. To add this feature, all we need is to call CCmdUI::SetText(…) in the above
message handler as follows:

void CMenuDoc::OnUpdateEditPaste(CCmdUI* pCmdUI)


{
pCmdUI->Enable(m_bPasteAvailable);
pCmdUI->SetText(m_bPasteAvailable ? "Please &paste":"Do not &paste");
}

Checking a Menu Item


Let’s further add some more interesting features to the menu commands. With the current
implementation we do not know if the data has been “Pasted” after it is “cut” or “copied” to the
“clipboard”. We can indicate the data status by putting a check mark on the menu item so the user knows if
the current “data” in the “clipboard” has been pasted (after the user executes View | Paste command). This
check will be removed when either “cut” or “copy” command is executed.
Similar to tool bar, we can call function CCmdUI::SetCheck(…) to set check for a menu item. The
difference between the results of this function is the changing on the interface: while setting check for a

35
Chapter 2. Menu

button will make it recess, setting check for a menu item will put a check mark at the left side of the menu
item.
We need a new Boolean type variable to indicate the status of “data”. In the sample application, this
variable is CMenuDoc::m_bDataPasted, which is initialized to FALSE in the constructor. The following
functions show how its value is changed under different situations:

void CMenuDoc::OnEditCopy()
{
m_bPasteAvailable=TRUE;
m_bDataPasted=FALSE;
}

void CMenuDoc::OnEditCut()
{
m_bPasteAvailable=TRUE;
m_bDataPasted=FALSE;
}

void CMenuDoc::OnEditPaste()
{
m_bDataPasted=TRUE;
}

In function OnUpdateEditPaste(…), the menu item is checked only when flag CMenuDoc::
m_bDataPasted is TRUE:

void CMenuDoc::OnUpdateEditPaste(CCmdUI* pCmdUI)


{
pCmdUI->Enable(m_bPasteAvailable);
pCmdUI->SetCheck(m_bDataPasted);
pCmdUI->SetText
(
m_bPasteAvailable ?
(m_bDataPasted ? "Data &pasted":"Please &paste"):
"Do not &paste"
);
}

The text of the menu item is also change to “Data pasted” when the menu item is checked.
The last thing need to be mentioned here is another member function of class CCmdUI: CCmdUI::
SetRadio(…). Like CCmdUI::SetCheck(…), this function will put a check mark on a menu item. The
difference between two functions is that CCmdUI::SetRadio(…) makes menu items behave like radio
buttons: when this function is called to check one item, all other items in the same group will be unchecked
automatically. Calling function CCmdUI::SetCheck(…) does not affect other menu items.

2.2 Right Click Pop Up Menu


In Windows 95, right-click menu becomes a standard user interface. We can right click on the
desktop, task bar, or other types of windows to bring up a menu that contains the most commonly used
commands. In this section, we will discuss how to add right-click menu to our application.

Adding Menu Resource


Sample 2.2\Menu demonstrates right-click menu implementation. It is a standard SDI application
generated by Application Wizard with all the default settings. This is the same with the previous sample.
We can also start from sample 2.1\Menu and add the new features that will be discussed below.
Like tool bar and dialog bar, a menu can be implemented starting from building menu resource. To add
a menu resource, We can execute Insert | Resource command in Developer Studio, select “menu” resource
type from the popped up dialog box, and click button “New”. Now a new menu resource with a default ID
will be added to the application. In the sample, this default ID is changed to IDR_MENU_POPUP, and a sub-
menu with four menu items is created. The newly created menu items are “Pop Up Item 1”, “Pop Up Item
2”, “Pop Up Item 3” and “Pop Up Item 4”, whose command IDs are ID__POPUPITEM1, ID__POPUPITEM2,
ID__POPUPITEM3 and ID__POPUPITEM4 respectively (Figure 2-1).

36
Chapter 2. Menu

For right-click menu, we


don’t need to provide a
text for sub menu title

Add menu items here

Figure 2-1: Add menu resource for right-click menu

Trapping Right Button Clicking Event


The first step to implement a right-click menu is to detect mouse’s right clicking event, which is a
standard Windows event, and its corresponding message is WM_RBUTTONDOWN. To trap this message, we
need to implement message handler.
When we click mouse’s right button on a window, message WM_RBUTTONDOWN will be sent to that
window. This window could be any type: mainframe window, client window, dialog box, or even button.
We need to handle this message in the class that implements the window. For example, if we want to
handle right click in a dialog box, we need to add the message handler in a CDialog derived class, if we
want to handle it in the client window of an SDI application, we need to add the message handler in CView
derived class.
In our sample, right-clicking menu is implemented in the client window. So we need to trap message
WM_RBUTTONDOWN in class CMenuView.
Adding message handler for message WM_RBUTTONDOWN is similar to that of WM_COMMAND: first we need
to declare an afx_msg type member function OnRButtonDown(…), then use ON_RBUTTONDOWN macro to map
the message to this function. Finally, we need to implement the message handler. Please note that
OnRButtonDow(…) is the standard function name that will be automatically associated with message
WM_RBUTTONDOWN. When using ON_RBUTTONDOWN macro, we do not need to specify function name.
The above-mentioned procedure can be implemented through using Class Wizard as follows: after
invoking the Class Wizard, select class CMenuView, which is the class used to implement the client window.
There are a lot of virtual functions and messages listed in the “Messages” window. By scrolling the vertical
scroll bar, it is easy to find message WM_RBUTTONDOWN. Now highligh this message and click “Add function”
button. This will cause a new member function OnRButtonDown to be added to class CMenuView, and
message mapping macros to be added to the implementation file (See Figure 2-2).
In the sample, the newly added function is CMenuView::OnRButtonDown(…), which needs to be
modified to implement right-click menu. By default, this function does nothing but calling the message
handler implemented by the base class:

void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)


{
CView::OnRButtonDown(nFlags, point);
}

Using Class CMenu


We need to modify the above function in order to implement right-click pop up menu. In MFC, there is
a class designed for menu implementation: CMenu, which contains some member functions that allow us to
create menu dynamically, track and update menu items, and destroy the menu.
The first function we will use is CMenu::LoadMenu(…), it allows us to load a menu resource and use it
later. This function has two different versions, one allows us to load a menu resource with a numerical ID,
and the other allows us to load a resource with a string ID:

37
Chapter 2. Menu

Step 3: Select Step 1: Execute View |


CMenuView class ClassWizard …
command
Step 2: Click
“Message Maps” tab

Step 4: Highlight
CMenuView Step 6: Click “Add
Function” button

Step 5: Locate
WM_RBUTTONDOWN
and highlight it
When finished, click
“OK” button

Figure 2-2. Add message handler for WM_RBUTTONDOAND

BOOL LoadMenu(LPCTSTR lpszResourceName);


BOOL LoadMenu(UINT nIDResource);

In the sample application, the menu resource is stored by a numerical ID (IDR_MENU_POPUP). We can
also assign a string ID to it by inputting a quoted text in the edit box labeled with “ID”.
We need to use CMenu to declare a variable that will be used to load the menu resource. Normally the
right-click menu will be initiated after right-clicking event has been detected. Then the mouse’s activities
will be tracked by the menu until the user executes one of the menu commands or dismisses the menu.
Because all these things can be handled within the message handler, the variable used to implement menu
can be declared as a local variable. In the sample, the menu resource is loaded as follows:

void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)


{
CMenu menu;

menu.LoadMenu(IDR_MENU_POPUP);
CView::OnRButtonDown(nFlags, point);
}

Generally, one menu contains several sub-menus, and each sub-menu contains several menu items. For
right click menu, only one sub-menu (instead of whole menu) will be implemented each time the user
clicks mouse’s right button. Because of this, in the sample application, menu IDR_MENU_POPUP contains
only one sub-menu. To obtain a pointer to the sub-menu, we can call function CMenu::GetSubMenu(…),
which has the following format:
CMenu *Cmenu::GetSubMenu(int nPos) const;

Parameter nPos indicates which sub-menu we are trying to obtain. In a menu resource, the left-most
sub-menu is indexed 0, next sub-menu indexed 1, and so on. In the sample application, sub-menu that
contains items “Pop Up Item 1”… is located at position 0.
This function returns a CMenu type pointer that could be used to further access each item contained in
the sub-menu. Before the menu is displayed, we may want to set the state of each menu item: we can
enable, disable, set check or change text for a menu item. Please note that for a right-click menu, we do not
need to handle message UPDATE_COMMAND_UI in order to set the states of menu items. Instead, there exist
two member functions that can be used:

UINT CMenu::EnableMenuItem(UINT nIDEnableItem, UINT nEnable);


UINT CMenu::CheckMenuItem(UINT nIDCheckItem, UINT nCheck);

The above two functions can be used to enable/disable, set/remove check for a menu item. When
calling the two functions, we can reference a menu item by using either its command ID or its position.
Normally we can pass a command ID to nIDEnableItem or nIDCheckItem parameter. If we want to

38
Chapter 2. Menu

reference an item by its position (0 based, for example, in the sample application, ID__POPUPITEM1’s
position is 0, and ID__POPUPITEM2’s position is 1…), we need to set MF_BYPOSITION bit of nEnable or
nCheck parameter.
The menu can be activated and tracked by calling function CMenu::TrackPopupMenu(…):

BOOL CMenu::TrackPopupMenu
(
UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect=NULL
);

This function has 5 parameters. The first parameter nFlags lets us set styles of the menu (Where
should the menu be put, which mouse button will be tracked). The most commonly used combination is
TPM_LEFTALIGN | TPM_RIGHTBUTTON, which aligns menu’s left border according to parameter x, and tracks
mouse’s right button activity (because we are implementing a right-click menu). The second parameter y
decides the vertical position of the menu’s top border. Please note that when message WM_RBUTTONDOWN is
received, position of current mouse cursor will be passed to one of the parameters of function
OnRButtonDown(…) as a CPoint type object. To make right-click menu easy to use, we can pass this
position to function CMenu::TrackPopupMenu(…), which will create a pop up menu at the position of
current mouse cursor. The fourth parameter is a CWnd type pointer, which indicates which window owns the
pop up menu. In the sample, because the menu is implemented in the member function of class CMenuView,
we can use this pointer to indicate the menu owner. The final parameter discribes a rectangle within which
the user can click the mouse without dismissing the pop up menu. We could set it to NULL, in which case
the menu will be dismissed if the user clicks outside the pop up menu.

Implementing Right-Click Menu


Now we can implement WM_RBUTTONDOWN message handler, load the menu resource and create right-
click menu in the member function:

void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)


{
CMenu menu;
CMenu *ptrMenu;

menu.LoadMenu(IDR_MENU_POPUP);
ptrMenu=menu.GetSubMenu(0);
ptrMenu->EnableMenuItem(ID__POPUPITEM1, MF_GRAYED);
ptrMenu->EnableMenuItem(ID__POPUPITEM2, MF_ENABLED);
ptrMenu->CheckMenuItem(ID__POPUPITEM3, MF_UNCHECKED);
ptrMenu->CheckMenuItem(ID__POPUPITEM4, MF_CHECKED);
ClientToScreen(&point);
ptrMenu->TrackPopupMenu
(
TPM_LEFTALIGN|TPM_RIGHTBUTTON,
point.x,
point.y,
this,
NULL
);
CView::OnRButtonDown(nFlags, point);
}

After implementing the right-click menu, we still need to call function CView::OnRButtonDown(…).
This is to make sure that the application does not lose any default property implemented by class CView.
In the above function, before CMenu::TrackPopupMenu(…) is called, function
CWnd::ClientToScreen() is used to convert the coordinates of a point from the client window to the
desktop window (the whole screen). When point parameter is passed to CMenuView::OnRButtonDown(…), it
is assumed to be measured in the coordinates system of the client window, which means (0, 0) is located at
the upper-left corner of the client window. When we implement a menu, function
CMenu::TrackPopupMenu(…) requires coordinates to be measured in the desktop window system, which
means (0, 0) is located at the upper-left corner of the screen. Function CWnd::ClientToScreen(…) can
convert the coordinates of a point between the two systems. This function is frequently used when we need
to convert coordinates from one window to another.

39
Chapter 2. Menu

By compiling and executing the application at this point, we will see that the right-click menu is
implemented successfully.

Message Mapping for Right-Click Menu


Although the right-click menu is working now, we still can not use it to execute any command. The
reason is simple: we haven’t implemented WM_COMMAND type message handlers for the menu commands yet.
For right-click menu, we cannot add message handlers using Class Wizard, because the command IDs of
the menu items are not listed in “Object IDs” window of the Class Wizard. Thus, we have to do everything
manually. Actually, adding message handlers for right-click menu items is the same with adding handlers
for a normal menu item: we need to declare afx_msg type functions, use ON_COMMAND macros to do the
message mapping, and implement the member functions. In the sample, WM_COMMAND type message handler
is added for each menu item contained in the right-click menu. Within each message handler, a message
box pops up indicating which menu item it is.
The following portion of code shows the member functions declared in class CMenuDoc:

class CMenuDoc : public CDocument


{
……
//{{AFX_MSG(CMenuDoc)
//}}AFX_MSG
afx_msg void OnPopUpItem1();
afx_msg void OnPopUpItem2();
afx_msg void OnPopUpItem3();
afx_msg void OnPopUpItem4();
DECLARE_MESSAGE_MAP()
……
}

Message mapping macros are implemented as follows:

BEGIN_MESSAGE_MAP(CMenuDoc, CDocument)
//{{AFX_MSG_MAP(CMenuDoc)
//}}AFX_MSG_MAP
ON_COMMAND(ID__POPUPITEM1, OnPopUpItem1)
ON_COMMAND(ID__POPUPITEM2, OnPopUpItem2)
ON_COMMAND(ID__POPUPITEM3, OnPopUpItem3)
ON_COMMAND(ID__POPUPITEM4, OnPopUpItem4)
END_MESSAGE_MAP()

Four member functions are implemented as follows:

void CMenuDoc::OnPopUpItem1()
{
AfxMessageBox("Pop up menu item 1");
}

void CMenuDoc::OnPopUpItem2()
{
AfxMessageBox("Pop up menu item 2");
}

void CMenuDoc::OnPopUpItem3()
{
AfxMessageBox("Pop up menu item 3");
}

void CMenuDoc::OnPopUpItem4()
{
AfxMessageBox("Pop up menu item 4");
}

With the above implementation, we are able to execute the commands contained in the right-click pop
up menu.

40
Chapter 2. Menu

2.3 Updating Menu Dynamically

Sometimes it is desirable to change the contents of a menu dynamically. For example, if we create an
application that supports many commands, we may want to organize them into different groups. Sometimes
we want to enable a group of commands, sometimes we want to disable them.
Although we can handle UPDATE_COMMAND_UI message to enable or disable commands, sometimes it is
more desirable if we can remove the whole sub-menu instead of just graying the menu text. Actually, sub-
menu and menu item can all be modified dynamically: we can either add or delete a sub-menu or menu
item at any time; we can also change the text of a menu item, move a sub-menu or menu item, or add a
separator between two menu items. All these things can be implemented at run-time.

Menu Struture
The structure of menu Æ sub menu Æ menu item is like a tree. At the topmost level (the root), the
menu comprises several sub-menus. Each sub-menu also comprises several items, which could be a normal
command or another sub-menu. For example, in application Explorer (file browser in Windows95), its
first level menu comprises five sub-menus: File, Edit, View, Tool, and Help. If we examine File sub-
menu, we will see that it comprises eight items: New, separator, Create Shortcut, Delete, Rename,
Properties, separator and Close. Here, item New is another sub-menu, which comprises several other
menu items. This kind of structure can continue. As long as our program needs, we can organize our menu
into many different levels.
In MFC, class CMenu should be used this way. With a CMenu type pointer to a menu object, we have the
access to only the menu items at certain level. If we want to access a menu item at a lower level, we first
need to access the sub-menu that contains the desired menu item.
This can be explained by the previous “Explorer” example: suppose we have a CMenu type pointer to
the main menu, we can use it to access the first level menu items: File, Edit, View, Tool, and Help. This
means we can use the pointer to disable, enable or set text for any of the above items, but we can not use it
to make change to the items belonging to other levels, for example, New item under File sub-menu. To
access this item, we need to first obtain a CMenu type pointer to File sub-menu, then use it to modify item
File | New.

Inserting and Removing Menu Item


Class CMenu has certain member functions that allow us to insert or delete a menu item dynamically.
We can add either a menu item (including separator), or a whole sub-menu. When we remove a sub-menu,
all the lower level items and sub-menus will be removed.
The function that can be used to insert menu items or sub-menus is CMenu::InsertMenu(…), it has the
following format:

BOOL CMenu::InsertMenu
(
UINT nPosition, UINT nFlags, UINT nIDNewItem=0, LPCTSTR lpszNewItem=NULL
);

This function has five parameters. The first parameter, nPosition, indicates where we want our new
menu item to be inserted. It could be an absolute position, 0, 1, 2…, or a command ID of the menu item. In
the former case, MF_BYPOSITION bit of second parameter nFlags must be set. In the latter case,
MF_BYCOMMAND bit must be set. Since not all menu items have a command ID (such as a separator), using
position to indicate a menu item is sometimes necessary.
Generally, we can insert three types of items: a menu item with specified command ID, a separator or a
sub-menu. To insert a menu item, we need to pass the command ID to nIDNewItem parameter, then use the
final parameter lpszNewItem to specify the text of this menu item. If we want to insert a separator, we must
set MF_SEPARATOR bit of parameter nFlag. In this case the rest two parameters nIDNewItem and
lpszNewItem will be ignored, so we can pass any value to them. If we want to insert a sub-menu, we must
pass a menu handle to parameter nIDNewItem, and use lpszNewItem to set the text for the menu item.

41
Chapter 2. Menu

In Windows programming, handle is a very important concept. Many types of resources are managed
through using handles. A handle is just a unique number that can be used to reference a block of memory.
After an object (program, resource, dynamically allocated memory block, etc.) is loaded into the memory,
it will be assigned a unique handle that can be used to access this object. As a programmer, we don’t need
to know the exact value of a handle. When accessing an object, instead of using handle’s absolute value, we
can just use the variable that stores the handle.
Different handles have different prototypes, for a menu object, its prototype is HMENU.
In MFC, this is further simplified. When we call a member function to load an object into the memory,
the handle will be automatically saved to a member variable. Later if we need this handle, we can just call a
member function to retrieve it.
In the case of class CMenu, after calling function CMenu::LoadMenu(…), we can obtain the handle of the
menu resource by calling function CMenu::GetSafeHmenu().
For example, in sample 2.2\Menu, after menu resource IDR_MENU_POUP is loaded into the memory, we
could obtain the handle of its first sub-menu and store it to an HMENU type variable as follows:

CMenu menu;
CMenu *ptrMenu;
HMENU hMenu;

menu.LoadMenu(IDR_MENU_POPUP);
ptrMenu=menu.GetSubMenu(0);
hMenu=ptrMenu->GetSafeHmenu();

To remove a menu item, we need to use another member function of CMenu:

BOOL CMenu::RemoveMenu(UINT nPosition, UINT nFlags);

The meanings of nPosition and nFlags parameters are similar to those of function CMenu::
InsertMenu(…).
There is another similar function: CMenu::DeleteMenu(…), which can also remove a menu item or sub-
menu. However, if we use this function to delete a sub-menu, the menu resource will be released from the
memory. In this case, if we wand to use the sub-menu again, we need to reload the menu resource.

Sample Implementation
Sample 2.3\Menu demonstrates how to add and delete menu items dynamically. It is a standard SDI
application generated by Application Wizard, with all the default settings. In this sample, there are two
commands Edit | Insert Dynamic Menu and Edit | Delete Dynamic Menu. If we execute the first
command, a new sub-menu will be added between File and Edit sub-menus. We can use the second
command to remove this dynamically added sub-menu.
The first step is to add two menu items to IDR_MAINFRAME menu resource. In the sample, two
commands are added to Edit sub-menu, their description text are “Insert Dynamic Menu” and “Delete
Dynamic Menu” respectively, and their command IDs are ID_EDIT_INSERTDYNAMICMENU and
ID_EDIT_DELETEDYNAMICMENU. Both of them have WM_COMMAND and UPDATE_COMMAND_UI message handlers
in class CMenuDoc, whose function names are OnEditInsertDynamicMenu,
OnUpdateEditInsertDynamicMenu, OnEditDeleteDynamicMenu and OnUpdateEditDeleteDynamicMenu.
Because we want to disable command ID_EDIT_DELETEDYNAMICMENU and enable command
ID_EDIT_INSERTDYNAMICMENU before the sub-menu is inserted, and reverse this after the menu is inserted,
another Boolean type variable m_bSubMenuOn is declared in class CMenuDoc, which will be used to indicate
the state of the inserted menu. It is initialized to FALSE in the constructor.
Preparing the menu resource that will be used to implement dynamic sub-menu is the same with what
we did in the previous sample. Here a resource IDR_MENU_POPUP is added to the application, whose content
is the same with the resource created in sample 2.2\Menu.
In this case, we could not use a local variable to load the menu, because once the menu is inserted, it
may exist for a while before the user removes it. If we still use a local variable, it will go out of scope after
the messagae hander returns. In the sample, a CMenu type variable is declared in class CMenuDoc, which is
used to load the menu resource in the constructor.
The following shows the modified class CMenuDoc:

42
Chapter 2. Menu

class CMenuDoc : public CDocument


{
protected: // create from serialization only
CMenu m_menuSub;
BOOL m_bSubMenuOn;
……
protected:
//{{AFX_MSG(CMenuDoc)
afx_msg void OnEditInsertDynamicMenu();
afx_msg void OnUpdateEditInsertDynamicMenu(CCmdUI* pCmdUI);
afx_msg void OnEditDeleteDynamicMenu();
afx_msg void OnUpdateEditDeleteDynamicMenu(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

The following is the constructor within which the menu resource is loaded and m_bSubMenuOn is
initialized:

CMenuDoc::CMenuDoc()
{
m_menuSub.LoadMenu(IDR_MENU_POPUP);
m_bSubMenuOn=FALSE;
}

The following shows two UPDATE_COMMAND_UI message handlers where two menu commands are
enabled or disabled:

void CMenuDoc::OnUpdateEditInsertDynamicMenu(CCmdUI* pCmdUI)


{
pCmdUI->Enable(m_bSubMenuOn == FALSE);
}

void CMenuDoc::OnUpdateEditDeleteDynamicMenu(CCmdUI* pCmdUI)


{
pCmdUI->Enable(m_bSubMenuOn == TRUE);
}

At last, we must implement two WM_COMMAND message handlers. First, we need to find a way of
accessing mainframe menu IDR_MAINFRAME of the application. In MFC, a menu associated with a window
can be accessed by calling function CWnd::GetMenu(), which will return a CMenu type pointer. Once we get
this pointer, we can use it to access any of its sub-menus.
The mainframe window pointer can be obtained by calling function AfxGetMainWnd() anywhere in the
program. An alternate way is to call AfxGetApp() to obtain a CWinApp type pointer, then access its public
member m_pMainWnd. We could use CMenu type pointer to insert or remove a sub-menu dynamically.
The following shows two message handlers that are used to insert or remove the sub-menu:

void CMenuDoc::OnEditInsertDynamicMenu()
{
CMenu *pTopMenu=AfxGetMainWnd()->GetMenu();
CMenu *ptrMenu=m_menuSub.GetSubMenu(0);
pTopMenu->InsertMenu
(
1, MF_BYPOSITION | MF_POPUP, (UINT)ptrMenu->GetSafeHmenu(), "&Dynamic Menu"
);
AfxGetMainWnd()->DrawMenuBar();
m_bSubMenuOn=TRUE;
}

void CMenuDoc::OnEditDeleteDynamicMenu()
{
CMenu *pTopMenu=AfxGetMainWnd()->GetMenu();
pTopMenu->RemoveMenu(1, MF_BYPOSITION);
AfxGetMainWnd()->DrawMenuBar();
m_bSubMenuOn=FALSE;
}

When inserting sub-menu, flag MF_BYPOSITION is used. This is because the first level menu items do
not have command IDs.

43
Chapter 2. Menu

After the menu is inserted or removed, we must call function CWnd::DrawMenuBar() to let the menu be
updated. Otherwise although the content of the menu is actually changed, it will not be reflected to the user
interface until the update is triggered by some other reasons.

2.4 Bitmap Check


The default menu check provided by MFC is a tick mark, and nothing is displayed when the check is
removed. With a little effort, we can prepare our own bitmaps and use them to implement the checked and
unchecked state (Figure 2-3).
To implement the checked and unchecked states of menu items using bitmaps, we need to call the
following member function of CMenu:

BOOL CMenu::SetMenuItemBitmaps
(
UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked,
const CBitmap* pBmpChecked
);

The first two parameters of this function indicate which menu item we are working with. Their
meanings are the same with that of functions such as CMenu::EnableMenuItem(…). When calling this
function, we can use either a command ID or an absolute position to identify a menu item. The third and
fourth parameters are pointers to bitmaps (CBitmap type objects), one for checked state, one for unchecked
state.

Standard checks
Bitmap checks

Figure 2-3. Standard menu checks and bitmap menu checks

Like menu, bitmap can also be prepared as resource then be loaded at program’s runtime. We can edit
a bitmap in Developer Studio, and save it as application’s resource. Adding a bitmap resource is the same
with adding other types of resources: we can execute Insert | Resource… command, then select Bitmap
from the popped up dialog box. The newly added resource will be assigned a default ID, it could also be
changed by the programmer.
To load a bitmap resource into the memory, we need to use class CBitmap. This procedure is similar to
loading a menu resource: first we need to use CBitmap to declare a variable, then call function
CBitmap::LoadBitmap(…) to load the resource. For example, if we have a CBitmap type variable bmp, and
our bitmap resource’s ID is IDB_BITMAP, we can load the bitmap as follows:

bmp.LoadBitmap(IDB_BITMAP);

When calling function CMenu::SetMenuItemBitmaps(…), we can pass the pointers of CBitmap type
variables to its parameters.
Sample 2.4\Menu demonstrates bitmap check implementation. It is based on sample 2.3\Menu, which
adds check bitmaps to menu item ID__POPUPITEM1 and ID__POPUPITEM2. Two bitmap resources
IDB_BITMAP_CHECK and IDB_BITMAP_UNCHECK are used to indicate menu item’s checked and unchecked
states respectively. Both bitmaps have a size of 15×15, which is a suitable size for normal menu items. If
we use bigger bitmaps, they might be chopped to fit into the area of menu item.
In the sample, two new CBitmap type variables m_bmpCheck and m_bmpUnCheck are declared in class
CMenuDoc, which are used to load the bitmap resources:

44
Chapter 2. Menu

class CMenuDoc : public CDocument


{
protected:
CMenu m_menuSub;
CBitmap m_bmpCheck;
CBitmap m_bmpUnCheck;
BOOL m_bSubMenuOn;
……
}

In the constructor of CMenuDoc, bitmap resources IDB_BITMAP_CHECK and IDB_BITMAP_UNCHECK are


loaded using two variables. Also, after we load the pop up menu resource, function CMenu::
SetMenuItemBitmap(…) is called for both ID__POPUPITEM1 and ID__POPITEM2. We use bitmaps to indicate
both checked and unchecked states. The following code fragment shows how it is implemented:

CMenuDoc::CMenuDoc()
{
CMenu *ptrMenu;

m_menuSub.LoadMenu(IDR_MENU_POPUP);
m_bmpCheck.LoadBitmap(IDB_BITMAP_CHECK);
m_bmpUnCheck.LoadBitmap(IDB_BITMAP_UNCHECK);
ptrMenu=m_menuSub.GetSubMenu(0);
ptrMenu->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bmpUnCheck, &m_bmpCheck);
ptrMenu->SetMenuItemBitmaps(1, MF_BYPOSITION, &m_bmpUnCheck, &m_bmpCheck);
m_bSubMenuOn=FALSE;
}

When calling function CMenu::SetMenuItemBitmap(…), we use absolute position instead of command


ID to identify a menu item. So the second parameter passed to the function is MF_BYPOSITION.
Besides these, two UPDATE_COMMAND_UI message handlers are also added to CMenuDoc to set item
ID__POPUPITEM1 to checked state and set ID_POPUPITEM2 to unchecked state permenently. The following
two functions show the implementation of the messages handlers:

void CMenuDoc::OnUpdatePopUpItem1(CCmdUI *pCmdUI)


{
if(m_bSubMenuOn)pCmdUI->SetCheck(TRUE);
}

void CMenuDoc::OnUpdatePopUpItem2(CCmdUI *pCmdUI)


{
if(m_bSubMenuOn)pCmdUI->SetCheck(FALSE);
}

In the sample, bitmaps are prepared for both checked and unchecked states for a menu item. When
calling function CMenu::SetMenuItemBitmaps(…), if either of the bitmaps is not provided (the
corresponding parameter is NULL), nothing will be displayed for that state. If both parameters are NULL,
the default tick mark will be used for the checked state.

2.5 System Menu and Bitmap Menu Item

System Menu
By default, every application has a system menu, which is accessible through left clicking on the small
icon located at the left side of application’s caption bar, or right clicking on the application when it is in
icon state. The system menu can be customized to meet special requirement. Especially, we can add and
delete menu items dynamically just like a normal menu.
We already know how to access an application’s standard menu. Once we obtained a CMenu type
pointer to the application’s standard menu, we can feel free to add new menu items, remove menu items,
and change their attributes dynamically.
System menu is different from a standard menu. We need to call another function to obtain a pointer to
it. In MFC, the function that can be used to access system menu is CWnd::GetSystemMenu(…). Please note

45
Chapter 2. Menu

that we must call this function for a window that has an attached system menu. For an SDI or MDI
application, system menu is attached to the mainframe window. For a dialog box based application, the
system menu is attached to the dialog window.
Unlike user implemented commands, system commands (commands on the system menu) are sent
through WM_SYSCOMMAND rather than WM_COMMAND message. If we implement message handlers to receive
system commands, we need to use ON_WM_SYSCOMMAND macro.

Bitmap Menu Item


From the samples in the previous sections, we already know how to customize a menu item: we can set
check, remove check, change text dynamically. We can also use bitmaps to represent its checked and
unchecked states. Besides above features, a menu item can be modified to display a bitmap instead of a text
string. This can make the application more attractive, because sometimes images are more intuitive than
text strings.
Sample 2.5\Menu demonstrates the two techniques described above, it is based on sample 2.4\Menu.
In this sample, one of the system menu items (a separator) is changed to bitmap menu item. If we execute
this “bitmap command”, a message box will pop up. Also, another new command is added to the system
menu, it allows the user to resume the original system menu.

New Functions
Function CWnd::GetSystemMenu(…) has only one Boolean type parameter:

CMenu *CWnd::GetSystemMenu(BOOL bRevert);

Although we can call this function to obtain a pointer to the system menu and manipulate it, the
original default system menu can be reverted at any time by calling this function and passing a TRUE value
to its bRevert parameter. In this case, function’s returned value has no meaning and should not be treated
as a pointer to a menu object. We need to pass FALSE to this parameter in order to obtain a valid pointer to
the system menu.
Function CMenu::ModifyMenu(…) allows us to change any menu item to a separator, a sub-menu, a
bitmap menu item. It can also be used to modify a menu item’s text. This member function has two
versions:

BOOL CMenu::ModifyMenu
(
UINT nPosition, UINT nFlags, UINT nIDNewItem=0, LPCTSTR lpszNewItem = NULL
);
BOOL CMenu::ModifyMenu
(
UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp
);

The first version of this function allows us to change a menu item to a text item, a separator, or a sub-
menu. The second version allows us to change a menu item to a bitmap item. For the second version,
parameter nIDNewItem specifies the new command ID, and parameter pBmp is a pointer to a CBitmap object,
which must contain a valid bitmap resource.

Menu Modification
In the sample application, a bitmap resource ID_BITMAP_QUESTION is prepared for implementing
bitmap menu item. This bitmap contains a question mark. There is no restriction on the bitmap size,
because the size of menu item will be adjusted automatically to let the image fit in.
To load the image, a new CBitmap type variable m_bmpQuestion is declared in class CMainFrame, and
bitmap resource ID_BITMAP_QUESTION is loaded in the constructor of class CMainFrame:

class CMainFrame : public CFrameWnd


{
……
protected:
CStatusBar m_wndStatusBar;

46
Chapter 2. Menu

CToolBar m_wndToolBar;
CBitmap m_bmpQuestion;
……
};

CMainFrame::CMainFrame()
{
m_bmpQuestion.LoadBitmap(IDB_BITMAP_QUESTION);
}

The pointer to the system menu is obtained in function CMainFrame::OnCreate(…). First, system
menu’s fifth item (a separator) is modified to a bitmap menu item, then another new command “Resume
standard system menu” is inserted before this bitmap menu item. The IDs of the two commands are
ID_QUESTION and ID_RESUME respectively.
Although we can use any numerical values as the command IDs of the newly added menu items, they
should not be used by other resources of the application. The best way to prevent this from happening is to
generate two new string resources in the application, and use ID_QUESTION and ID_RESUME as their
symbolic IDs. Because Developer Studio will always allocate unused values for new resources, we can
avoid sharing IDs with other resources by using this method.
The following shows how we access the system menu and make changes to its items:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)


{
CMenu *ptrMenu;
……
ptrMenu=GetSystemMenu(FALSE);
ptrMenu->ModifyMenu(5, MF_BYPOSITION, ID_QUESTION, &m_bmpQuestion);
ptrMenu->InsertMenu(5, MF_BYPOSITION, ID_RESUME, "Resume standard system menu");

return 0;
}

Message Mapping for System Command


In order to trap events for the system menu, we need to handle message WM_SYSCOMMAND. We can map
this message to our member function by using macro ON_WM_SYSCOMMAND. The format of the message
handler is:

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

And the format of mapping macro is:

ON_WM_SYSCOMMAND()

Of course, we can ask Class Wizard to do the mapping for us. Before using it to add the above message
handler to CMainFrame class, we need to make following changes to the settings of Class Wizard: first click
“Class info” tab of the Class Wizard, then select “Window” from the window “Message filter” (Figure 2-
4). The default message filter for CMainFrame frame window is “Topmost frame”, and WM_SYSCOMMAND will
not be listed in the message list. After this modification, we can go back to “Message Maps” page, and
choose “WM_SYSCOMMAND” from messages window. To add the message handler, we simply need to
click “Add function” button (make sure the settings in other windows are correct). After this, the new
function OnSysCommand(…) will be added to the application.
Here is how this function is declared in class CMainFrame:

class CMainFrame
{
……
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
……
};

The message mapping is as follows:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
……

47
Chapter 2. Menu

ON_WM_SYSCOMMAND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Select “Window” to let


WM_SYSCOMMAND be
included in the message list

Figure 2-4. Customize message filter

Message handler CMainFrame::OnSysCommand(…) has two parameters, first of which is the command
ID, and the second is LPARAM message parameter. In our case, we only need to use the first parameter,
because it tells us which command is being executed. If the command is ID_RESUME, we need to call
function CMenu::GetSystemMenu(…) to resume the default system menu; if the command is ID_QUESTION,
we pop up a message box:

void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam)


{
if(nID == ID_RESUME)GetSystemMenu(TRUE);
if(nID == ID_QUESTION)AfxMessageBox("Question");
CFrameWnd::OnSysCommand(nID, lParam);
}

2.6 Owner-Draw Menu

When we highlight a bitmap menu item by selecting it using mouse, the bitmap will be inversed. We
have no way of modifying this property because function CMenu::ModifyMenu(…) requires only one
bitmap, which will be used to implement menu item’s normal state. The other states of a bitmap menu item
will be drawn using the default implementations. Normally this is good enough. However, sometimes we
may want to use different bitmaps to represent a menu item’s different states: selected, unselected, checked,
unchecked, grayed or enabled.
Also, sometimes we may want to paint a menu item dynamically. Suppose we need to create a “Color”
menu item: the menu item represents a color that the user can use, and this color can be modified to
represent any available color in the system. To implement this type of menu item, we can paint it using the
currently selected color. In this case it is not appropriate to create the menu item using bitmap resources:
there may exist thousands of available colors in the system, and it is just too inconvenient to prepare a
bitmap resource for each possible color.
The owner-draw menu can help us build more powerful user interface. With this type of menu, we can
draw the menu items dynamically, using different bitmaps to represent different states of the menu item.
We can also change the associated bitmaps at any time.

Overriding Two Functions


The implementation of owner draw menu is not very difficult, all we need is to override two member
functions of class CMenu: CMenu::MeasureItem(…) and CMenu::DrawItem(…).

48
Chapter 2. Menu

By default, the menu is drawn by the system. We can change this attribute by specifying MF_OWNERDRAW
style for a menu. Any menu with this style will be drawn by its owner. Since a menu’s owner is usually the
mainframe window (in SDI and MDI applications), we can add code to class CMainFrame to implement
dynamic menu drawing. Actually, the menu drawing has two associated messages: WM_MEASUREITEM and
WM_DRAWITEM. When a menu item with MF_OWNERDRAW style needs to be drawn, the menu sends out the
above two messages to the mainframe window. The mainframe window finds out the pointer to the
corresponding menu and calls functions CMenu::MeasureItem(…) and CMenu::DrawItem(…). Here, the first
function is used to retrieve the dimension of the menu item, which can be used to calculate its layout. The
second function implements menu drawing. We need to override it in order to implement custom interface.
One simple and most commonly used way to provide graphic interface is to prepare a bitmap resource
then load and draw it at run time. Please note that this is different from preparing a bitmap resource and
calling CMenu::ModifyMenu(…) to associate the bitmap with a menu item. If we implement drawing by
ourselves, we can manipulate drawing details. For example, when drawing the bitmap, we can change the
size of the image, add a text over it. If we assign the bitmap to the menu item, we lose the control over the
details of painting the bitmap.

Drawing a Bitmap
To draw a bitmap, we need to understand some basics on graphic device interface (GDI). This topic
will be thoroughly discussed from chapter 8 through 12, here is just a simple discussion on bitmap drawing.
In Windows operating system, when we want to output objects (a pixel, a line or a bitmap) to the screen,
we can not write directly to the screen. Instead, we must write to the device context (DC). A device context
is a data structure containing information about the drawing attributes of a device (typically a display or a
printer). We can use DC to write text, draw pixels, lines and bitmaps to the devices. In MFC the device
context is supported by class CDC, which has many functions that can let us draw different types of objects.
We can implement bitmap drawing by obtaining a target DC and calling member functions of CDC. The
target DC is usually obtained from a window.
A DC can select a lot of GDI objects, such pen, brush, font and bitmap. Pen can be different type of
pens, and brush can be different types of brushes. A DC can select any pen or brush as its current tool, but
at any time, a DC can select only one pen and one brush. If we want to draw a pixel or a line, we can select
the appropriate pen into the target DC and use it to implement drawing. A DC can also select bitmap for
drawing. However, to paint a bitmap, we can not select it into the target DC and draw it directly. The
normal way of painting a bitmap is to prepare a compatible memory DC (which is a block of memory with
the same attributes of the target DC), select the bitmap into the memory DC, and copy the bitmap from the
memory DC to the target DC.
We will learn more about DC and bitmap drawing in later chapters. For the time being, we can neglect
the drawing details.

Deriving a New Class from CMenu


Sample 2.6\Menu demonstrates owner-draw menu implementation. It is base on sample 2.5\Menu. In
this sample menu items ID__POPUPITEM1 and ID__POPUPITEM2 of the dynamic menu are implemented as
owner-draw menu, their menu items are painted using different bitmaps.
Like sample 2.5\Menu, some new images are prepared as bitmap resources. In the sample, the newly
added bitmaps are IDB_BITMAP_QUESTIONSEL, IDB_BITMAP_SMILE and IDB_BITMAP_SMILESEL. We will use
IDB_BITMAP_SMILE and IDB_BITMAP_SMILESEL to implement owner-draw menu item ID__POPITEM1, and
use IDB_BITMAP_QUESTION to implement ID__POPITEM2.
First, we need to override class CMenu. In the sample, a new class MCMenu is derived from CMenu, in this
class, we declare four CBitmap type variables that will be used to load bitmaps for menu drawing. Also,
functions CMenu::MeasureItem(…) and CMenu::DrawItem(…) are overridden:

class MCMenu : public CMenu


{
protected:
CBitmap m_bmpQuestion;
CBitmap m_bmpQuestionSel;
CBitmap m_bmpSmile;
CBitmap m_bmpSmileSel;

49
Chapter 2. Menu

public:
MCMenu();
virtual ~MCMenu();
virtual void MeasureItem(LPMEASUREITEMSTRUCT);
virtual void DrawItem(LPDRAWITEMSTRUCT);
};

In the constructor of class MCMenu, four bitmaps are loaded:

MCMenu::MCMenu() : CMenu()
{
m_bmpQuestion.LoadBitmap(IDB_BITMAP_QUESTION);
m_bmpQuestionSel.LoadBitmap(IDB_BITMAP_QUESTIONSEL);
m_bmpSmile.LoadBitmap(IDB_BITMAP_SMILE);
m_bmpSmileSel.LoadBitmap(IDB_BITMAP_SMILESEL);
}

Overriding Function CMenu::MeasureItem(…)


Next we need to override function CMenu::MeasureItem(…). It has only one parameter, which is a
pointer to MEASUREITEMSTRUCT type object. Structure MEASUREITEMSTRUCT is used to inform system the
dimension of the owner-draw menu and other controls. There are three important members that will be
used: itemWidth and itemHeight, which represent width and height of the menu item; itemData, from
which we know the type of the menu item that is being inquired.
The program can assign special data to an owner-draw menu item when it is being created. When the
mainframe window calls the overridden functions to inquire the item’s attributes or paint the menu, the data
will be passed through itemData member of structure MEASUREITEMSTRUCT or DRAWITEMSTRUCT
(DRAWITEMSTRUCT will be passed to function CMenu::DrawItem(…)). By examining this member in the
overriden functions, we know what type of menu item we are working with.
Class CBitmap has a member function that can be used to obtain the size of a bitmap:
CBitmap::GetBitmap(…). We need to prepare a BITMAP type object and pass its pointer to this function.
After calling this function, the structure will be filled with a lot of information about the bitmap (not only
the image dimension). In this sample, we need to use only two members of BITMAP structure: bmWidth and
bmHeight, which represent a bitmap’s width and height.
The following is the overridden function of CMenu::MeasureItem(…):

void MCMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)


{
BITMAP bm;

switch(lpMeasureItemStruct->itemData)
{
case MENUTYPE_SMILE:
{
m_bmpSmile.GetBitmap(&bm);
break;
}
case MENUTYPE_QUESTION:
{
m_bmpQuestion.GetBitmap(&bm);
break;
}
}
lpMeasureItemStruct->itemWidth=bm.bmWidth;
lpMeasureItemStruct->itemHeight=bm.bmHeight;
}

In this function, MENUTYPE_SMILE and MENUTYPE_QUESTION are user-defined macros that represent the
type of menu items. First we examine member itemData and decide the type of the menu item. For
different types of menu items, the corresponding bitmap sizes are retrieved and set to members itemWidth
and itemHeight of structure MEASUREITEMSTRUCT. This size will be sent to the system and be used to
calculate the layout of the whole sub-menu.

50
Chapter 2. Menu

Overriding Function CMenu::DrawItem(…)


ow we need to override another member function of CMenu: CMenu::DrawItem(…). This function uses a
different structure, DRAWITEMSTRUCT. It is used to inform us the state of the menu item, pass the target DC,
and tell us the position where we can draw the customized menu. The following table lists some of its
important members:

Member Explanation
CtlType Tells what kind of object is being drawn. Since this structure is also used to for owner-draw
button, combo box, list box, we need to check this member and make sure it is ODT_MENU.
hDC A handle to the target device context. From this handle, we can obtain a CDC type pointer to
the target device context.
itemState Specifies the state of the current menu item. It could be ODS_CHECKED, ODS_DISABLED,
ODS_FOCUS, ODS_GRAYED or ODS_SELECTED, whose meanings are easy to guess. In the sample
application, we take care only default and checked states.
itemData Same as structure MEASUREITEMSTRUCT, we need to check this member to decide what kind of
owner-draw menu item is being painted.
rcItem A rectangle specifies where we should implement drawing.

The following is the overridden function:

void MCMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)


{
CDC *ptrDC;
CDC dcMem;
CBitmap *ptrBmpOld;
CBitmap *ptrBmp;
CRect rect;

if(!(lpDrawItemStruct->CtlType & ODT_MENU))


{
CMenu::DrawItem(lpDrawItemStruct);
return;
}

ptrDC=CDC::FromHandle(lpDrawItemStruct->hDC);
dcMem.CreateCompatibleDC(ptrDC);

if(lpDrawItemStruct->itemState & ODS_SELECTED)


{
switch(lpDrawItemStruct->itemData)
{
case MENUTYPE_SMILE:
{
ptrBmp=&m_bmpSmileSel;
break;
}
case MENUTYPE_QUESTION:
{
ptrBmp=&m_bmpQuestionSel;
break;
}
}
}
else
{
switch(lpDrawItemStruct->itemData)
{
case MENUTYPE_SMILE:
{
ptrBmp=&m_bmpSmile;
break;
}
case MENUTYPE_QUESTION:
{
ptrBmp=&m_bmpQuestion;
break;
}

51
Chapter 2. Menu

}
}

ptrBmpOld=dcMem.SelectObject(ptrBmp);
rect=lpDrawItemStruct->rcItem;
ptrDC->BitBlt
(
rect.left,
rect.top,
rect.Width(),
rect.Height(),
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(ptrBmpOld);
}

First, we check if the item is a menu. If not, we call the same function implemented by the base class
and return. If so, first we obtain a CDC type pointer to the target device by calling function
CDC::FromHandle(…). Then, we create a compatible memory DC with target DC, which will be used to
draw the bitmap. Next, we check the menu item’s state and type by looking at itemState and itemData
members of structure DRAWITEMSTRUCT, and choose different bitmaps according to different situations. At
last we select the appropriate bitmap into the memory DC, copy the bitmap to target device using function
CDC::BitBlt(…). This function has many parameters: the first four are position and size on the target
device; the fifth parameter is the pointer to the memory DC; the last parameter specifies drawing mode
(SRCCOPY will copy the source bitmap to the target device). Finally, we must select the bitmap out of the
memory DC, and resume its original state.

Using the New Class


Now we can use this class to implement owner-draw menu. In the sample application, the first two
items of dynamic menu IDR_MENU_POPUP are implemented with this style.
First a new MCMenu type variable m_menuModified is declared in class CMenuDoc (the original
m_menuSub variable remain unchanged):

class CMenuDoc : public CDocument


{
protected:
CMenu m_menuSub;
MCMenu m_menuModified;
……
}

The original variable m_menuSub is used to load the menu resource, whose first sub-menu is obtained
by calling function CMenu::GetSubMenu(…) and attached to variable m_menuModified. By doing this, the
system will call the member functions of class MCMenu instead of CMenu when the owner-draw menu needs
to be painted. To change a menu item’s default style, function CMenu::ModifyMenu(…) is called and
MF_OWNERDRAW flag is specified:

CMenuDoc::CMenuDoc()
{
CMenu *ptrMenu;

m_menuSub.LoadMenu(IDR_MENU_POPUP);
m_bmpCheck.LoadBitmap(IDB_BITMAP_CHECK);
m_bmpUnCheck.LoadBitmap(IDB_BITMAP_UNCHECK);
ptrMenu=m_menuSub.GetSubMenu(0);
m_menuModified.Attach(ptrMenu->GetSafeHmenu());
ptrMenu->ModifyMenu
(
0,
MF_BYPOSITION | MF_ENABLED | MF_OWNERDRAW, ID__POPUPITEM1,
(LPCTSTR)MENUTYPE_SMILE
);
ptrMenu->ModifyMenu
(
1,

52
Chapter 2. Menu

MF_BYPOSITION | MF_ENABLED | MF_OWNERDRAW, ID__POPUPITEM2,


(LPCTSTR)MENUTYPE_QUESTION
);
m_bSubMenuOn=FALSE;
}

In the above code, menu IDR_MENU_POPUP is loaded into m_menuSub, then the first sub-menu is obtained
and attached to variable m_menuModified. Here, function CMenu::Attach(…) requires a HMENU type
parameter, which can be obtained by calling function CMenu::GetSafeHmenu(). When calling function
CMenu::ModifyMenu(…), we pass integer instead of string pointer to its last parameter. This does not matter
because the integer provided here will not be treated as memory address, instead, it will be passed to
itemData member of structure MEASUREITEMSTRUCT (and DRAWITEMSTRUCT) to indicate the type of the
owner-drawn menu items.
Because the popup menu is attached to variable CMenuDoc::m_menuModified, we need to detach it
before application exits. The best place of implementing this is in class MCMenu’s destructor, when the menu
object is about to be destroyed:

MCMenu::~MCMenu()
{
Detach();
}

Now we can compile the sample project and execute it. By executing command Edit | Insert Dynamic
Menu and expanding Dynamic Menu then selecting the first two menu items, we will see that both
selected and unselected states of them will be implemented by our own bitmaps.
Bitmap is not restricted to only indicating selected and normal menu states. With a little effort, we
could also use bitmap to implement other menu states: grayed, checked, and unchecked. This will make our
menu completely different from a menu implemented by plain text.

2.7 Changing the Whole Menu Dynamically


If we write MDI application, we can see that the main menu may change with different type of views.
Generally, when there is no client window opened, the mainframe menu will have only three sub-menus
(File, View, Help). After the user opens a client window, the mainframe menu will be replaced with a new
menu that has more sub-menus. Although we could call CMenu::ModifyMenu(…) to implement this, it is not
efficient because with this method, we have to the change menu items one by one. Actually, there is
another easy way to change the whole menu dynamically: we can prepare a new menu resource, use it to
replace the menu currently associated with the window at run time.
Class CWnd has a member function that can be used to set a window’s menu dynamically:
CWnd::SetMenu(…). The function has only one parameter, which is a CMenu type pointer. By using this
function, we can change an application’s mainframe menu at any time we want. Sample 2.7\Menu
demonstrates this technique. It is a standard SDI application generated by Application Wizard with all
default settings. In the sample, besides mainframe menu IDR_MAINFRAME, a new menu resource
IDR_MAINFRAME_ACTIVE is prepared in the application. This menu has some new sub-menus (Options,
Properties, Settings). For the purpose of demonstration, none of these sub-menus contains menu items.
The default menu will be changed to this menu after we execute File | New or File | Open command. If we
execute File | Close command from menu IDR_MAINFRAME_ACTIVE, the default menu will be resumed.
Three WM_COMMAND message handlers for commands ID_FILE_NEW, ID_FILE_OPEN and ID_FILE_CLOSE
are generated using class wizard. The member functions are named OnFileNew, OnFileOpen and
OnFileClose. In the first two functions, we change the mainframe menu to IDR_MAINFRAME_ACTIVE, and in
the third function, we change the menu back to IDR_MAINFRAME:

void CMenuDoc::OnFileNew()
{
CMenu menu;

menu.LoadMenu(IDR_MAINFRAME_ACTIVE);
AfxGetMainWnd()->SetMenu(&menu);
AfxGetMainWnd()->DrawMenuBar();

53
Chapter 2. Menu

menu.Detach();
}

void CMenuDoc::OnFileOpen()
{
CMenu menu;

menu.LoadMenu(IDR_MAINFRAME_ACTIVE);
AfxGetMainWnd()->SetMenu(&menu);
AfxGetMainWnd()->DrawMenuBar();
menu.Detach();
}

void CMenuDoc::OnFileClose()
{
CMenu menu;

menu.LoadMenu(IDR_MAINFRAME);
AfxGetMainWnd()->SetMenu(&menu);
AfxGetMainWnd()->DrawMenuBar();
menu.Detach();
}

In the above three member functions, we use a local variable menu to load the menu resource. It will be
destroyed after the function exits. So before the variable goes out of scope, we must call function
CMenu::Detach() to release the loaded menu resource so that it can continue to be used by the application.
Otherwise, the menu resource will be destroyed automatically.

Summary
1) In order to execute commands, we need to handle message WM_COMMAND. In order to update user
interfaces of menu, we need to handle message UPDATE_COMMAND_UI.
1) To implement right-click menu, we need to first prepare a menu resource, then trap WM_RBUTTONDOWN
message. Within the message handler, we can use CMenu type variable to load the menu resource, and
call CMenu::TrackPopupMenu(…) to activate the menu and track mouse activities. Before the menu is
shown, we can call functions CMenu::EnableMenuItem(…) and CMenu::CheckMenuItem(…) to set the
states of the menu items.
1) To add or remove a menu item dynamically, we need to call functions CMenu::InsertMenu(…) and
CMenu::RemoveMenu(…).
1) With function CMenu::SetMenuItemBitmaps(…), we can use bitmap images to implement checked and
unchecked states for a menu item.
1) A window’s standard menu can be obtained by calling function CWnd::GetMenu(), and the
application’s system menu can be obtained by calling function CWnd::GetSysMenu(…).
1) We can change a normal menu item to a bitmap menu item, a separator, or a sub-menu by calling
function CMenu::ModifyMenu(…).
1) Owner-draw menu can be implemented by setting MF_OWNERDRAW style then overriding functions
CMenu::MeasureItem(…) and CMenu::DrawItem(…).
1) We can change the whole menu attached to a window by calling function CWnd::SetMenu(…).

54
Chapter 3. Splitter Window

Chapter 3

Splitter Window

A
splitter window resides within the frame window. It is divided into several panes, each pane can
have a different size. Splitter window provides the user with several different views for monitoring
data contained in the document at the same time. Normally, the size of each pane can be adjusted
freely, this gives the user a better view of data. There are two types of splitter windows: Dynamic
Splitter Window and Static Splitter Window. For a dynamic splitter window, all views within the splitter
window are of the same type. The user can create new panes or remove old panes on the fly. For a static
splitter window, the views could be of different types and the number of panes has to be fixed at the
beginning. In this case, the user can not add or delete views after the program has started.
Both SDI and MDI applications can have splitter windows. In an SDI application, the splitter window
is embedded in the mainframe window. In an MDI application, it is embedded in the child frame window.

3.1 Implementing Static Splitter Windows


In MFC, class CSplitterWnd is used to implement window splitting. To split a window into several
panes, we must first declare a CSplitterWnd type variable in the frame window class. One CSplitterWnd
can divide window into M×N sub-panes. The splitter window can be nested, which means we can further
split a single pane into several sub panes by using another CSplitterWnd type variable. So if we want to
create an unevenly divided splitter window, we need to declare more than one CSplitterWnd type
variables.
For example, if we want to create a splitter window that has two rows, the first row has two columns
and the second row has two columns, we need to first split the client area into a 1×2 splitter window, then
split the first row into a 2×1 splitter window (Figure 3-1).

Divide the window Divide row 0 into column


into row 0 and row 1 0 and column 1

Figure 3-1. Create irregularly divided splitter window

To create static splitter window, first we need to declare CSplitterWnd type variable(s) in the frame
window class, then in frame window’s OnCreateClient(…) member function, call functions
CSplitterWnd::CreateStatic(…) and CSplitterWnd::CreateView(…). Here, function CSplitterWnd::
CreateStatic(…) is used to split the window into several panes and CSplitterWnd::CreateView(…) is
used to attach a view to each pane.

55
Chapter 3. Splitter Window

Sample application 3.1\Sdi\Spw demonstrates how to implement splitter window. It is generated by


Application Wizard, with all settings set to default ones. Four main classes used to implement the
application are CSpwApp, CMainFrame, CSpwDoc and CSpwView.
Each pane of the splitter window must be attached with a view in order to make it work. The view
could be implemented by deriving class from any of the standard view classes: CView, CScrollView,
CRichEditView, CListView, CTreeView etc. In the sample, besides the default view CSpwView created by
the Application Wizard, two other classes are derived from CFormView and CEditView, which will be used
to implement different panes of the splitter window.
Class CFormView is a standard MFC class that can be used to create view window from dialog
template. A CFormView class must have a corresponding dialog template resource, which will be used to
create the view. To implement a form view, we must first design dialog template, then derive a new class
from CFormView.
In the sample, the dialog template used to implement the form view is IDD_DIALOG_VIEW. Its styles are
set to “Child” and “No border”, this is exactly the same with that of dialog bar (This is because both splitter
window and dialog bar must be child windows). The dialog template contains a static text control and a
multiple-line edit box. By double clicking on the dialog template resource, we will be prompted to add a
new class for it. In this case the template resource ID will be automatically selected to be used by the new
class. In the sample application, the new class is named CSpwFView. If the dialog template is not open when
adding this new class, we must select the dialog ID by ourselves (Figure 3-2).

Input the new


class name

Select the header and


implementation file name

Select base class

If the dialog template resource


is not open, we must select the
ID manually

Figure 3-2. Derive class from CFormView

The other pane of the splitter window is implemented using edit view. The new class for this window
is derived from CEditView, and its name is CSpwEView.
To split a window, we need to call function CSplitterWnd::CreateStatic(…), which has five
parameters:

BOOL CSplitterWnd::CreateStatic
(
CWnd *pParentWnd, int nRows, int nCols,
DWORD dwStyle=WS_CHILD | WS_VISIBLE,
UINT nID=AFX_IDW_PANE_FIRST
);

The first parameter pParentWnd is a CWnd type pointer that points to the parent window. Because a
splitter window is always the child of frame window, this parameter can not be set to NULL. The second
and third parameters specify the number of rows and columns the splitter window will have. The fourth
parameter dwStyle specifies the styles of splitter window, whose default value is WS_CHILD | WS_VISIBLE.
The fifth parameter, nID, identifies which splitter window is being created. This is necessary because
within one frame window, we can create several nested splitter windows. For the root splitter window (The
splitter window whose parent window is the frame window), this ID must be AFX_IDW_PANE_FIRST. For
other nested splitter windows, this ID need to be obtained from the parent splitter windows by calling
function CSplitterWnd::IdFromRowCol(…), and passing appropriate column and row coordinates to it. The
following is the format of this function:

56
Chapter 3. Splitter Window

int CSplitterWnd::IdFromRowCol(int row, int col);

To attach a specific view to a pane, we need to call function CSplitterWnd::CreateView(…), which


also has five parameters:

BOOL CSplitter.CreateView
(
int row, int col,
CRuntimeClass *pViewClass, SIZE sizeInit, CCreateContext *pContext
);

The first two parameters specify which pane is being created. The third parameter specifies what kind
of view will be used to create this pane. Usually macro RUNTIME_CLASS must be used to obtain a
CRuntimeClass type pointer. The fifth parameter is a creation context used to create the view. Within
CMainFrame::OnCreateClient(…), the creation context is passed through the second parameter of this
function.
In the sample application, we first use m_wndSpMain to call function CSplitterWnd::
CreateStatic(…) to split the client window into a 2×1 splitter window. Then, we use this variable to call
CSplitterWnd::CreateView(…) and pass two 0s to the first two parameters of this function (This specifies
(0, 0) coordinates). This will attach a new view to the left pane of the splitter window. Next we use
m_wndSpSub to call CSplitterWnd::CreateStatic(…) to further split the right pane into a 1×2 splitter
window, and call CSplitterWnd::CreateView(…) twice to create views for the two panes. At last, instead
of calling function CMainFrame::OnCreateClient(…), a TRUE value is returned. This can prevent the
default client window from being created.
The following steps show how the static splitter window is implemented in the sample:

1) Declare two CSplitterWnd type variables in class CMainFrame:

class CMainFrame : public CFrameWnd


{
……
protected:
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CSplitterWnd m_wndSpMain;
CSplitterWnd m_wndSpSub;
……
}

Variable m_wndSpMain will be used to split the mainframe client window into a 2×1 splitter window,
and m_wndSpSub will be used to further split the right column into a 1×2 splitter window.
2) In function CMainFrame::OnCreateClient(…), create splitter windows and attach views to each pane:

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)


{
if
(
!m_wndSpMain.CreateStatic
(
this, 1, 2, WS_CHILD | WS_VISIBLE, AFX_IDW_PANE_FIRST
)
)
{
TRACE0("Failed to CreateStaticSplitter\n");
return FALSE;
}

if
(
!m_wndSpMain.CreateView
(
0, 0, RUNTIME_CLASS(CSpwView), CSize(100, 100), pContext
)
)
{
TRACE0("Failed to create first pane\n");
return FALSE;

57
Chapter 3. Splitter Window

if
(
!m_wndSpSub.CreateStatic
(
&m_wndSpMain, 2, 1,
WS_CHILD | WS_VISIBLE, m_wndSpMain.IdFromRowCol(0, 1)
)
)
{
TRACE0("Failed to create nested splitter\n");
return FALSE;
}

if
(
!m_wndSpSub.CreateView
(
0, 0, RUNTIME_CLASS(CSpwFView), CSize(50, 50), pContext
)
)
{
TRACE0("Failed to create second pane\n");
return FALSE;
}

if
(
!m_wndSpSub.CreateView
(
1, 0, RUNTIME_CLASS(CSpwEView), CSize(50, 50), pContext
)
)
{
TRACE0("Failed to create third pane\n");
return FALSE;
}

return TRUE;
}

For an MDI application, everything is almost the same except that here CChildFrame replaces class
CMainFrame. We can create an MDI application, declare m_wndSpMain and m_wndSpSub variables in class
CChildFrame, and add code to CChildFrame::OnCreateClient(…) to create splitter windows. The code
required here is exactly the same with implementing splitter window in an SDI application. Sample
3.1\MDI\Spw demonstrates this.

3.2 Dynamic Splitter Window


Once we understand how to create static splitter window, it is easier for us to create dynamic splitter
window because it takes fewer steps. For a dynamic splitter window, all panes are created to be the same
type of view, so there is no need to call function CSplitterWnd::CreateView(…) for each individual pane.
Also, instead of calling CSplitterWnd::CreateStatic(…), we need to call function CSplitterWnd::
Create(…) to create dynamic splitter window within function CFrameWnd::OnCreateClient(…). The
following is the format of function CSplitterWnd::Create(…):

BOOL CSplitterWnd::Create
(
CWnd* pParentWnd,
int nMaxRows, int nMaxCols,
SIZE sizeMin,
CCreateContext* pContext,
DWORD dwStyle=WS_CHILD | WS_VISIBLE |WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT,
UINT nID=AFX_IDW_PANE_FIRST
);

The difference between function CSplitterWnd::CreateStatic(…) and CSplitterWnd::Create(…)


is that when creating dynamic splitter window, we need to specify the maximum number of rows and

58
Chapter 3. Splitter Window

columns. The maximum values of nMaxRows and nMaxCols parameters are both 2, which means that a
window can be split to have at most 2×2 panes.
The Application Wizard has a built-in feature to add dynamic splitter window to the applications. In
step 4 of the Application Wizard, if we press “Advanced…” button, an “Advanced Options” property sheet
will pop up. By clicking “Window styles” tab then checking “Use split window” check box, code will be
automatically added to the application for implementing dynamic split window (static splitter window can
not be created this way).
It is also simple to implement splitter window manually. Like creating static split window, first we
need to declare a CSplitterWnd type variable in class CMainFrame (In MDI applications, we need to do this
in class CChildFrame). Then we can use Class Wizard to override function OnCreateClient(…). Within the
overridden function, we can call CSplitterWnd::Create(…) to create splitter window.
Sample 3.2\Spw demonstrates how to create dynamic splitter window in an SDI application. The
application is created from Application Wizard with all default settings. Then a new variable m_wndSp is
declared in class CMainFrame, which will be used to implement the splitter window. In function
CMainFrame::OnCreateClinet(…), the splitter window is created as follows:

BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,


CCreateContext* pContext)
{
if
(
m_wndSp.Create
(
this, 2, 2, CSize(10, 10), pContext
) == FALSE
)return FALSE;
return TRUE;
}

In the above code, we did not pass any value to dwStyle and nID parameters of function
CSplitterWnd::Create(…),so the default values are used.

3.3 Customizing the Behavior of Split Bar


The behavior of a dynamic splitter window is different from that of a static splitter window. For the
dynamic splitter window, panes could be dynamically created by double clicking on the split boxes (Figure
3-3). After new panes are added, one or more split bars will appear. If the user double clicks any of the split
bar, one f the two panes divided by that split bar will be deleted (Figure 3-4). We can examine the sample
applications we’ve created to see the difference between static splitter window and dynamic splitter
window.

Split box

Split box

Figure 3-3. Double clicking on any of the split boxes will divide
the window into panes dynamically

59
Chapter 3. Splitter Window

This behavior could be customized. For example, sometimes by double clicking on the split bar, we
want to resize the two panes instead of deleting one of them. This feature gives the user much convenience
for changing the size of each pane bit by bit.

Split bar

Figure 3-4. Double clicking on the split bar will delete one of the
two panes divided by the split bar

Splitter Window Layout


We need to override the following two member functions of class CSplitterWnd in order to implement
this feature: CSplitterWnd::DeleteRow(…) and CSplitterWnd::DeleteColumn(…). When the user double
clicks on the split bar, one of the two functions will be called to delete a row or column dynamically. In
order to customize this behavior, after the split bar is clicked, we can first change the size of each pane,
then judge if the size of one pane is smaller than its minimum size. If so, we call the default implementation
of the corresponding function to delete one row or column.
To change a pane’s size, we need to call function CSplitterWnd::SetColumnInfo(…) and
CSplitterWnd::SetRowInfo(…). The current size of a pane could be obtained by their counterpart
functions CSplitterWnd::GetColumnInfo(…) and CSplitterWnd::GetRowInfo(…). The following shows
the formats of the above four functions:

void CSplitterWnd::SetColumnInfo(int col, int cxIdeal, int cxMin);


void CSplitterWnd::SetRowInfo(int row, int cyIdeal, int cyMin);
void CSplitterWnd::GetColumnInfo(int col, int& cxCur, int& cxMin);
void CSplitterWnd::GetRowInfo(int row, int& cyCur, int& cyMin);

In the above functions, parameters row and col are used to identify a pane with specified row and
column indices, cyIdeal and cxIdeal are the ideal size of a pane, cyMin and cxMin indicate minimum size
of it.
When the splitter window is being displayed, each pane’s dimension is decided from its ideal size.
According to the current size of the frame window, some panes may be set to their ideal sizes, but some
may not (This depends on how much space is left for that pane). In any case, a pane’s actual size should not
be smaller than its minimum size. This is why we need both ideal size and minimum size to set a row or
column’s dimension.
The number of rows and columns a splitter window currently has can be obtained by calling other two
member functions of CSplitterWnd:

int CSplitterWnd::GetRowCount();
int CSplitterWnd::GetColumnCount();

After we call function CSplitterWnd::SetColumnInfo(…) or CSplitterWnd::SetRowInfo(…), the old


layout will not change until we call function CSplitterWnd::RecalcLayout() to update the splitter
window. The system will re-calculate the layout for each pane according to their new sizes (both ideal size
and minimum size), and the split bar will be moved to a new position according to the new layout.

60
Chapter 3. Splitter Window

Overriding CSplitterWnd::DeleteRow(…) and CSplitterWnd::


DeleteColumn(…)
Sample 3.3\Spw is based on sample 3.2\Spw. In the new sample, the behavior of the split bar is
modified: if the user double clicks on it, it will move a small step downward (for horizontal split bar) or
rightward (for vertical split bar). A pane will be deleted after it reaches its minimum size.
In the sample application, first a new class MCSplitterWnd is derived from class CSplitterWnd:

class MCSplitterWnd : public CSplitterWnd


{
public:
void DeleteRow(int);
void DeleteColumn(int);
};

The class does nothing but overriding two functions. The implementation of function MCSplitterWnd
::DeleteRow(…) is listed as follows:

void MCSplitterWnd::DeleteRow(int rowDelete)


{
int nNumRows;

nNumRows=GetRowCount();
if(nNumRows < 2)
{
CSplitterWnd::DeleteRow(rowDelete);
}
else
{
int nCyCur, nCyMin;

GetRowInfo(0, nCyCur, nCyMin);


nCyCur+=ONE_STEP_MOVE;
SetRowInfo(0, nCyCur, nCyMin);

GetRowInfo(1, nCyCur, nCyMin);


nCyCur-=ONE_STEP_MOVE;
if(nCyCur < nCyMin)
{
CSplitterWnd::DeleteRow(rowDelete);
}
else
{
SetRowInfo(1, nCyCur, nCyMin);
RecalcLayout();
}
}
}

Since the maximum number of rows that can be implemented in a dynamic split window is 2, we will
call the default implementation of this function (the corresponding function of the base class) if the number
of rows is not 2. Otherwise, we first obtain the size of upper pane (pane 0), enlarge its vertical size, and set
its current size. Then the current size of lower pane is reduced, if its ideal size is smaller than its minimum
size after change, we simply call function CSplitterWnd::DeleteRow(…) to delete this row. If the panes are
resized instead of being deleted, we call function CSplitterWnd::RecalcLayout() to update the new
layout.
Function MCSplitterWnd::DeleteColumn(int colDelete) is implemented in the same way, except
that here we call all the functions dealing with column instead of row.

Using the New Class


Using this new class is simple, we just need to include the header file containing class MCSplitterWnd
in file “MainFrm.h”, then use it to declare variable m_wndSpw in class CMainFrame as follows:

class CMainFrame : public CFrameWnd


{
protected:
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

61
Chapter 3. Splitter Window

MCSplitterWnd m_wndSp;
……
}

After these changes, by compiling and executing the application again, we will see that the split bar
behaves differently.

3.4 Customizing the Default Appearance

Drawing Functions
Class CSplitterWnd has two member functions that can be overridden to customize the appearance of
split bar, split box, split border, and split tracker. The functions are CSplitterWnd::OnDrawSplitter(…)
and CSplitter::OnInvertTracker(…) respectively, which have the following formats:

void CSplitterWnd::OnDrawSplitter(CDC *pDC, ESplitType nType, const CRect &rect);


void CSplitterWnd::OnInvertTracker(const CRect &rect);

Function CSplitterWnd::OnDrawSplitter(…) is called when either the split bar, split box or split
border needs to be painted. It has three parameters, the first of which is a pointer to the target device DC,
which will be used to draw the objects. The second parameter is an enumerate type, which indicates what
type of object is being drawn. This parameter could be either CSplitterWnd::splitBox, CSplitterWnd::
splitBar, or CSplitterWnd::splitBorder, which indicates different splitter window objects. The third
parameter specifies a rectangle region within which the object will be drawn.
Function CSplitterWnd::OnInvertTracker(…) is called when the user clicks the mouse on the split
bar and drags it to resize the panes contained in the splitter window. In this case, a tracker will appear on
the screen and move with the mouse. By default, the tracker is a grayed line. By overriding this function,
we could let the tracker have a different appearance.

Sample
Sample 3.4\Spw demonstrates how to customize these styles. It is based on sample 3.3\Spw. First, two
functions are declared in class MCSplitterWnd to override the default implementation:

class MCSplitterWnd : public CSplitterWnd


{
public:
virtual void DeleteRow(int);
virtual void DeleteColumn(int);

protected:
virtual void OnDrawSplitter(CDC*, CSplitterWnd::ESplitType, const CRect&);
virtual void OnInvertTracker(const CRect& rect);
};

Function MCSplitterWnd::OnDrawSplitter(…) is overridden as follows:

void MCSplitterWnd::OnDrawSplitter
(
CDC* pDC,
ESplitType nType,
const CRect& rect
)
{
CBrush brush;
CBrush *ptrBrushOld;

if(pDC == NULL)
{
CSplitterWnd::OnDrawSplitter(pDC, nType, rect);
return;
}
switch(nType)
{

62
Chapter 3. Splitter Window

case CSplitterWnd::splitBox:
{
VERIFY(brush.CreateSolidBrush(RGB(255, 0, 0)));
break;
}
case CSplitterWnd::splitBar:
{
VERIFY(brush.CreateSolidBrush(RGB(0, 255, 0)));
break;
}
case CSplitterWnd::splitIntersection:
case CSplitterWnd::splitBorder:
{
CSplitterWnd::OnDrawSplitter(pDC, nType, rect);
return;
}
}
ptrBrushOld=pDC->SelectObject(&brush);
pDC->Rectangle(rect);
pDC->SelectObject(ptrBrushOld);
}

In the above function, first parameter pDC is checked. If it is not an available DC, we do nothing but
calling the default implementation of the base class. Otherwise, the object type is checked. We will go on to
implement the customization if the object is either a split bar or a split box.
The simplest way to fill a rectangle with certain pattern is to use brush. A brush can be different types:
solid, hatched, etc. It could also be initialized with any color. To use a brush, we need to first create brush,
then select it into the device context. If we draw a rectangle with this DC, the interior of the rectangle will
be automatically filled with the currently selected brush, and its border will be drawn using the currently
selected pen. After using the brush, we must select it out of the DC.
Brush selection can be implemented by calling function CDC::SelectObject(…). This function will
return a pointer to the old brush. After using the brush, we can call this function again and pass the old
brush to it. This will let the old brush be selected into the DC so the new brush is selected out.
When creating a brush, we need to use RGB macro to indicate the brush color. The three parameters of
RGB macro indicate the intensity of red, green and blue colors.
A rectangle can be drawn by calling function CDC::Rectangle(…). We need to pass a CRect type
variable to indicate the position and size of the rectangle.
In the sample, function MCSplitterWnd::OnInvertTracker(…)is implemented as follows:

void MCSplitterWnd::OnInvertTracker(const CRect& rect)


{
CDC* pDC;
CBrush brush;
CBrush *ptrBrushOld;

ASSERT_VALID(this);
ASSERT(!rect.IsRectEmpty());
ASSERT((GetStyle() & WS_CLIPCHILDREN) == 0);

pDC=GetDC();
brush.CreateSolidBrush(RGB(255, 0, 0));
ptrBrushOld=pDC->SelectObject(&brush);
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT);
pDC->SelectObject(ptrBrushOld);
brush.DeleteObject();
ReleaseDC(pDC);
}

There is no CDC type pointer passed to this function. However, for any window, its DC could always be
obtained by calling function CWnd::GetDC(). This function will return a pointer to window’s device
context. After we use the DC, we must release it by calling function CWnd::ReleaseDC(…). In function
MCSplitterWnd::OnDrawSplitter(…), first a solid brush with red color is created, then we select it into the
DC, call function CDC::PatBlt(…) to fill the interior of the rectangle using the selected brush.
Function CDC::PatBlt(…) allows us to create a pattern on the device. We can choose different color
output mode: we can copy the brush color to the destination, or we can combine brush color with the color
on the target device using bit-wise operations. The first four parameters of function CDC::PatBlt(…)
indicate the position and size of the rectangle within which we can output the pattern. The fifth parameter
indicates the output mode. In the sample we use PATINVERT drawing mode, this will combine the

63
Chapter 3. Splitter Window

destination color and brush color using bit-wise XOR operation. With this mode, the tracker can be easily
erased if it is drawn twice.
Since we use PATINVERT mode to paint the tracker, its color will become the complement color of red
when the user resizes panes using the mouse.

3.5 Splitter Window That Can’t be Resized by Tracking


Sometimes we want each pane of the splitter window to have a fixed size and prevent the user from
resizing the panes through using mouse or keyboard. Since dynamic resizing is a built-in feature of class
CSplitterWnd, whenever we directly derive a class from it, we will automatically have a resizable split bar.
It is not easy to disable this feature because by default, mouse clicking and dragging events will be
processed automatically.
In class CSplitterWnd, four mouse messages are handled to change the state of the split bar: mouse
left button down message WM_LBUTTONDOWN, mouse left button up message WM_LBUTTONUP, mouse move
message WM_MOUSEMOVE, and left button double click message WM_LBUTTONDBLCLK. We need to disable only
the first message handler if we want to disable tracking resize feature (Once the application cannot enter the
tracking state, the rest messages will be processed normally instead of being treated as part of tracking
instructions).
By default, when left button is clicked on a split bar, class CSplitterWnd will respond to this event by
letting the user drag the split bar and place it to a new place. We can bypass this feature by overriding
WM_LBUTTONDOWN message handler. Instead of calling the message handler implemented by CSplitterWnd,
we can call the default function implemented by class CWnd, which is the base class of CSplitterWnd. For a
dynamic splitter window, the WM_LBUTTONDBLCLK message handler should not be overridden because after
we disable the tracking, double clicking becomes the only way that can be used by the user to dynamically
add or delete panes. For WM_MOUSEMOVE and WM_LBUTTONUP messages, we don’t need to modify their
handlers because after message WM_LBUTTONDOWN is bypassed, the tracking will not happen anymore.
Sample 3.5\Spw demonstrates how to implement splitter window that cannot be resized through
tracking the split bar. It is based on sample 3.4\Spw.
To let class MCSplitterWnd support both resizable and non-resizable split bars, a new Boolean type
variable m_bResizable is declared in the class. Along with this variable a new member function
MCSplitterWnd::SetResizable(…) is also declared, which can be called to set m_bResizable flag and
indicate if tracking resize feature is currently supported. At the beginning variable
MCSplitterWnd::m_bResizable is initialized to TRUE in the constructor. The following code fragment
shows the modified class:

class MCSplitterWnd : public CSplitterWnd


{
DECLARE_DYNCREATE(MCSplitterWnd)
public:
MCSplitterWnd();
void SetResizable(BOOL bResizable){m_bResizable=bResizable;}
virtual void DeleteRow(int);
virtual void DeleteColumn(int);

protected:
BOOL m_bResizable;
……
}

A WM_LBUTTONDOWN message handler is added to the application. This includes function declaration,
adding ON_WM_LBUTTONDOWN message mapping macro, and the implementation of member function. Before
adding message mapping, we need to make sure that DECLARE_MESSAGE_MAP macro is included in the class.
This will enable massage mapping for the class. The following lists necessary steps for implementing the
above message mapping:

1) Declare an afx_msg type member function OnLButtonDown(…) in the class. This function is originally
declared in class CWnd, here we must declare it again in order to override it:

class MCSplitterWnd : public CSplitterWnd

64
Chapter 3. Splitter Window

{
……
protected:
……
afx_msg void OnLButtonDown(UINT, CPoint);
DECLARE_MESSAGE_MAP()
};

2) In the implementation file, add ON_WM_LBUTTONDOWN macro between BEGIN_MESSAGE_MAP and


END_MESSAGE_MAP macros:

BEGIN_MESSAGE_MAP(MCSplitterWnd, CSplitterWnd)
//{{AFX_MSG_MAP(MCSplitterWnd)
//}}AFX_MSG_MAP
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

Macro ON_WM_LBUTTONDOWN maps message WM_LBUTTONDOWN to function OnLButtonDown(…).


3) Implement the message handler as follows:

void MCSplitterWnd::OnLButtonDown(UINT uFlags, CPoint point)


{
if(m_bResizable == TRUE)CSplitterWnd::OnLButtonDown(uFlags, point);
else CWnd::OnLButtonDown(uFlags, point);
}

The function implementation is simple. If the splitter window is trackable, we call


CSplitterWnd::OnLButtonDown(…), which will implement tracking if mouse cursor is over the split bar.
Otherwise we bypass this feature by calling function CWnd::OnLButtonDown(…).
If the splitter window is created by the Application Wizard, there will be a command View | Split
implemented in the application. By default, this command gives an alternate way to resize panes by
tracking the split bar. If we want to disable the tracking completely, we also need to disable or remove this
menu command.

Summary
1) To implement static splitter window, we need to derive a class from CView (or other type of view
classes) for each pane, then declare a CSplitterWnd type variable in class CMainFrame. In function
CMainFrame::OnCreateClient(…), we need to call CSplitterWnd::CreateStatic(…) to create the
splitter window and call CSplitterWnd::CreateView(…) to attach a view to each pane.
1) Static splitter window can be nested. This means instead of attaching a view, we can use CSplitterWnd
to further create splitter window within a pane.
1) Creating dynamic splitter window is simple. In order to do this, we need to declare a CSplitterWnd
type variable in class CMainFrame; then in CMainFrame::OnCreateClient(…), we need to call function
CSplitterWnd::Create(…).
1) We can override functions CSplitterWnd::DeleteRow(…) and CSplitterWnd::DeleteColumn(…) to
customize the behavior of split bars.
1) We can override functions CSplitterWnd::OnDrawSplitter(…) and CSplitterWnd::
OnInvertTracker(…) to customize the appearance of split bar, split box, split border and tracker.
1) To disable split bar tracking, we need to call CWnd::OnLButtonDown(…) instead of CSplitterWnd::
OnLbuttonDown(…) when handling message WM_LBUTTONDOWN.

65
Chapter 4. Button

Chapter 4

Buttons

B
utton is one of the most commonly used controls, almost every application needs to include one or
more buttons. It seems that it is very easy to implement a standard button in a dialog box: all we
need to do is adding a button resource to the dialog template, then using Class Wizard to generate
message handlers. However, it is not very easy to further customize button’s default properties.
In this chapter, we will discuss how to implement different type of customized buttons. At the end of
this chapter, we will be able to include some eye-catching buttons in our applications.

4.1 Bitmap Button: Automatic Method


Generally, buttons display plain text on its interface. Sometimes it is more desirable to let them have
graphic user interface. A typical application that uses this type of buttons would be a CD player, everyone
would like the play button to have a graphic interface instead of just displaying text such as “play”, “stop”
(so that it looks like a real “play” button).

Button States
Before customizing button’s default feature, it is important for us to understand some basics of buttons.
Every button has four states. When a button is not clicked, is in “up” state (the most common state). When
it is pressed down, it is in “down” state. To emphasis a button’s 3D effect, a default button will recess when
it is pressed by the mouse. Also, a button could be disabled, in this state, the button will not respond to any
mouse clicking (As the default implementation, when a button is disabled, it will be drawn with “grayed”
effect). Finally, a button has a “focused” or “unfocused” state. In the “focused” state, the button is an active
window, and is accessible through using keyboard (ENTER or downward ARROW key). For the default
implementation, a rectangle with dashed border will be drawn over a button’s face when it has the current
focus.

Owner-Draw Bitmap Button


We can use owner draw bitmap button to add graphics to the button. To distinguish among different
states, we need to associate different states with different images if necessary.
The simplest way to create this type of button is to implement bitmap button using class
CBitmapButton. To create a bitmap button, first we must set its “Owner Draw” style. This can be
implemented by checking “Owner Draw” check box in the “Push Button Properties” property sheet when
creating the button resource (We need to add button resource in order to create button, this is the same with
a normal button. See Figure 4-1). For an owner-draw button, message WM_DRAWITEM will be sent to the
button when it needs to be painted (remember, a button is also a window that can receive message). Upon
receiving this message, the button will be drawn by the overridden function. For non-owner-draw button,
its interface is implemented by the default method, and will display a plain text on the button’s face.
Class CBitmapButton handles message trapping and processing; also, it contains member functions
that can be used to paint the button. If we use this class to implement bitmaps buttons, all we need to do is
preparing some bitmap resources, declaring variables using class CBitmapButton, and associating bitmap
resources with the corresponding buttons.

66
Chapter 4. Button

Check here to
create bitmap
button

Figure 4-1. Change button’s style to “Owner draw”

Although every button has four states, we do not need to provide four bitmaps all the time. If one of
the bitmaps is not available, class CBitmapButton will draw the button’s corresponding state using the
default bitmap, which is the bitmap associated with button’s “up” state. So the bitmap used to represent a
button’s “up” state is required all the time and can not be omitted.

Automatic Method
We have two ways of associating bitmap images with different states of a bitmap button: we can either
let class CBitmapButton handle this automatically or we can do it manually. To use the automatic method,
the IDs of all four bitmap resources must be text strings, and must be formed by suffixing one of the
following four letters to the button’s caption text: ‘U’, ‘D’, ‘F’, ‘X’. These letters represent “up”, “down”,
“focused” and “disabled” state respectively. By naming the resource IDs this way, the rest thing we need to
do is calling function CBitmapButton::AutoLoad() in the dialog box’s initialization stage (within member
function CDialog::OnInitDialog()). Please note that we cannot call this function in the constructor of
class CDialog. At that time, the dialog box window is still not created (Therefore, the buttons are still not
available), and the bitmaps cannot be associated with the button correctly.

Sample
Sample 4.1\Btn demonstrates how to create bitmap button using automatic method. It is a dialog-based
application that is generated by the Application Wizard. First, the ID of default dialog template is changed
to IDD_DIALOG_BTN. Also, the “OK” and “Cancel” buttons originally included in the template are deleted.
Then a new button IDC_PLAY is added, whose caption text is set to “Play” (Figure 4-2). Since the button
will be drawn using the bitmaps, it doesn’t matter how big the button resource is. Besides this, we need to
set button’s style to “Owner draw”.

Set the button’s


caption text to
“Play”

Figure 4-2. Add a button in the dialog template to create bitmap button

Two bitmap resources are added to the application whose IDs are “PLAYU” and “PLAYD”
respectively (Figure 4-3). They correspond to button’s “up” and “down” states. In addition, the sizes of the
two bitmaps are exactly the same.
A CBitmapButton type variable is declared in class CBtnDlg to implement this bitmap button:

67
Chapter 4. Button

Use string text as


the bitmap
resource ID

Figure 4-3. Bitmap “PLAYU”

class CBtnDlg : public CDialog


{
……
protected:
……
CBitmapButton m_btnPlay;
……
};

Within function CBtnDlg::OnInitDialog(), function CBitmapButton::AutoLoad(…) is called to


initialize the bitmap button and associate it with the corresponding bitmap resources. After this call, we
don’t need to do anything. The bitmap button will be created and its states will be set automatically:

BOOL CBtnDlg::OnInitDialog()
{
……
m_btnPlay.AutoLoad(IDC_PLAY, this);
……
}

Function CBitmapButton::AutoLoad(…) has two parameters, first of which is the button’s resource ID,
and the second is a pointer to the button’s parent window.
In the sample application only two bitmap images are prepared. We may add two other bitmaps whose
IDs are “PLAYF” and “PLAYX”. Then we can enable or disable the button to see what will happen to the
button’s interface.

4.2 Bitmap Check Box and Radio Button: Method 1


The bitmap button implemented this way behaves like a push button. Unfortunately, in MFC, there is
no class such as CBitmapCheckBox and CBitmapRadioButton to let us implement bitmap check box or radio
button. However, check box and radio button are another two types of buttons, both of them can be
implemented using class CButton.
A button implemented by class CButton can display either plain text or bitmap. Actually, there is a
member function of CButton that allows us to associate button with a bitmap: CButton::SetBitmap(…).
If a button implemented by class CButton can also be associated with a bitmap, what is the difference
between CBitmapButton and CButton? First, CBitmapButton is derived from CButton, so it has more
features than CButton. For example, with CBitmapButton, we can use automatic method to associate button
with bitmap resources by calling function CBitmapButton::AutoLoad(...). Second, CButton allows only
one bitmap to be associated with a button at any time, and it always implement the focus state of the button
by drawing a dash-bordered rectangle over button’s face.
Although only one bitmap could be associated with a button at any time, we still can manage to
represent a button’s different states using different bitmaps.
The trick here is to change button’s associated bitmap whenever its state changes. To achieve this, we
must implement a WM_COMMAND message handler for the button. For example, in the case of check box, we

68
Chapter 4. Button

can find out whether the current state of the check box is “Checked” or “Unchecked”. Based on this
information, we can decide which bitmap should be used.
Sample 4.2\Btn demonstrates the above method. It is based on sample 4.1\Btn. In the sample, three
new buttons are added: one of them is implemented as a check box; the rest are implemented as radio
buttons. The following describes how the bitmap check box and radio buttons are implemented in the
sample:

1) Add a check box and two radio buttons to the dialog template. Name the IDs of new controls
IDC_CHECK, IDC_RADIO_A and IDC_RADIO_B respectively. In the property sheet that lets us customize
control’s properties, check “Bitmap” check box (Figure 4-4).

Figure 4-4. Set properties for bitmap check box control

2) Add two bitmap resources, one for checked state and one for unchecked state. Their resource IDs are
ID_BITMAP_CHECK and ID_BITMAO_UNCHECK respectively. The bitmaps must have a same size.
3) Declare two CBitmap type variables m_bmpCheck and m_bmpUnCheck in class CBtnDlg, in function
CBtnDlg::OnInitDlg(), call CBitmap::LoadBitmap(…) to load the two bitmap resources. Then call
function CButton::SetBitmap(…) to set bitmap for the check box and radio buttons. In the sample, all
of the new controls are initialized to unchecked state (In order to do this, we need to associate buttons
with m_bmpUnCheck instead of m_bmpCheck). The following code fragment shows the modified class
CBtnDlg and the function CBtnDlg::OnInitDialog(…):

class CBtnDlg : public CDialog


{
……
protected:
……
CBitmap m_bmpCheck;
CBitmap m_bmpUnCheck;
……
};

BOOL CBtnDlg::OnInitDialog()
{
……
m_bmpCheck.LoadBitmap(IDB_BITMAP_CHECK);
m_bmpUnCheck.LoadBitmap(IDB_BITMAP_UNCHECK);
((CButton *)GetDlgItem(IDC_CHECK))->SetBitmap
(
(HBITMAP)m_bmpUnCheck.GetSafeHandle()
);
((CButton *)GetDlgItem(IDC_RADIO_A))->SetBitmap
(
(HBITMAP)m_bmpUnCheck.GetSafeHandle()
);
((CButton *)GetDlgItem(IDC_RADIO_B))->SetBitmap
(
(HBITMAP)m_bmpUnCheck.GetSafeHandle()
);
……
}

4) Declare a new member function CBtnDlg::SetCheckBitmap(…). We will use it to set a button’s bitmap
according to its current state. The function has one parameter nID that identifies the control. Within the
function, first the button’s current state is examined, if it is checked, we call CButton::SetBitmap(…)
to associate it with IDB_BITMAP_CHECK; otherwise we use bitmap IDB_BITMAP_UNCHECK. The following
is the implementation of this function:

69
Chapter 4. Button

void CBtnDlg::SetCheckBitmap(UINT nID)


{
BOOL bCheck;

bCheck=((CButton *)GetDlgItem(nID))->GetCheck();
((CButton *)GetDlgItem(nID))->SetBitmap
(
bCheck ? (HBITMAP)m_bmpCheck.GetSafeHandle():
(HBITMAP)m_bmpUnCheck.GetSafeHandle()
);
Invalidate(FALSE);
}

5) Use Class Wizard to implement three WM_COMMAND message handlers for IDC_CHECK, IDC_RADIO_A and
IDC_RADIO_B. Within each handler, we call CBtnDlg::SetCheckBitmap(…) to set appropriate bitmaps.
Because two radio buttons should be treated as a group (if one is checked, the other one will be
unchecked automatically), we need to set both button’s bitmaps within each message handler:

void CBtnDlg::OnCheck()
{
SetCheckBitmap(IDC_CHECK);
}

void CBtnDlg::OnRadioA()
{
SetCheckBitmap(IDC_RADIO_A);
SetCheckBitmap(IDC_RADIO_B);
}

void CBtnDlg::OnRadioB()
{
SetCheckBitmap(IDC_RADIO_B);
SetCheckBitmap(IDC_RADIO_A);
}

In step 3, when calling CWnd::GetDlgItem(…), we pass the control’s resource ID to the function to
obtain a pointer to the control and use it to call function CButton::SetBitmap(…). Because
CWnd::GetDlgItem(…) will return a CWnd type pointer, we must first cast it to CButton type pointer before
calling the member function of CButton.
Function Cbutton::SetBitmap(…) has an HBITMAP type parameter, which requires a valid bitmap
handle. A bitmap handle can be obtained by calling function CBitmap::GetSafeHandle(), of course, the
returned handle is valid only after the bitmap is loaded.
In step 4, function CButton::GetCheck() is called to retrieve button’s current state (checked or
unchecked). The function returns a Boolean type value, if the returned value is TRUE, the button is being
checked, otherwise it is not checked.
After these modifications, the bitmap check box and radio buttons will become functional.

4.3 Subclass
In section 4.1, we used automatic method to create bitmap buttons. This requires us to create owner-
draw buttons with special caption text, which will be used to name the bitmap resource IDs. For simple
cases, this is a very convenient method. However, if we implement bitmap buttons this way, it is difficult
for us to customize them at runtime.

Implementing Subclass
Class CBitmapButton gives us another member function that can be used to associate bitmaps with an
owner-draw button: CBitmapButton::LoadBitmaps(…). This function has two versions, the first version
allows us to load bitmaps with string IDs, the second version allows us to load bitmaps with integer IDs.
To use this function, we must first implement subclass for the owner-draw button. “Subclass” is a very
powerful technique in Windows programming. It allows us to write a procedure, attach it to a window,

70
Chapter 4. Button

and use it to intercept messages sent to this window then process it. By doing this, we are able to customize
the window’s behavior within the procedure.
Subclass is supported by class CWnd, so theoretically all windows (including client window, dialog box,
dialog common controls...) can be “subclassed”. There are two functions to implement subclass, one is
CWnd::SubclassWindow(…), which allows us to customize the normal behavior of a window. Here we will
use the other one: CWnd::SubclassDlgItem(…), which is specially designed to implement subclass for the
common controls contained in a dialog box.
In MFC, implementing subclass is very simple. We don’t need to write a special procedure to handle
the intercepted messages. All we need to do is designing a class as usual, adding message handlers for the
messages we want to process, and implementing the message handlers. Then we can declare a variable
using the newly designed class, and call function CWnd::SubclassDlgItem(…) to implement subclass.
Function CWnd::SubclassDlgItem(…) has two parameters:

BOOL CWnd::SubclassDlgItem(UINT nID, CWnd *pParent);

Parameter nID indicates which control we are dealing with, and pParent is the pointer to the control’s
parent window.
Class CBitmapButton uses subclass to change the default behavior of a button. If we use automatic
method to load the bitmaps, the subclass procedure is transparent to the programmer. However, if we want
to load the bitmaps by ourselves, we must implement subclass first.

Bitmap Button
Sample 4.3\Btn demonstrates how to associate bitmaps with an owner draw button by calling function
CBitmapButton::LoadBitmaps(…). It is based on sample 4.2\Btn. There is nothing new in this sample,
except that button IDC_PLAY is implemented differently.
In the previous samples, variable CBtnDlg::m_btnPlay is declared as a CBitmapButton type variable.
In the new sample, instead of using automatic method to load the bitmaps, we first implement the subclass
then load the bitmaps manually in function CBitmapButton::LoadBitmaps(…):

BOOL CBtnDlg::OnInitDialog()
{
CDialog::OnInitDialog();
……
m_btnPlay.SubclassDlgItem(IDC_PLAY, this);
m_btnPlay.LoadBitmaps
(
“PLAYU”,
“PLAYD”,
NULL,
NULL
);
m_btnPlay.SizeToContent();
……
return TRUE;
}

Here, function CBitmapButton::AutoLoad(…) is replaced by three new functions. The first function
added is CWnd::SubclassDlgItem(…). The second function is CBitmapButton::LoadBitmaps(…). This
function has four parameters, which are the bitmap IDs corresponding to button’s “Up”, “Down”,
“Focused” and “Disabled” states respectively. They could be either string IDs or integer IDs. The last
function is CBitmap::SizeToContent(), which allows us to set bitmap button’s size to the size of the
associated bitmaps. If we don’t call this function, the bitmaps may not fit well into the button.
Now we can remove or modify bitmap button IDC_PLAY’s caption text “Play. Actually, it doesn’t
matter if the button has caption text or not. By compiling the application and executing it at this point, we
will see that the bitmap button implemented here is exactly the same as the one implemented in the
previous sample.

71
Chapter 4. Button

4.4 Bitmap Check Box and Radio Button: Method 2


In sample 4.2\Btn, although we can represent the checked and unchecked states of a check box or a
radio button using different bitmaps, we could not customize their “focused” state. When a button has the
current focus, a rectangle with dashed border will always be put over button’s face (Figure 4-5). This is
because we use CButton instead of CBitmapButton to implement buttons, this allows only one bitmap to be
associated with a button.

A rectangle with
dashed border will
appear whenever the
control has current
focus

Figure 4-5. Bitmap check box and radio buttons implemented using class CButton
To improve this, we can use class CBitmapButton to create both check box and radio button. By doing
this, the button’s focused state will be implemented using the bitmap image provided by the programmer
instead of drawing a rectangle with dashed border over button’s face. Since class CBitmapButton supports
only push button implementation, we need to change the bitmap by ourselves to imitate check box and
radio button.
Sample 4.4\Btn is based on sample 4.3\Btn. In this sample, three new buttons are added to the dialog
template: one will be implemented as a check box; the other two will be implemented as radio buttons. All
of them will be based on class CBitmapButton.
First, we need a Boolean type variable for each check box and radio button to represent its current
state. This variable toggles between TRUE and FALSE, indicating if the button is currently checked or
unchecked. When the state of a button changes, we re-associate the button with an alternate bitmap and
paint the bitmap button again.
Since we use push button to implement check box and radio button, we can not call function
CButton::GetCheck(…) to examine if the button is currently checked or not. This is because a push button
will automatically resume to the “up” state after the mouse is released.
In the sample application, three new buttons are added to the dialog template IDD_BTN_DIALOG, and
their corresponding IDs are IDC_BMP_CHECK, IDC_BMP_RADIO_A, IDC_BMP_RADIO_B respectively. Also, they
all have a “Owner draw” style. Besides the new controls, two new bitmap resources are also added to the
application, which will be used to implement button’s “Checked” and “Unchecked” states. The IDs of the
new bitmap resources are IDB_BITMAP_BTNCHECK and IDB_BITMAP_BTNUNCHECK. The difference between the
new bitmaps and two old ones (whose IDs are IDB_BITMAP_CHECK and IDB_BITMAP_UNCHECK) is that the
new bitmaps have a 3-D effect. In sample 4.2\Btn, the check box and radio buttons are implemented using
class CButton, which automatically adds 3-D effect to the controls. Since we want the controls to be
implemented solely by programmer-provided bitmaps, we need to add 3-D effect by ourselves.
In the sample application, three new CBitmapButton type variables are declared in class CBtnDlg. Also,
a new member function SetRadioBitmap() is added to associate bitmaps with the two radio buttons. This
function will be called when one of the radio buttons is clicked by mouse. For the check box, associating
bitmap with it is relatively simple, so it is implemented within the message handler. Besides this, a new
Boolean type variable CBtnDlg::m_bBmpCheck is declared to indicate the current state of the check box, and
an unsigned integer CBtnDlg::m_uBmpRadio is declared to indicate which radio button is being selected.
For each button, WM_COMMAND message handler is added through using Class Wizard. These message
handlers are CBtnDlg::OnBmpCheck(), CBtnDlg::OnBmpRadioA() and CBtnDlg::OnBmpRadioB()
respectively. The following code fragment shows the new members added to the class:

class CBtnDlg : public CDialog


{
public:
……
void SetRadioBitmap();
……
protected:
……

72
Chapter 4. Button

CBitmapButton m_btnCheck;
CBitmapButton m_btnRadioA;
CBitmapButton m_btnRadioB;
CBitmap m_bmpCheck;
CBitmap m_bmpUnCheck;
BOOL m_bBmpCheck;
UINT m_uBmpRadio;
……
afx_msg void OnBmpCheck();
afx_msg void OnBmpRadioA();
afx_msg void OnBmpRadioB();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

In the constructor of CBtnDlg, variable CBtnDlg::m_bBmpCheck is initialized to FALSE and


CBtnDlg::m_uBmpRadio is initialized to zero. This means the original state of the check box is unchecked,
and no radio button is selected:

CBtnDlg::CBtnDlg(CWnd* pParent /*=NULL*/)


: CDialog(CBtnDlg::IDD, pParent)
{
……
m_bBmpCheck=FALSE;
m_uBmpRadio=0;
}

In the dialog initialization stage, subclass is implemented for buttons, then the corresponding bitmaps
are loaded:

BOOL CBtnDlg::OnInitDialog()
{
……
m_btnCheck.SubclassDlgItem(IDC_BMP_CHECK, this);
m_btnCheck.LoadBitmaps(IDB_BITMAP_BTNUNCHECK);
m_btnCheck.SizeToContent();
m_btnRadioA.SubclassDlgItem(IDC_BMP_RADIO_A, this);
m_btnRadioA.LoadBitmaps(IDB_BITMAP_BTNUNCHECK);
m_btnRadioA.SizeToContent();
m_btnRadioB.SubclassDlgItem(IDC_BMP_RADIO_B, this);
m_btnRadioB.LoadBitmaps(IDB_BITMAP_BTNUNCHECK);
m_btnRadioB.SizeToContent();
……
}

For each button, first we implement subclass for it, then we load the bitmap by calling function
CBitmapButton::LoadBitmaps(…). Because we provide only one bitmap for each control, state transition
of these buttons (e.g., normal state to focused state) will not be reflected to the interface unless we add
extra code to handle it.
For check box IDC_BMP_CHECK, when it is clicked by the mouse’s left button, we need to toggle the
value of variable CBtnDlg::m_bCheck, load the corresponding bitmap and paint the button again. The
following is the WM_DOMMAND message handler for check box:

void CBtnDlg::OnBmpCheck()
{
m_bBmpCheck=m_bBmpCheck ? FALSE:TRUE;
m_btnCheck.LoadBitmaps
(
m_bBmpCheck ?
IDB_BITMAP_BTNCHECK:IDB_BITMAP_BTNUNCHECK
);
Invalidate(FALSE);
}

Radio buttons are slightly different. When one radio button is checked, the other button should be
unchecked. So within both CBtnDlg::OnBmpRadioA() and CBtnDlg::OnBmpRadioB(), function CBtnDlg::
SetRadioBitmap() is called to set bitmaps for both radio buttons:

void CBtnDlg::OnBmpRadioA()
{

73
Chapter 4. Button

m_uBmpRadio=IDC_BMP_RADIO_A;
SetRadioBitmap();
}

void CBtnDlg::OnBmpRadioB()
{
m_uBmpRadio=IDC_BMP_RADIO_B;
SetRadioBitmap();
}

void CBtnDlg::SetRadioBitmap()
{
m_btnRadioA.LoadBitmaps
(
(m_uBmpRadio == IDC_BMP_RADIO_A) ?
IDB_BITMAP_BTNCHECK:IDB_BITMAP_BTNUNCHECK
);
m_btnRadioB.LoadBitmaps
(
(m_uBmpRadio == IDC_BMP_RADIO_B) ?
IDB_BITMAP_BTNCHECK:IDB_BITMAP_BTNUNCHECK
);
Invalidate(FALSE);
}

When the user clicks one of the radio buttons, its resource ID is assigned to variable
CBtnDlg::m_uBmpRadio. Then in function CBtnDlg::SetRadioBitmap(), this variable is compared to both
radio button IDs. Bitmap IDB_BITMAP_BTNCHECK will be associated to the button whose ID is currently
stored in variable CBtnDlg::m_uBmpRadio. For the other button, bitmap IDB_BITMAP_BTNUNCHECK will be
loaded.
The appearance of our new check box and radio buttons is almost the same with that of old ones
implemented in sample 4.2\Btn. However, here the rectangle with dashed border will not be put over a
button’s face when the button has the current focus (Figure 4-6).

Figure 4-6. Bitmap check box and radio buttons


implemented using class CBitmapButton

4.5 Irregular Shape Bitmap Button

Transparent Background
Up to now all the buttons created by us have a rectangular shape. It is relatively difficult to create a
button with irregular shapes (e.g., a round button). Even for bitmap buttons, their associated bitmaps are
always rectangular.
We may think that by setting the bitmap’s background color to the color of the dialog box, the bitmap
button may look like a non-rectangular button. For example, in the previous samples, button IDC_PLAY is
made up of two regions: its foreground is the triangular region, and rest area can be treated as its
background (Figure 4-7). We can change the background color to the color of the dialog box so that this
area appears transparent to us when the bitmap button is implemented.
However, this is not the best solution. In Windows operating system, the color of dialog box can be
customized. The user can double click “Display” icon contained in the “Control Panel” window and set the
colors for many objects, which include title bar, backdrop..., and so on. So actually we have no way of
knowing the color of dialog box beforehand. If we implement a bitmap button and set its background color
to the dialog box color in our system, it may not work properly in another system.

74
Chapter 4. Button

Background

Foreground

Figure 4-7. Foreground and background of a bitmap button

To draw a bitmap with transparent background, we need to use “mask” when painting the bitmap. We
can imagine a mask as a matrix with the same dimension of the bitmap image, and each element in the
matrix corresponds to a pixel contained in the bitmap. The elements in the matrix may be either “0” or “1”.
When we paint the bitmap, only those pixels with “0” masks are output to the device (we can also use “1”
to indicate the pixels that need to be drawn).
When programming the application, we can prepare two bitmaps of exactly the same size: one stores
the normal image, the other one stores mask. The mask bitmap is made up of only two types of pixels:
black and white. This is because for black color, its RGB elements are all 0s; for white color, the elements
are all 1s. By implementing the mask bitmap like this, the background area of the mask bitmap is white and
the foreground area is black.
Windows allows us to output the pixels of a bitmap to the target device using different operation
mode. We can copy a pixel directly to the target device, we can also combine a pixel contained in the
bitmap with the pixel contained in the target device using various mode: bit-wise OR, AND, and XOR. For
example, if we combine red color (RGB(255, 0, 0)) with green color (RGB(0, 255, 0)) using bit-wise AND
operation, the output will be yellow color (RGB(255, 255, 0)).
When painting the bitmap, we first need to output the normal bitmap to the target device using bit-wise
XOR drawing mode, then output the mask bitmap to the target device at the same position using bit-wise
AND mode. Finally we can output the normal bitmap again using bit-wise XOR mode. By doing this, the
output bitmap’s background will become transparent.
The reason for this is simple. Before bitmap is painted, the target device may contain a uniform color
or any image pattern. After normal bitmap is first painted, the foreground area of the target device will
become the combination of source image pattern and target image pattern. After we output mask bitmap
using bit-wise AND operation, the foreground area of the target device will become black. This means on
the target device, every pixel in the foreground area is zero now. Since we use bit-wise XOR mode to
output normal image in the last step, and XORing anything with zero will not change the source, the
foreground area on the target device will finally contain the pattern of the source image. For mask area, the
second ANDing operation doesn’t make any change to it because ANDing a pixel with white color (all 1s)
doesn’t change that pixel. So the overall operations on the mask region is equivalent to two consecutive
XOR operations, which will resume all the pixels in this region to their original colors.
However there is still a slight problem here: if we draw the source and mask bitmaps directly to the
target device using the method mentioned above, we will see a quick flicker on the mask area of the target
device. Although it lasts only for a very short period, it is an undesirable effect. To overcome this, we can
prepare a bitmap in the memory, copy the target image pattern to this bitmap, do the transparent painting on
the memory bitmap, and copy the final result back to the target. Since the background area of the memory
bitmap has the same pattern with the background area of the target device, this final copy will not cause
any flicker.
To customize the drawing behavior of bitmap button, we need to override function
CBitmapButton::OnDrawItem(…). For an owner-draw button, this function will be called when a button
needs to be updated. Actually, menu and combo box also use similar functions. We can create an owner
draw menu or combo box by overriding the member functions with the same name. For these functions,

75
Chapter 4. Button

their parameters are all pointers to DRAWITEMSTRUCT type object. This structure stores information such as
button’s current state (i.e. focused, disabled), the device context that can be used to implement drawing,
and the rectangle indicating where we can output the image pattern.

New Class
Sample 4.5\Btn is based on sample 4.4\Btn, it demonstrates how to create bitmap buttons with
transparent background. In this sample, a new class MCBitmapButton is derived from CBitmapButton.
Besides the default properties inherited from base class, the following new features are added to this class:

1) The new class handles transparent background drawing automatically. Programmer can prepare a
black-and-white mask bitmap and associate it with the bitmap button together with other required
bitmaps. The drawing will be taken care in the member function of the class. Programmer doesn’t need
to add extra code.
2) The mask bitmap can be loaded along with other images by calling either AutoLoad(…) or
LoadBitmaps(…).
2) Function AutoLoad(…) is overridden to load the mask image automatically. In this case, the mask
bitmap must have a string ID that is created by suffixing an ‘M’ character to button’s caption text. For
example, if we want to create mask bitmap for a button whose caption text is “PLAY”, the ID of the
mask bitmap must be “PLAYM”. If we load the mask bitmap using automatic method, there is no
difference between using the new class and CBitmapButton.
2) Mask bitmap could also be loaded by calling function LoadBitmaps(…). The overridden function has
five parameters, the last of which is the ID of the mask bitmap.
2) If the mask bitmap is not present, the bitmap will be output directly to the target device using the
default implementation.

In the sample, a mask bitmap “PLAYM” is added to the application. It will be used to draw button
IDC_PLAY with transparent background.
The new class derived from CBitmapButton is MCBitmapButton. In this class, a new CBitmap type
variable m_bitmapMask is added to load the mask bitmap, also, functions AutoLoad(…) and LoadBitmaps(…)
are overridden. The following code fragment shows this new class:

class MCBitmapButton : public CBitmapButton


{
public:
MCBitmapButton();
BOOL LoadBitmaps
(
LPCTSTR lpszBitmapResource,
LPCTSTR lpszBitmapResourceSel=NULL,
LPCTSTR lpszBitmapResourceFocus=NULL,
LPCTSTR lpszBitmapResourceDisabled=NULL,
LPCTSTR lpszBitmapResourceMask=NULL
);
BOOL LoadBitmaps
(
UINT nIDBitmapResource,
UINT nIDBitmapResourceSel=0,
UINT nIDBitmapResourceFocus=0,
UINT nIDBitmapResourceDisabled=0,
UINT nIDBitmapResourceMask=0
);
BOOL AutoLoad(UINT nID, CWnd* pParent);

protected:
CBitmap m_bitmapMask;
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
DECLARE_DYNAMIC(MCBitmapButton)
DECLARE_MESSAGE_MAP()
};

Function LoadBitmaps(…) has two versions, one is used to load bitmaps with string IDs, the other is
used to load bitmaps with integer IDs. Both functions have five parameters.

76
Chapter 4. Button

Overriding Function CBitmapButton::LoadBitmaps(…)


When overriding functions, we can utilize the features implemented by the base class to load standard
four bitmaps, and add our own code to load the mask bitmap. The following is the implementation of
function MCBitmapButton::LoadBitmaps(…):

BOOL MCBitmapButton::LoadBitmaps
(
LPCTSTR lpszBitmapResource,
LPCTSTR lpszBitmapResourceSel,
LPCTSTR lpszBitmapResourceFocus,
LPCTSTR lpszBitmapResourceDisabled,
LPCTSTR lpszBitmapResourceMask
)
{
BOOL bAllLoaded;

m_bitmapMask.DeleteObject();
bAllLoaded=CBitmapButton::LoadBitmaps
(
lpszBitmapResource,
lpszBitmapResourceSel,
lpszBitmapResourceFocus,
lpszBitmapResourceDisabled
);
if(lpszBitmapResourceMask != NULL)
{
if(!m_bitmapMask.LoadBitmap(lpszBitmapResourceMask))
{
TRACE0(“Failed to load bitmap for normal background image.\n”);
bAllLoaded=FALSE;
}
}
return bAllLoaded;
}

First we must use variable m_bitmapMask to call function CBitmap::DeleteObject(), which is


inherited from class CGdiObject. Since bitmap is a GDI (graphics device interface) object, once it is
initialized, it will allocate some memory. If we want to initialize it again, we must first release the
previously allocated memory. Function CGdiObject::DeleteObject() can be used for this purpose.
Next we call function CBitmapButton::LoadBitmaps(…) to load the default four bitmaps, and see if
the mask bitmap is available. If so, we use m_bitmapMask to load the mask bitmap.

Overriding Function CBitmapButton::AutoLoad(…)


In member function MCBitmapButton::AutoLoad(…), the bitmap resource IDs are obtained by
suffixing ‘U’, ‘D’, ‘F’, ‘X’ or ‘M’ to button’s caption text. With the resource IDs, function
MCBitmapButton:: LoadBitmaps(…) is called to load the relevant bitmaps:

BOOL MCBitmapButton::AutoLoad(UINT nID, CWnd* pParent)


{
CString buttonName;

if(!SubclassDlgItem(nID, pParent))return FALSE;


GetWindowText(buttonName);
ASSERT(!buttonName.IsEmpty());
LoadBitmaps
(
buttonName + “U”,
buttonName + “D”,
buttonName + “F”,
buttonName + “X”,
buttonName + “M”
);
if(m_bitmap.m_hObject == NULL)return FALSE;
SizeToContent();
return TRUE;
}

77
Chapter 4. Button

The caption text of the button can be retrieved by calling function CWnd::GetWindwoText(…). Actually,
every window can have a caption text, which can be an empty string, or any text. We can use this function
to retrieve the caption text of any window, for example, a title tar.
We need to call functions CWnd::SubclassDlgItem(…) and CBitmapButton::SizeToContent() to
change the button’s default properties. Actually, the above two functions are also called in function
CBitmapButton::AutoLoad(…).

Overriding Function CBitmapButton::DrawItem(…)


Then we need to implement MCBitmapButton::DrawItem(…). In this member function, we need to
check the current state of button, and choose appropriate bitmaps for painting button’s face.
Class CBitmapButton has four CBitmap type variables: m_bitmap, m_bitmapSel, m_bitmapFocus and
m_bitmapDisabled, which are used to store the standard four bitmaps. Because they are not declared as
private members, we can access them from the derived classes and use them to paint the button.
The only parameter of function CBitmapButton::DrawItem(…) is a pointer to a DRAWITEMSTRUCT type
object. When overriding this function, we need to use four members contained in DRAWITEMSTRUCT:
itemState, which indicates the current state of the button; hDC, which is the handle to the target device
context; rcItem, which is the rectangle specifies the position and size of the output area. Besides these, we
also need to check the following bits of member itemState: ODS_SELECTED, ODS_FOCUS, ODS_DISABLED,
which indicate if the current state of button is “selected”, “focused” or “disabled”.
At the beginning of the overridden function, we need to declare several CBitmap and CDC type variables
or pointers, all of which are used for bitmap drawing:

void MCBitmapButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)


{
CBitmap *pBitmap;
CBitmap *pBitmapMask;
CBitmap *pOld;
CBitmap *pOldMask;
CBitmap *pOldImage;
CBitmap bmpImage;
BITMAP bm;
UINT state;
CDC *pDC;
CDC memDC;
CDC memDCMask;
CDC memDCImage;
CRect rect;
……

Three DCs are declared here. To draw a bitmap, we must create a memory DC, select the bitmap into it
and copy the bitmap from the memory DC to the target DC. The target could be either a device or a
memory block (we could copy bitmap between two memory DCs). A DC can select only one bitmap at any
time.
When there is no mask bitmap, variable memDC is used to perform normal bitmap drawing: it is used to
select the normal bitmap, and copy it directly to the target DC. When there is a mask bitmap, memDC will be
used along with memDCMask to implement transparent background drawing.
Variable memDCImage is used to act as the target memory DC and implement transparent background
drawing. It will be used in conjunction with bmpImage, which will be selected into memDCImage. To draw the
bitmap, first we need to copy the image pattern from the target device to the memory bitmap, then copy the
source bitmap to the memory bitmap (perform AND and XOR drawings). Finally, we can output the result
from the memory bitmap to the target device.
Variable bmpImage is used to create bitmap in memory.
Variable memDCMask is used to select mask bitmap image.
Pointer pDC will be used to store the pointer of the target device context that is created from hDC
member of structure DRAWITEMSTRUCT.
Pointers pBitmap and pBitmapMask will be used to store the pointers to the normal bitmap (could be
one of the bitmaps indicating the four states of the button) and the mask bitmap respectively.
The other three CBitmap pointers pOld, pOldMask and pOldImage are used to select the bitmaps out of
the DCs (When the bitmaps are being selected into the DCs, these pointers are used to store the bitmaps

78
Chapter 4. Button

selected out of the DCs. After bitmap drawing is finished, we can select old bitmaps back into the DCs, this
will select our bitmaps out of the DCs automatically).
Variable state is used to store the current state of button.
The following portion of function MCBitmapButton::DrawItem(…) shows how to choose appropriate
bitmaps:

……
ASSERT(lpDIS != NULL);
ASSERT(m_bitmap.m_hObject != NULL);
pBitmap=&m_bitmap;
if(m_bitmapMask.m_hObject != NULL)
{
pBitmapMask=&m_bitmapMask;
}
else pBitmapMask=NULL;
state=lpDIS->itemState;
if
(
(state & ODS_SELECTED) &&
(m_bitmapSel.m_hObject != NULL)
)
{
pBitmap=&m_bitmapSel;
}
else if
(
(state & ODS_FOCUS) &&
(m_bitmapFocus.m_hObject != NULL)
)
{
pBitmap=&m_bitmapFocus;
}
else if
(
(state & ODS_DISABLED) &&
(m_bitmapDisabled.m_hObject != NULL)
)
{
pBitmap=&m_bitmapDisabled;
}
……

First pBitmap is assigned the address of variable m_bitmap, which holds the default bitmap. Then we
check if the mask bitmap exists, if so, we assign its address to pBitmapMask. The current state of the button
is read into variable state, whose ODS_SELECTED, ODS_FOCUS and ODS_DISABLED bits are examined in turn.
If any of them is set, the corresponding bitmap’s address will be stored in pBitmap.
The following portion of this function creates the memory DCs and selects relative bitmaps into
different DCs:

……
pDC=CDC::FromHandle(lpDIS->hDC);
memDC.CreateCompatibleDC(pDC);
if(pBitmapMask != NULL)
{
memDCMask.CreateCompatibleDC(pDC);
memDCImage.CreateCompatibleDC(pDC);
}
pOld=memDC.SelectObject(pBitmap);
if(pBitmapMask != NULL)
{
pOldMask=memDCMask.SelectObject(pBitmapMask);
pBitmap->GetBitmap(&bm);
bmpImage.CreateCompatibleBitmap(pDC, bm.bmWidth, bm.bmHeight);
pOldImage=memDCImage.SelectObject(&bmpImage);
}
……

First, the address of the target DC is obtained from the DC handle by calling function CDC::
FromHandle(…). Then the memory DC that will select source image is created by calling function
CDC::CreateCompatibleDC(…). Since the bitmap could be copied only between compatible DCs, each time
we create a memory DC, we need to make sure that it is compatible with the target DC. Next, if the mask
bitmap exists, we create three DCs: memDC for normal bitmap, memDCMask for mask bitmap and memDCImage

79
Chapter 4. Button

for memory target bitmap (It will act as temparory target device DC). In this case, we also create a memory
bitmap using variable bmpImage, which is selected into memDCImage (This bitmap must also be compatible
with the DC that will select it). In the above implementation, we call function CBitmap::GetBitmap(…) to
obtain the dimension information of a bitmap and call function CBitmap::CreateCompatibleBitmap(…) to
create compatible memory bitmap). The mask bitmap is selected into memDCMask. The normal bitmap is
always selected into memDC.
The following portion of function MCBitmapButton::DrawItem(…) draws the bitmap by copying
normal and mask bitmaps among different DCs:

……
rect.CopyRect(&lpDIS->rcItem);
if(pBitmapMask == NULL)
{
pDC->BitBlt
(
rect.left,
rect.top,
rect.Width(),
rect.Height(),
&memDC,
0,
0,
SRCCOPY
);
}
else
{
memDCImage.BitBlt
(
0,
0,
rect.Width(),
rect.Height(),
pDC,
rect.left,
rect.top,
SRCCOPY
);
memDCImage.BitBlt
(
0,
0,
rect.Width(),
rect.Height(),
&memDC,
0,
0,
SRCINVERT
);
memDCImage.BitBlt
(
0,
0,
rect.Width(),
rect.Height(),
&memDCMask,
0,
0,
SRCAND
);
memDCImage.BitBlt
(
0,
0,
rect.Width(),
rect.Height(),
&memDC,
0,
0,
SRCINVERT
);
pDC->BitBlt
(
rect.left,
rect.top,
rect.Width(),

80
Chapter 4. Button

rect.Height(),
&memDCImage,
0,
0,
SRCCOPY
);
}
memDC.SelectObject(pOld);
if(pBitmapMask != NULL)
{
memDCMask.SelectObject(pOldMask);
memDCImage.SelectObject(pOldImage);
}
}

Bitmap copy is implemented by calling function CDC::BitBlt(…). This function will copy the selected
bitmap from one DC to another. If there is no mask bitmap, we copy the normal bitmap (selected by memDC)
directly to the target DC (pointed by pDC). Otherwise, first we copy the image pattern from the target device
(pDC) to memory bitmap (selected by memDCImage). Then we copy normal bitmap and mask bitmap
(selected by memDC and memDCMask) to this memory bitmap three times, using different operation modes,
and copy the final result to the target DC (pDC). At last, we select the bitmaps out of DCs.

Using Class MCBitmapButton


In sample 4.5\Btn, automatic method is used to load the mask bitmap. There are two differences
between this sample and sample 4.4\Btn. First, in the new sample, a new bitmap resource “PLAYM” is
added to the application that is used as the mask bitmap. Second, variable CBtnDlg::m_btnPlay is declared
using MCBitmapButton instead of CBitmapButton (Also, we need to include the header file of
MCBitmapButton). No other change is needed.
With the above implementation, the button will have a transparent background. We can test this by re-
configuring the system colors.

4.6 Making Button Aware of Mouse Position


By now no button we’ve made could provide us with information of mouse position when it is pressed.
Although most of the time it is not necessary to know the exact coordinates of the mouse cursor, it may
help us create more powerful buttons if we have this information. For example, we can create a bitmap
button with four arrows pointing to different directions. When the user clicks mouse on different arrows,
we can let different commands be executed if we know the current cursor position.
Since class CButton is derived from CWnd, and CWnd handles different types of mouse events, we should
be able to trap mouse related messages into the member functions of CButton. Actually, all classes derived
from CWnd can trap mouse events such as left button down, left button up, left button double click, etc. In
order to implement the button described above, we need to trap left button up message, which is defined as
WM_LBUTTONUP under Windows.
Sample 4.6\Btn demonstrates how to handle mouse-related message for a button. It is based on sample
4.6\Btn, with a new button added to the application. The new button has four arrows pointing to different
directions, if the user clicks on any of the arrows, a message box will pop up displaying a different message
(Figure 4-8).

A button that is
aware of mouse
position
Figure 4-8. New button implemented in sample 4.6\Btn

81
Chapter 4. Button

Trapping Message WM_LBUTTONUP within Button


In the sample, WM_LBUTTONUP message handler is added to class MCBitmapButton. Like trapping any
other type of message, in order to handle WM_LBUTTONUP, we need to declare an afx_msg type function and
add message mapping macros. If the class is created by Class Wizard, this procedure could be very easy.
Otherwise, we must add everything manually.
First we need to declare an afx_msg type member function in the class:

class MCBitmapButton : public CBitmapButton


{
……
protected:
……
afx_msg void OnLButtonUp(UINT, CPoint);
……
DECLARE_MESSAGE_MAP()
};

There must be a DECLARE_MESSAGE_MAP macro in the class in order to let it support message mapping.
The message mapping macros are added to the implementation file as follows:

BEGIN_MESSAGE_MAP(MCBitmapButton, CBitmapButton)
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()

Message WM_LBUTTONUP will be trapped to member function MCBitmapButton::OnLButtonUp(…),


which has the following format:

void MCBitmapButton::OnLButtonUp(UINT nFlags, CPoint point)


{
}

We see that the mouse position is passed to parameter point of this function.
Because the commands are generally handled in the parent window of the button, we need to resend
mouse clicking information from the button to class CBtnDlg. In the sample application, this is
implemented through sending a user- defined message.

User-Defined Message
User defined messages can be treated the same with other standard Windows messages: they can be
sent from one window to another, and we can add message handlers for them. All user-defined messages
must have a message ID equal to or greater than WM_USER.
In the sample, a new message WM_BTNPOS is defined in file “MButton.h”:

#define WM_BTNPOS WM_USER+1000

By doing this, WM_BTNPOS becomes a message that can be used in the application. Please note that this
message can not be sent to other applications. If we want to send user-defined message among different
applications, we need to register that message to the system.
In function MCBitmapButton::OnLButtonUp(…), user defined message WM_BTNPOS is sent to the parent
window, with the current mouse position stored in LPARAM parameter:

void MCBitmapButton::OnLButtonUp(UINT nFlags, CPoint point)


{
CBitmapButton::OnLButtonUp(nFlags, point);
GetParent()->PostMessage
(
WM_BTNPOS, GetDlgCtrlID(), MAKELPARAM(point.x, point.y)
);
}

First the default implementation of function OnLButtonUp(…)is called. Then a CWnd type pointer of
parent window is obtained by calling function CWnd::GetParent(). Class CWnd has several member
functions that can be used to send Windows message, the most commonly used ones are CWnd::

82
Chapter 4. Button

SendMessage(…) and CWnd::PostMessage(…). The difference between the two functions is that after
sending out the message, CWnd::SendMessage(…) does not return until the message has been processed by
the target window, and CWnd::PostMessage(…) returns immediately after the message has been sent out. In
the sample, function CWnd::PostMessage(…) is used to send WM_BTNPOS message.
All messages in Windows have two parameters, WPARAM and LPARAM. For Win32 applications, both
WPARAM and LPARAM are 32-bit integers. They can be used to send additional information.
In MFC, usually message parameters are passed as arguments to message handlers, so they are rarely
noticed. For example, for message WM_LBUTTONDOWN, its WPARAM parameter is used to indicate if any of
CTRL, ALT, or SHIFT key is held down when the mouse button is up. In the message handler, this
information is mapped to the first parameter of CWnd::OnLButtonUp(…). Again, its LPARAM parameter
contains the information of current mouse position, which is mapped to the second parameter of
CWnd::OnLButtonUp(…). Both CWnd::SendMessage(…) and CWnd::PostMessage(…) have three parameters,
the first of which specifies message ID, and the rest two are WPARAM and LPARAM parameters. If we don’t
want to send additional message, we can pass 0 to both of them.
In the sample, we need to use both parameters: the parent window needs to know the control ID of the
button; also, it needs to know the current mouse position.
The button’s ID can be retrieved by calling function CWnd::GetDlgCtrlID(), it will be sent through
WPARAM parameter to the button’s parent. The x and y coordinates of mouse cursor can be combined together
to form an LPARAM parameter by using MAKELPARAM macro. Here, macro MAKELPARAM can combine two 16-
bit numbers to form a 32-bit message. If we provide two 32-bit numbers, only the lower 16 bits will be
used (Of course, screen coordinates won’t use more than 16 bits).
The message is received and processed in class CBtnDlg. In MFC, general message can be mapped to a
member function by using ON_MESSAGE macro. This type of message handler has two parameters, one for
receiving WPARAM information and the other for receiving LPARAM information. Also, it must return a LONG
type value.
The following code fragment shows how member function OnBtnPos(…) is declared in class CBtnDlg
(It will be used to receive WM_BTNPOS message):

class CBtnDlg : public CDialog


{
……
protected:
……
afx_msg LONG OnBtnPos(UINT, LONG);
DECLARE_MESSAGE_MAP()
};

In the implementation file, ON_MESSAGE macro is added as follows:

BEGIN_MESSAGE_MAP(CBtnDlg, CDialog)
……
ON_MESSAGE(WM_BTNPOS, OnBtnPos)
END_MESSAGE_MAP()

The control ID and the mouse information can be extracted within the message handler as follows:

LONG CBtnDlg::OnBtnPos(UINT wParam, LONG lParam)


{
CPoint pt;
UINT uID;

uID=wParam;
pt.x=LOWORD(lParam);
pt.y=HIWORD(lParam);
return (LONG)TRUE;
}

Sample
Sample 4.6\Btn has a four-arrow bitmap button. First a button resource is added to the dialog template,
whose ID is IDC_PLAY_POS and caption text is “PLAYPOS” (bitmaps will be loaded through automatic

83
Chapter 4. Button

method). Two new bitmap resources “PLAYPOSU” and “PLAYPOSD” are also added to the application,
which will be used to draw button’s “up” and “down” states.
We need to know the sizes and the positions of four arrows within the bitmap button so we can judge if
the mouse cursor is over any of the arrows. Within class CBtnDlg, a CRect type array with size of 4 is
declared for this purpose. Their values are initialized in function CBtnDlg::OnInitDialog(). Also an
MCBitmapButton type variable m_btnPlayPos is declared to implement this new button:

class CBtnDlg : public CDialog


{
……
protected:
……
MCBitmapButton m_btnPlayPos;
……
CRect m_rectBtnPos[4];
……
};

The following portion of function CBtnDlg::OnInitDialog() shows how array m_rectBtnPos is


initialized:

BOOL CBtnDlg::OnInitDialog()
{
……
m_btnPlayPos.AutoLoad(IDC_PLAY_POS, this);
m_btnPlayPos.GetClientRect(rect);
x=rect.Width()/2;
y=rect.Height()/2;
m_rectBtnPos[0]=CRect(x-2, rect.top, x+2, y);
m_rectBtnPos[1]=CRect(rect.left, y-2, x, y+2);
m_rectBtnPos[2]=CRect(x-2, y, x+2, rect.bottom);
m_rectBtnPos[3]=CRect(x, y-2, rect.right, y+2);
……
}

Here, we call function CWnd::GetClientRect() to retrieve the button size. We need to calculate the
sizes and positions of the arrows after bitmaps have been loaded. This is because a button will be resized
according to the bitmap size after it is initialized.
Function CBtnDlg::OnBtnPos(…) is implemented just for the purpose of demonstration: if any of the
four arrows is pressed, a message box will pop up displaying a different message:

LONG CBtnDlg::OnBtnPos(UINT wParam, LONG lParam)


{
CPoint pt;

if(wParam == IDC_PLAY_POS)
{
pt.x=LOWORD(lParam);
pt.y=HIWORD(lParam);
if(m_rectBtnPos[0].PtInRect(pt))AfxMessageBox(“Hit upward arraow”);
if(m_rectBtnPos[1].PtInRect(pt))AfxMessageBox(“Hit leftward arraow”);
if(m_rectBtnPos[2].PtInRect(pt))AfxMessageBox(“Hit downward arraow”);
if(m_rectBtnPos[3].PtInRect(pt))AfxMessageBox(“Hit rightward arraow”);
}
return (LONG)TRUE;
}

The application is now ready for compile. Based on this method, we can implement bitmap buttons
with more complicated functionality.

84
Chapter 4. Button

4.7 Mouse Sensitive Button

Setting Capture
In this section, we are going to create a very special button. It is not a push button, nor a check box or
radio button. The button has two states: normal and highlighted. Generally, the button will stay in normal
state. When the mouse cursor is within the button’s rectangle (with no mouse button being clicked), the
button will become highlighted, and if the mouse moves out of the rectangle, the button will resume to
normal state.
To implement this type of button, we need to trap mouse messages and implement handlers. One of the
messages we need to handle is WM_MOUSEMOVE, which will be sent to a window if the mouse cursor is
moving over the button. We need to respond to this message and set button’s state to “highlighted” when
the mouse cursor first comes into the button’s rectangle. From now on, we need to keep an eye on the
mouse’s movement, if the mouse moves out of the rectangle, we need to resume button’s normal state.
However, since message WM_MOUSEMOVE will only be sent to a window when the mouse cursor is within
it, it is difficult to be informed of the event that mouse has just left the button’s window. This is because
once the mouse leaves, the button will not be able to receive WM_MOUSEMOVE message anymore.
To help us solve this type of problems, Windows provides a technique that can be used to track
mouse’s activities after it leaves a window. This technique is called Capture. By using this method, we
could capture all the mouse-related messages to one specific window, no matter where the mouse is.
We can call function CWnd::SetCapture() to set capture for a window. The capture can also be
released by calling function ::ReleaseCapture(), which is a Win32 API function. Besides using this
function, the capture can also be removed by the operating system under certain conditions. If this happens,
the window that is losing capture will receive a WM_CAPTURECHANGED message.

New Class
Sample 4.7\Btn demonstrates how to implement “mouse sensitive button”. In the application, a new
class MCSenButton is created for this purpose, and is defined as follows:

class MCSenButton : public MCBitmapButton


{
public:
MCSenButton();

protected:
BOOL m_bCheck;
afx_msg void OnMouseMove(UINT, CPoint);
afx_msg void OnCaptureChanged(CWnd *);
DECLARE_DYNAMIC(MCSenButton)
DECLARE_MESSAGE_MAP()
};

The class contains only a constructor, two message handlers, and a Boolean type variable m_bCheck.
Variable m_bCheck will be used to indicate the button’s current state: it is TRUE if the button is currently
highlighted, and is FALSE if the button is in the normal state. Within the constructor, this variable is
initialized to FALSE:

MCSenButton::MCSenButton() : MCBitmapButton()
{
m_bCheck=FALSE;
}

Functions MCSenButton::OnMouseMove(…) and MCSenButton::OnCaptureChanged(…) are message


handlers for WM_MOUSEMOVE and WM_CAPTURECHANGED respectively. Like all other types of messages, their
message mapping macros should be added in the implementation file:

IMPLEMENT_DYNAMIC(MCSenButton, MCBitmapButton)
BEGIN_MESSAGE_MAP(MCSenButton, MCBitmapButton)
ON_WM_MOUSEMOVE()
ON_WM_CAPTURECHANGED()

85
Chapter 4. Button

END_MESSAGE_MAP()

Here, ON_WM_MOUSEMOVE and ON_WM_CAPTURECHANGED are message mapping macros defined by MFC.
The following two functions handle above two messages:

void MCSenButton::OnCaptureChanged(CWnd *pWnd)


{
m_bCheck=FALSE;
SetState(FALSE);
Invalidate();
}

void MCSenButton::OnMouseMove(UINT nFlags, CPoint point)


{
if(m_bCheck == FALSE)
{
SetCapture();
m_bCheck=TRUE;
SetState(TRUE);
Invalidate();
}
else
{
CRect rect;

GetClientRect(rect);
if(!rect.PtInRect(point))
{
ReleaseCapture();
m_bCheck=FALSE;
SetState(FALSE);
Invalidate();
}
}
}

In function MCSenButton::OnMouseMove(…), if m_bCheck is FALSE, it means the button is in the


normal state. In this case, we need to set mouse capture, change the button to “highlighted” state, and
redraw the button. If the button is currently highlighted, we need to check the current position of mouse
cursor, if it has moved out of the button window, we should resume button’s normal state, and redraw the
button. Here, CButton::SetState(…) is used to set the button to different states (it will cause the bitmap
button to use the corresponding bitmap), and CWnd::Invalidate() is used to cause the button to be
redrawn.
In function MCSenButton::OnCaptureChanged(…), we need to change m_bCheck back to FALSE, and
resume button’s normal state.

Implementation
In the sample, four new buttons are added to the application. The IDs of these new buttons are
IDC_MOUSE_SEN_1, IDC_MOUSE_SEN_2, IDC_MOUSE_SEN_3 and IDC_MOUSE_SEN_4 respectively. An
MCSenButton type array m_btnBmp (The array size is 4) is declared in class CBtnDlg and initialized in
function CBtnDlg::OnInitDialog() as follows:

class CBtnDlg : public CDialog


{
……
protected:
……
MCSenButton m_btnBmp[4];
……
};

BOOL CBtnDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
int x, y;
int i;

……
for(i=IDC_MOUSE_SEN_1; i<=IDC_MOUSE_SEN_4; i++)

86
Chapter 4. Button

{
m_btnBmp[i-IDC_MOUSE_SEN_1].SubclassDlgItem(i, this);
m_btnBmp[i-IDC_MOUSE_SEN_1].LoadBitmaps
(
IDB_BITMAP_BTNUNCHECK, IDB_BITMAP_BTNCHECK
);
m_btnBmp[i-IDC_MOUSE_SEN_1].SizeToContent();
}
……
}

Here, we use subclass instead of automatic method to load bitmaps. Also, we use
IDB_BITMAP_BTNUNCHECK and IDB_BITMAP_BTNCHECK to implement button’s normal and highlighted states
respectively.
Because mouse related messages are handled within the member functions of class MCSenButton, once
we declare variables within it, the buttons will automatically become mouse sensitive. There is no need for
us to write extra code for handling mouse messages outside the class.

Summary
1) We can use class CBitmapButton to implement bitmap buttons. To use this class, we need to prepare 1
to 4 bitmap resources indicating button’s different states, then use class CBitmapButton to declare
variables, and call either CBitmapButton::AutoLoad(…) or CBitmapButton::LoadBitmaps(…) to
associate the bitmap resources with the buttons.
2) To use function CBitmapButton::AutoLoad(…), the bitmap resources must have string IDs, and must
be created by suffixing ‘U’, ‘D’, ‘F’ or ‘X’ to the button’s caption text.
3) Buttons, check boxes and radio buttons implemented by class CButton can display user-provided
bitmaps by calling function CButton::LoadBitmap(…). With this method, the button could be
associated with only one image at any time. Also, its focused state will be indicated by drawing a dash-
bordered rectangle over button’s face.
4) We can call function CBitmapButton::LoadBitmaps(…) at any time to change the associated bitmaps.
This provides us a way of implementing check box and radio button using push button.
5) Irregular shape button can be implemented by drawing images with transparency. We can prepare a
normal image and a black-and-white mask image. When drawing the button, only the unmasked region
of the normal image should be output to the target device.
6) A button can handle mouse-related messages. If we want to know the mouse position when a button is
being pressed, we can trap WM_LBUTTONUP message.
5) We can implement mouse sensitive button by handling WM_MOUSEMOVE message and setting window
capture.

87
Chapter 5. Common Controls

Chapter 5

Common Controls

I
n this chapter we will discuss some common controls that can be included in a dialog box. These
controls include spin control, progress bar control, slider control, tree control, tab control, animate
control and combo box. These controls can all be included in a dialog template as resources. Besides,
all the controls have corresponding MFC classes that can be used to implement them.
In Developer Studio, Class Wizard has some features that can be used to add member variables and
message handlers for the common controls. This simplifies the procedure of writing source code.

5.1 Spin Control


Spin control is a rectangular button with two arrows pointing to opposite directions (either vertically or
horizontally), it is one of the most commonly used controls in a dialog box. Usually a spin is used together
with another control, in most cases this control is an edit box (Though not common, this control can also be
a button or a static control). By clicking on one of the arrows, the contents in the accompanying control will
change accordingly indicating current position of the spin control.
The control used together with the spin control is called spin’s Buddy Control. In MFC, it is very easy
to use spin control along with edit box, they are specially designed to cooperate together.

Using Spin Control with Edit Box


By default, a spin control should be associated with an edit box. Usually this type of edit box contains
a number indicating the current position of spin. If we make no modification, the range of this number will
be from 0 to 100. If the spin’s orientation is vertical, pressing the downward arrow will cause the number to
increment. If the spin’s orientation is horizontal, pressing the leftward arrow will have the same effect.
When adding a spin control resource, we must set several styles in order to make it work correctly. In
the property page whose caption is “Spin properties”, by clicking “Styles” tab, we will see all the
customizable styles (Figure 5-1). Here, style “Auto buddy” will allow spin’s buddy to be automatically
selected (By enabling this style, we do not need to set spin’s buddy within the program). If we check this
selection, the window prior to the spin control in the Z order will be used as the spin’s buddy window.

Figure 5-1. Spin control styles

To let buddy window be automatically selected, we must first add resource for the buddy control then
resource for the spin control. For example, if we want to use an edit box together with a spin control, we
can add edit box resource first, then add spin control next. We can check controls’ Z order by executing

88
Chapter 5. Common Controls

command Layout | Tab order (or pressing CTRL+D keys). The Z-order of the controls can be reordered
by clicking them one by one according to the new sequence.
In the left-bottom corner of “Spin properties” property sheet, there is a combo box labeled
“Alignment”. This allows us to specify how the spin will be attached to its buddy window when being
displayed. If we select “Unattatched” style, the spin and the buddy control will be separated. Usually we
select either “Left” or “Right” style to attach the spin to the left or right side of the buddy control. In this
case, the size and position of the spin control in the dialog template has no effect on its real size and
position in the runtime, its layout will be decided according to the size and position of the buddy control.
Also, there is a “Set buddy integer” check box. If this style is set, the spin will automatically send out
message to its buddy control (must be an edit box) and cause it to display a series of integers when the
spin’s position is changed. By default, the integer contained in the edit box will increment or decrement
with a step of 1. If we want to customize this (For example, if we want to change the step or want to display
floating point numbers), we should uncheck this style and set the buddy’s text within the program.
Sample 5.1-1\CCtl demonstrates how to use spin control with edit control and set buddy automatically.
The sample is a standard dialog based application generated by Application Wizard, with all default
settings. The resource ID of the main dialog template is IDD_CCTL_DIALOG, which contains two spin
controls and two edit boxes. Both spins have “Auto buddy” and “Set buddy integer” styles. Also, their
alignment styles are set to “Right” (Figure 5-2).

Figure 5-2. Dialog template IDD_CCTRL_DIALOG

Without adding a single line of code, we can compile the project and execute it. The spin controls and
the edit controls will work together to let us select integers (Figure 5-3).

Figure 5-3. Sample 5-1\CCtrl

In MFC, spin control is implemented by class CSpinButtonCtrl. We need to call various member
functions of this class in order to customize the properties of the spin control. In sample 5.1-2\CCtl, the
control’s buddy is set by calling function CSpinButtonCtrl::SetBuddy(…) instead of using automatic
method. The best place to set a spin’s buddy is in the dialog box’s initialization stage. This corresponds to
calling function CDialog::OnInitDialog().
Sample 5.1-2\CCtl is based on sample 5.1-1\CCtl. Here, style “Auto buddy” is removed for two spin
controls. Also, some changes are nade to set the spin buddies manually.
There are two ways of accessing a specific spin: we can use a spin’s ID to call function
CWnd::GetDlgItem(…), which will return CWnd type pointer to the spin control; or we can add a

89
Chapter 5. Common Controls

CSpinButtonCtrl type variable for the spin control (through using Class Wizard). The following code
fragment shows how the buddy of the two spin controls are set using the first method:

BOOL CCCtlDlg::OnInitDialog()
{
……
(
(CSpinButtonCtrl *)GetDlgItem(IDC_SPIN_VER)
)->SetBuddy(GetDlgItem(IDC_EDIT_VER));
(
(CSpinButtonCtrl *)GetDlgItem(IDC_SPIN_HOR)
)->SetBuddy(GetDlgItem(IDC_EDIT_HOR));

return TRUE;
}

Since CWnd::GetDigItem(…) returns a CWnd type pointer, we need to first cast it to CSpinButtonCtrl
type pointer in order to call any member function of class CSpinButtonCtrl. The only parameter that needs
to be passed to function CSpinButtonCtrl::SetBuddy(…) is a CWnd type pointer to the buddy control,
which can also be obtained by calling function CWnd::GetDlgItem(…).
Spin controls implemented in sample 5.1-2\CCtl behaves exactly the same with those implemented in
sample 5.1-1\CCtl.

5.2 Customizing the Properties of Spin Control


We can customize a spin control’s properties in function CDialog::OnInitDialog(). The following
three functions are the most commonly used ones for doing customization: