Earlier this month, I put together my first C++ sample in about a hundred years.
I did that using win32 and Visual Studio 2010. Why? Well, a
surprising number of folks are doing real work on impressive
applications in C++, and not just inside Microsoft. Some are even
working on refacing C++ applications with
WPF.
Besides, it's good to learn, or re-learn, new things. It often
gives you a different perspective on how to solve problems. Plus,
since I'll be doing some intro videos in C++, I figured it would be
good to get back into it.
So, continuing in the same theme, I decided to try out MFC for
the first time ... ever. I wrote some Borland OWL code back in the
early 90s, but I'm sure there are more differences than just
TEverything vs. CEverything.
For the examples here, you'll need Visual Studio 2010 (I used
the RC) and Windows 7. You can run it on older versions of Windows,
but the Win7 ribbon style likely won't look the same.
In this sample we'll:
- Create an MFC Document/View application with a ribbon
control
- Create a custom ribbon category
- Add ribbon buttons
- Change the application theme
- Change the MDI tab container theme
Let's go!
Creating the Project
In Visual Studio 2010, pick the Visual C++ language. For many of
us with language-specific IDE preferences, it's down under "Other
Languages". Select MFC and then the MFC Application option.
Give your project a name and hit OK. As was the case in the
Win32 project, you'll get an application Wizard that walks you
through the startup steps.
The second page of the wizard has all the Application Type
options. As someone who usually works in WPF and Silverlight, I
am totally jealous! The wizard offers you options for Basic apps,
Windows Explorer type apps, Office type apps and even Visual Studio
type applications. You can even pick the styles and colors you
want, right from this page:
For this sample, I picked the following options:
- Multiple Documents (Tabbed)
- Document/view architecture support (default)
- English language (default)
- Use Unicode Libraries (default)
- Office project style
- Office 2007 (Blue theme) visual style
- Enable visual style switching (default)
- Use MFC in a shared DLL (default)
The next page of the wizard offers you the ability to support
compound documents / OLE embedding
I decided not to bother with that for this sample. Leave it at
"none"
The next wizard page is for Document Template Properties. This
allows you to create all the metadata for your document type. On
this page, I typed in "hello" for the file extension
Next, we move on to Database support. If you're going to make
calls into OLE DB or ODBC, you can set the right options here. I
decided not to complicate things and instead just leave it at the
default of "none"
Next stop, "User Interface Features." Are you kidding me? This
is awesome. From this one step, you can stick with classic menus,
menu bar and toolbar, or a ribbon. You can also set the style of
your main window and, if traditional MDI, the child windows.
Excellent! I left the defaults on, using a ribbon as the
commandbar/menu choice.
The next tab is for advanced features. From here you can choose
support for activeX controls, integration with MAPI
(outlook/exchange), sockets and more. I left all the defaults in
place
The final page shows you the classes that will be generated
Hit "Finish" and look at the generated project:
Nice! You get classes for your application and the windows, as
well as for your document and document view. You also get a bunch
of resources for the ribbon icons. Let's run this baby and see what
it looks like.
Wow. Just...wow. Sure, that's a pretty busy app (selected a
bunch of options), but that is a great structure coming right out
of the wizard. Sure beats the blank slate we usually see. Here's a
some close-ups
Ok, maybe I should have picked the Dialog project to do my first
Hello World. There's a lot here! :) Nah. Let's have at this.
Modifying the Ribbon
One thing you noticed is that the design of the application is
more Office 2007 and less Windows 7. That's ok. During the app
wizard, had I chosen "Windows 7" instead of "Office 2007", I would
have gotten the win7 style ribbons and UI.
The end result of combining "Office" with "Windows 7" looks a
little hokey. Perhaps other combinations would work better.
So, we'll stick to the Office 2007 theme for this example.
However, if you want to change it, since we support changing the
overall theme and code was generated to handle that, all you need
to change is the parameter that sets the "App Look":
// Office 2007 style
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"),
ID_VIEW_APPLOOK_OFF_2007_BLUE);
}
// Change to this for Windows 7 style
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"),
ID_VIEW_APPLOOK_WINDOWS_7);
}
Opening the Ribbon Resource Editor
Visual Studio includes a designer for the Ribbon. To use it,
double click the MfcHelloWorld.rc file in the Resource Files
folder. Your file may be named differently, but will have a "rc"
extension. Then, find the Ribbon folder and double-click the
IDR_RIBBON resource folder
Once you see the ribbon displayed, click on the toolbox to
display the ribbon controls
Adding a Ribbon Category with Buttons
Drag a category from the Ribbon Editor toolbox next to the Home
category on the ribbon resource editor. Give the category the
caption "View" by right-clicking and selecting properties. Also set
the keys property to "V" (for the accelerator key) and the large
and small images the same as those used by "Home". If you were to
use a different image set, you'd put those keys here.
In the new "View" category, right-click the default panel and
set its caption to "Theme". Then drag two buttons on to the Theme
panel.
The first button has the following properties:
I picked some pre-existing images just to give the button
something to size around. You'd want to load in new images in a
real application.
And a menu items collection with these items. We're reusing IDs
from an existing function, so the IDs are important.
Windows 2000 |
ID_VIEW_APPLOOK_WIN_2000 |
Office XP |
ID_VIEW_APPLOOK_OFF_XP |
Windows XP |
ID_VIEW_APPLOOK_WIN_XP |
Windows 7 |
ID_VIEW_APPLOOK_WINDOWS_7 |
Visual Studio 2005 |
ID_VIEW_APPLOOK_VS_2005 |
Visual Studio 2008 |
ID_VIEW_APPLOOK_VS_2008 |
Office 2007 Blue |
ID_VIEW_APPLOOK_OFF_2007_BLUE |
Office 2007 Black |
ID_VIEW_APPLOOK_OFF_2007_BLACK |
Office 2007 Silver |
ID_VIEW_APPLOOK_OFF_2007_SILVER |
The second button has these properties:
And a menu items collection with these items:
3D |
ID_TAB_THEME_3D |
3D OneNote |
ID_TAB_THEME_3D_ONENOTE |
3D Rounded |
ID_TAB_THEME_3D_ROUNDED |
3D Visual Studio |
ID_TAB_THEME_3D_VISUAL_STUDIO |
Flat |
ID_TAB_THEME_FLAT |
The View category should look something like this when
complete
If you run the application now, the theme buttons will be
disabled.
Wiring up the Ribbon Buttons
Now to wire it all up.
In the MainFrm.h header file, add a prototype for
OnAppTheme():
afx_msg void OnAppTheme();
In the code in MainFrm.cpp, around
CMainFrame::OnApplicationLook, add the following empty
function:
void CMainFrame::OnAppTheme()
{
}
Finally, inside the message map block in MainFrm.cpp, add the
highlighted line (handler for ID_APP_THEME):
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx)
ON_WM_CREATE()
ON_COMMAND(ID_APP_THEME, &CMainFrame::OnAppTheme)
ON_COMMAND(ID_WINDOW_MANAGER, &CMainFrame::OnWindowManager)
ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)
ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook)
ON_COMMAND(ID_VIEW_CAPTION_BAR, &CMainFrame::OnViewCaptionBar)
ON_UPDATE_COMMAND_UI(ID_VIEW_CAPTION_BAR, &CMainFrame::OnUpdateViewCaptionBar)
ON_COMMAND(ID_TOOLS_OPTIONS, &CMainFrame::OnOptions)
END_MESSAGE_MAP()
If you don't take those three steps, the ribbon button will not
be enabled. The handlers for the menu options themselves are
already set, as they fall under the
ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000 ... ) block.
Now run the application and switch between the various
application themes using the new ribbon button. Pretty nice, eh?
:)
Aside: Where the ribbon data is Stored
You may wonder where all that ribbon config information is
stored. If you look under your resource files, you'll see a
ribbon.mfcribbon-ms file. Double-click that and it will open in the
XML editor so you can see the contents. The ribbon resource editor
overwrites/modifies this file, so don't hand-edit it unless you
know what to expect.
Changing the Document Tab Style
The previous example worked so well because we simply provided
another UI entry point into existing code. What about brand new
code? For this part, we'll implement an option to change the
document tab style.
Create the Function Prototypes
In MainFrm.h, add our three prototype functions. The first is
for the drop-down button, the second is for the menu options inside
the button. The third is called by the button that sets the
look.
afx_msg void OnTabTheme();
afx_msg void OnTabLook(UINT id);
afx_msg void OnUpdateTabLook(CCmdUI* pCmdUI);
Create the Functions
Next, create the actual functions in MainFrm.cpp. We'll add the
implementation code shortly.
void CMainFrame::OnTabTheme()
{
}
void CMainFrame::OnTabLook(UINT id)
{
}
void CMainFrame::OnUpdateTabLook(CCmdUI* pCmdUI)
{
}
Add the Message Map
Inside the Message Map block in MainFrm.cpp, add the following
three lines
ON_COMMAND(ID_TAB_THEME, &CMainFrame::OnTabTheme)
ON_COMMAND_RANGE(ID_TAB_THEME_3D, ID_TAB_THEME_FLAT, &CMainFrame::OnTabLook)
ON_UPDATE_COMMAND_UI_RANGE(ID_TAB_THEME_3D, ID_TAB_THEME_FLAT, &CMainFrame::OnUpdateTabLook)
Compile and run to make sure the app compiles and the tab theme
button is enabled.
Add Application Field to Hold Tab Style
Open up MfcHelloWorld.h and add in the following:
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
// Implementation
UINT m_nAppLook;
UINT m_nTabLook;
BOOL m_bHiColorIcons;
The new line is line 6, m_nTabLook. Like m_nAppLook, this is
going to hold an ID that specifies the look for the tab controls in
the app.
Inside the constructor CMainFrame::CMainFrame in MainFrm.cpp,
add the TabLook line right under the AppLook line. This will
initialize the value from stored settings (or the default
specified)
theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"), ID_VIEW_APPLOOK_OFF_2007_BLUE);
theApp.m_nTabLook = theApp.GetInt(_T("TabLook"), ID_TAB_THEME_3D_ONENOTE);
Inside CMainFrame::OnCreate in MainFrm.cpp, add in a new line
for OnTabLook right under the call to OnApplicationLook. This will
call our function to update the look.
OnApplicationLook(theApp.m_nAppLook);
OnTabLook(theApp.m_nTabLook);
Inside the same function, remove the lines that
sets the Tab Control style. We're going to move these to another
function, so you may want to just cut and paste them or comment
them out for now. In any case, make sure they aren't present in the
OnCreate function.
CMDITabInfo mdiTabParams;
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ONENOTE; // other styles available...
mdiTabParams.m_bActiveTabCloseButton = TRUE; // set to FALSE to place close button at right of tab area
mdiTabParams.m_bTabIcons = FALSE; // set to TRUE to enable document icons on MDI taba
mdiTabParams.m_bAutoColor = TRUE; // set to FALSE to disable auto-coloring of MDI tabs
mdiTabParams.m_bDocumentMenu = TRUE; // enable the document menu at the right edge of the tab area
EnableMDITabbedGroups(TRUE, mdiTabParams);
Handle the Menu Button Click and Set the Field
OnUpdateTabLook takes an instance of a UI control. OnTabLook
takes the command ID for a control. Following the pattern used by
the application look, we'll set the radio button on our menu to
indicate the current tab style.
void CMainFrame::OnTabLook(UINT id)
{
theApp.m_nTabLook = id;
theApp.WriteInt(_T("TabLook"), theApp.m_nTabLook);
}
void CMainFrame::OnUpdateTabLook(CCmdUI* pCmdUI)
{
pCmdUI->SetRadio(theApp.m_nTabLook == pCmdUI->m_nID);
}
Now is a good time to save, build and run. When you select the
menu to change the tab style, you should see the radio button
change along with it. The tab theme won't change, but the menu will
otherwise work. If you close and re-open the application, your
selected theme should have the radio button next to it.
Actually Change the Tab Theme
Next we'll add in the switch statement to actually change the
tab theme. open up some space in between the two lines in OnTabLook
and add in the switch statement that handles all the tab looks we
defined.
void CMainFrame::OnTabLook(UINT id)
{
theApp.m_nTabLook = id;
CMDITabInfo mdiTabParams;
mdiTabParams.m_bActiveTabCloseButton = TRUE; // set to FALSE to place close button at right of tab area
mdiTabParams.m_bTabIcons = FALSE; // set to TRUE to enable document icons on MDI taba
mdiTabParams.m_bAutoColor = TRUE; // set to FALSE to disable auto-coloring of MDI tabs
mdiTabParams.m_bDocumentMenu = TRUE; // enable the document menu at the right edge of the tab area
switch (theApp.m_nTabLook)
{
case ID_TAB_THEME_3D:
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D;
break;
case ID_TAB_THEME_3D_ONENOTE:
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ONENOTE;
break;
case ID_TAB_THEME_3D_ROUNDED:
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ROUNDED;
break;
case ID_TAB_THEME_3D_VISUAL_STUDIO:
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_VS2005;
break;
case ID_TAB_THEME_FLAT:
mdiTabParams.m_style = CMFCTabCtrl::STYLE_FLAT_SHARED_HORZ_SCROLL;
break;
}
EnableMDITabbedGroups(TRUE, mdiTabParams);
theApp.WriteInt(_T("TabLook"), theApp.m_nTabLook);
}
All we're doing in this function is mapping from the menu
options to the built-in tab themes, and building a parameter set
containing that style information as well as a few other
options.
When you run the app, change the tab themes around and take a
look at how the MDI tabs switch with them. Close and open the app,
and notice that the settings are saved.
Feel free to play with the other tab parameter settings. For
example, you can put the close button on the far right, you can
stop the auto-coloring of the tabs, add or remove the document menu
and even add icons. All very nice.
Conclusion
I'm impressed. I have a feeling that, given the need, I could
get up to speed in MFC in a reasonably short amount of time (not
saying it would be good code, but it would work <g>). It
helps that I did do windows programming in C++ a zillion years ago,
so the concepts were familiar. It also helps that the MFC
application wizard generates so much structure for your right out
of the bad.
Ok MFC gurus. What did I do wrong in the above code? Any
anti-patterns? Suggestions for improvement? I especially wonder
about the empty functions in the message map just to enable the
menu buttons.