Contents
Introduction
The Ribbon was originally designed by the Office team to replace their applications' complex systems of menus
and toolbars. In Windows 7, Microsoft released a reusable COM-based Ribbon component for use in native Win32 applications.
The native Ribbon is slightly different from the other Ribbon components in Office, WPF, and MFC, but they all
share the same basic layout and functionality.
Because the Ribbon has lots of features, this first article will cover just the basics of the Ribbon and its
COM interfaces, and will demonstrate the steps required to create an app from scratch that uses a simple Ribbon.
If you want to dive right into Ribbon development, check out the other Ribbon articles here on CodeProject, or
the Ribbon classes in the forthcoming WTL 8.1.
The system requirements for building the sample code are Visual Studio 2008, WTL
8.0, and the Windows
7 SDK. If you are running Windows 7 or Server 2008 R2, you have everything you need. If you are using Vista
or Server 2008, you must install service pack 2 and the platform
update for your operating system to use the Ribbon.
Starting a New App
Prep work in CMainFrame
We'll start with a basic SDI app as created by the WTL AppWizard. This app does not have a toolbar (since the
Ribbon replaces the toolbar), but it does have a status bar. It uses a separate class for the view window, but
the view window won't do too much, since our focus is on getting the Ribbon up and running. Later on, we'll do
more with the view window when it comes to laying out the main frame's child controls.
The first step is to include UIRibbon.h, which contains the type and interface definitions we'll need
to use the Ribbon. Our app will implement two of those interfaces, and the implementation details are outlined
throughout the rest of the article. For starters, all we need to do is add IUIApplication
to the inheritance
list of CMainFrame
, then add stub implementations of the three methods:
#include <UIRibbon.h>
class CMainFrame :
public CFrameWindowImpl<CMainFrame>,
public CMessageFilter,
public CComObjectRootEx<CComSingleThreadModel>,
public IUIApplication
{
STDMETHODIMP OnCreateUICommand (
UINT32 uCmdID, UI_COMMANDTYPE nType,
IUICommandHandler** ppHandler )
{ return E_NOTIMPL; }
STDMETHODIMP OnDestroyUICommand (
UINT32 uCmdID, UI_COMMANDTYPE nType,
IUICommandHandler* pHandler )
{ return E_NOTIMPL; }
STDMETHODIMP OnViewChanged (
UINT32 uViewID, UI_VIEWTYPE nType, IUnknown* pView,
UI_VIEWVERB nVerb, INT32 nReason )
{ return E_NOTIMPL; }
};
Since CMainFrame
needs to be a COM object, it also derives from CComObjectRootEx
.
Also, the single CMainFrame
instance in the Run()
function is created using CComObjectStackEx
,
in order to use ATL's implementation of IUnknown
:
CComObjectStackEx<CMainFrame> wndMain;
Initializing the UI Framework
The first interaction we have with the Ribbon is through the IUIFramework
interface. CMainFrame
uses this interface to initialize and shut down the framework, so it needs to keep an interface pointer for the
lifetime of the app:
CComPtr<IUIFramework> m_pFramework;
In CMainFrame::OnCreate()
, we cocreate the framework's COM object:
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
m_pFramework.CoCreateInstance ( CLSID_UIRibbonFramework );
IUIApplication* pApp = this;
m_pFramework->Initialize ( m_hWnd, pApp );
return 0;
}
IUIFramework::Initialize()
takes the HWND
of the window that will contain the Ribbon,
and the IUIApplication
interface that the framework will use to communicate with the app. The framework
changes the appearance of the window to fit in with the Ribbon's visual guidelines. For example, the framework
removes the window's menu and draws controls in the caption bar. To ensure that those features work smoothly, the
framework requires that the window be a top-level window with the WS_CAPTION
style, and without the
WS_EX_TOOLWINDOW
style. IUIFramework::Initialize()
will return ERROR_INVALID_WINDOW_HANDLE
if the window does not meet those conditions.
In order to properly shut down the framework and release any resources it was using, we also need to call IUIFramework::Destroy()
in the WM_DESTROY
handler:
void CMainFrame::OnDestroy()
{
m_pFramework->Destroy();
m_pFramework.Release();
}
After adding the call to IUIFramework::Initialize()
, our frame window looks like this:
There is no Ribbon yet, of course, since we haven't defined the contents of our Ribbon, but the framework has
made the necessary visual tweaks like removing the menu.
Adding a Ribbon to the Application
The Ribbon works differently than other UI widgets that you've used before. One of the design principles is
that the definition of a Ribbon's contents should be separate from its visual design. An app can specify what commands
appear in the Ribbon, how they are organized and grouped, and so on. But the app has limited control over how the
Ribbon will actually look. This lets Microsoft change the Ribbon's appearance in the future with fewer compatibility
problems than existing ways of creating GUIs like dialog resources.
The steps in creating a Ribbon definition are:
- Create an XML file that defines the Ribbon's contents and add that file to your project.
- Compile the XML with the uicc tool.
- Add the files that uicc generates to your app's resources.
- Call
IUIFramework::LoadUI()
to create a Ribbon using the compiled definition.
Before we dive into the XML, we need to become familiar with the elements of the Ribbon.
Parts of the Ribbon
The Ribbon uses the notion of a command in many places. A command is more than a button, it's essentially
anything in the Ribbon that you can click and do things with. Commands have various properties, such as a text
label, an icon, and a shortcut key. Once you've made the list of commands in your app, you start building other
parts of the Ribbon out of those commands.
Here's a screen shot of the Paint window, showing the various parts of the Ribbon:
- Application menu: The button to the left of the first tab opens the Application menu. This is a command
itself, although it does not allow its appearance to be customized. The menu contains other commands.
- Quick access toolbar (QAT): The QAT is the toolbar that the Ribbon draws in the window's caption area.
You can add other commands to the toolbar by right-clicking them and selecting Add to Quick Access Toolbar
on the context menu. The down arrow at the right edge of the QAT shows a menu where you can customize the commands
in the toolbar and change the appearance of the QAT and the Ribbon.
- Tabs: The tab row shows all the tabs that you've defined in the XML file. Each tab is a command, and
it contains one or more groups.
- Groups: Each group is a collection of commands. A group has an optional label at the bottom, and the
Ribbon draws dividers between the groups. You might occasionally see the term chunk used instead of group.
This is not the official term, but it was used by the Office team when they were building the first Ribbon, and
it still gets used in unofficial written material like blog posts.
Starting the XML file
Here's the skeleton XML file that's our starting point:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns='http://schemas.microsoft.com/windows/2009/Ribbon'>
<Application.Commands>
</Application.Commands>
<Application.Views>
<Ribbon>
<Ribbon.Tabs>
</Ribbon.Tabs>
</Ribbon>
</Application.Views>
</Application>
Notice that some properties are written using child tags with the "object.property" naming convention,
similar to XAML. For example, <Application.Commands>
defines the Commands
property
of the <Application>
tag.
The first step is to create a few commands that we'll use in the <Ribbon>
sections. Each
command is defined in a <Command>
tag. <Command>
has several properties that
we'll see later, but for now, we'll use two: Name
and LabelTitle
. The Name
property is a string that identifies the command in the rest of the XML file, and LabelTitle
is a
string that sets the text of the UI element.
In order to create a button, we also need a group and a tab to put it in, so we start with three commands:
<Application.Commands>
<Command Name="tabMain" LabelTitle="Main" />
<Command Name="grpHello" LabelTitle="Hello Ribbon!" />
<Command Name="cmdClickMe" LabelTitle="Click me" />
</Application.Commands>
(The Microsoft samples I've seen use the "cmd" prefix in all command names, but I've chosen to use
more descriptive prefixes that indicate the function of each command.) Then we create a tab, a group, and a button
control in the group:
<Application.Views>
<Ribbon>
<Ribbon.Tabs>
<Tab CommandName="tabMain">
<Group CommandName="grpHello" SizeDefinition="OneButton">
<Button CommandName="cmdClickMe" />
</Group>
</Tab>
</Ribbon.Tabs>
</Ribbon>
</Application.Views>
This creates a tab that contains one group. Most of the tab's properties come from the associated <Command>
tag. There's a level of indirection here: You don't say what the text of a tab is, you say what command
the tab is, and the Ribbon looks up the text in the associated <Command>
tag. Similarly, the
group's label comes from the LabelTitle
property of the grpHello
command. Don't worry
about the SizeDefinition
attribute for now, it's just something that's required so the Ribbon knows
how to arrange the controls in the group.
Finally, there's a <Button>
tag that creates a regular push button. The separation of properties
into <Command>
tags is more useful for buttons, since it's possible to have the same command
in multiple places, and having that separation means you don't have to repeat the same properties in every place
that the command appears.
Compiling the Ribbon XML file
Now that we've created the Ribbon definition file, we need to incorporate it into the app. If you've saved the
above XML to a file called ribbon.xml, you can add it to the Visual Studio project by clicking Project|Add
Existing Item and selecting ribbon.xml.
The next step is to invoke the compiler (uicc.exe) that converts the XML to a binary format that the Ribbon
understands. Open the properties for ribbon.xml, and in the Custom build step settings, set the Command
line field to:
uicc.exe $(InputFileName) $(InputName).bml /header:$(InputName)ids.h
/res:$(InputName).rc /name:HelloRibbon
uicc generates three files when it compiles ribbon.xml:
- ribbon.bml: The compiled output. Microsoft's samples use the
.bml
extension, and I've followed
suit.
- ribbonids.h: A C header file that has
#define
s for all commands and strings that are defined
in the XML.
- ribbon.rc: A resource definition file that has definitions for string and icons referenced in the XML,
and one custom resource type that pulls in ribbon.bml. The
/name
switch controls the name of
this resource. In this example, the name is HELLORIBBON_RIBBON
. That is, the name is whatever is specified
in the /name
switch, uppercased, with "_RIBBON" appended.
We should also tell Visual Studio what files uicc creates by setting the Outputs field to "$(InputName).bml;$(InputName)ids.h;$(InputName).rc".
Next, we add the resources from ribbon.rc to our app's resources. In the Resource View tab, right-click
HelloRibbon.rc and select Resource Includes. In the Compile-time directives edit box, add:
#include "ribbon.rc"
Now when the app is compiled, the app's resources will include any resources that were listed in ribbon.rc.
uicc also verifies that the XML contains no errors, and doesn't break any of the Ribbon's layout rules (for example,
having the same command appear more than once in a group). If uicc finds any errors, it will fail to compile the
XML, and the project will not build.
Loading the Ribbon
The last step is to initialize the Ribbon using the compiled version of the XML. We do this by adding a call
to IUIFramework::LoadUI()
in CMainFrame::OnCreate()
:
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
m_pFramework.CoCreateInstance ( CLSID_UIRibbonFramework );
IUIApplication* pApp = this;
m_pFramework->Initialize ( m_hWnd, pApp );
m_pFramework->LoadUI ( _Module.GetResourceInstance(),
L"HELLORIBBON_RIBBON" );
return 0;
}
IUIFramework::LoadUI()
takes two parameters, the HMODULE
that contains the compiled
XML, and the resource name.
If we compile the app after making these changes, and ignore a couple of warnings from uicc about missing XML
tags, we'll see our first Ribbon!
Fixing uicc warnings
Before we move on, let's fix those warnings from uicc about missing ApplicationMenu
and QuickAccessToolbar
properties. We'll need one command for each of those elements:
<Application.Commands>
<Command Name="cmdApplicationMenu" />
<Command Name="cmdQAT" />
<Application./Commands>
Then we add two new tags under the <Ribbon>
tag:
<Application.Views>
<Ribbon>
<Ribbon.ApplicationMenu>
<ApplicationMenu CommandName="cmdApplicationMenu" />
</Ribbon.ApplicationMenu>
<Ribbon.QuickAccessToolbar>
<QuickAccessToolbar CommandName="cmdQAT" />
</Ribbon.QuickAccessToolbar>
</Ribbon>
</Application.Views>
With these changes, all of the required elements are in place, and uicc won't issue any more warnings.
Communicating with the Ribbon
We've already seen the IUIFramework
interface, which our app uses to set up and tear down the Ribbon
framework. There is another interface that our app uses to communicate directly with the Ribbon: IUIRibbon
.
The Ribbon passes this interface to our app via IUIApplication::OnViewChanged()
, so we'll start by
filling in that method.
First, add a member to CMainFrame
that will hold the IUIRibbon
interface:
CComQIPtr<IUIRibbon> m_pRibbon;
CMainFrame::OnViewChanged()
is called when the Ribbon is created, destroyed, or resized. (Technically,
it deals with application views, not the Ribbon specifically, but the Ribbon is the only view that has been
defined so far.) The parameters to OnViewChanged
are:
uViewID
: The view ID, currently always 0.
nType
: The view type, currently always UI_VIEWTYPE_RIBBON
.
pView
: An IUnknown
interface provided by the view.
nVerb
: The action that the view is performing: UI_VIEWVERB_CREATE
, UI_VIEWVERB_DESTROY
,
or UI_VIEWVERB_SIZE
.
nReason
: Not currently used.
When nVerb
is UI_VIEWVERB_CREATE
, the Ribbon is being created and we can query it
for an IUIRibbon
interface. When nVerb
is UI_VIEWVERB_DESTROY
, the Ribbon
is going away, so we release the IUIRibbon
interface:
STDMETHODIMP CMainFrame::OnViewChanged (
UINT32 uViewID, UI_VIEWTYPE nType, IUnknown* pView, UI_VIEWVERB nVerb,
INT32 nReason )
{
switch ( nVerb )
{
case UI_VIEWVERB_CREATE:
m_pRibbon = pView;
break;
case UI_VIEWVERB_DESTROY:
m_pRibbon.Release();
break;
}
return S_OK;
}
A more interesting case is UI_VIEWVERB_SIZE
. This tells us that the Ribbon is changing size, being
hidden, or being shown. Since that affects the layout of the main frame, we get the Ribbon's new height in pixels
and lay out the frame window:
case UI_VIEWVERB_SIZE:
if ( m_pRibbon )
{
HRESULT hr = m_pRibbon->GetHeight ( &m_cyRibbon );
if ( SUCCEEDED(hr) )
UpdateLayout();
}
break;
CFrameWindowImpl::UpdateLayout()
positions the various kinds of bars that WTL supports -- rebars,
toolbars, and status bars -- and then positions the view window to take up the remaining space. Since WTL doesn't
know about the Ribbon, we need to override UpdateLayout()
and use the Ribbon's height as part of the
calculations. Our override is pretty similar to CFrameWindowImpl::UpdateLayout()
, but it determines
how many pixels to reserve at the top of the client area by looking at m_cyRibbon
:
void CMainFrame::UpdateLayout ( BOOL bResizeBars )
{
CRect rect;
GetClientRect ( rect );
UpdateBarsPosition ( rect, bResizeBars );
rect.top += m_cyRibbon;
if ( m_hWndClient != NULL )
CWindow(m_hWndClient).SetWindowPos ( NULL, rect,
SWP_NOZORDER | SWP_NOACTIVATE );
}
After adding this UpdateLayout()
override, you can try minimizing the Ribbon and the view window
will resize accordingly:
If you make the window small enough, the Ribbon will hide itself entirely. Our UpdateLayout()
handles
that case as well.
Handling Notifications from Commands
IUICommandHandler
The next interface we'll use is IUICommandHandler
. The app implements this interface, and the Ribbon
calls its methods to read properties for each command and notify the app that it should execute a command.
When the Ribbon parses the compiled XML, it asks the app for a COM interface on each command. The app must return
an IUICommandHandler
interface for a command to be functional. This interface can be implemented by
any COM object, but for our sample app, we'll add the interface directly to CMainFrame
:
class CMainFrame :
...
public IUICommandHandler
{
STDMETHODIMP Execute (
UINT32 uCmdID, UI_EXECUTIONVERB nVerb, const PROPERTYKEY* pKey,
const PROPVARIANT* pCurrVal,
IUISimplePropertySet* pCmdProperties );
STDMETHODIMP UpdateProperty (
UINT32 uCmdID, REFPROPERTYKEY key,
const PROPVARIANT* pCurrVal,
PROPVARIANT* pNewVal );
};
Creating and destroying IUICommandHandlers
The Ribbon will call IUIApplication::OnCreateUICommand()
once per command to get an IUICommandHandler
interface, and IUIApplication::OnDestroyUICommand()
once when it no longer needs the interface. Since
CMainFrame
implements IUICommandHandler
for every command, CMainFrame::OnCreateUICommand()
is simple:
STDMETHODIMP CMainFrame::OnCreateUICommand (
UINT32 uCmdID, UI_COMMANDTYPE nType, IUICommandHandler** ppHandler )
{
return QueryInterface ( IID_PPV_ARGS(ppHandler) );
}
We don't have to do any cleanup when commands are destroyed, so CMainFrame::OnDestroyUICommand()
just returns a successful HRESULT
:
STDMETHODIMP CMainFrame::OnDestroyUICommand (
UINT32 uCmdID, UI_COMMANDTYPE nType, IUICommandHandler* pHandler )
{
return S_OK;
}
Inserting C command identifiers into the Ribbon XML
Notice that OnCreateUICommand()
and OnDestroyUICommand()
have command ID parameters.
By default, uicc generates IDs automatically. If you look at the ribbonids.h file, you'll see lines like
the following:
#define cmdClickMe 7
where uicc has used the Name
attribute of a <Command>
tag to create a C identifier.
Let's see how to control these identifiers and make them follow the C convention of being all uppercase.
Going back to the <Command>
tag for the Click Me button:
<Command Name="cmdClickMe" LabelTitle="Click me" />
we can add a Symbol
attribute that sets the identifier's name, and an Id
attribute
that sets its value. For example:
<Command Name="cmdClickMe" Symbol="RIDC_CLICK_ME"
Id="42" LabelTitle="Click me" />
(I've used RIDC
as a prefix by adding R
(ribbon) in front of the conventional prefix
IDC
.) With this change, ribbonids.h will now have:
#define RIDC_CLICK_ME 42
Name
can be any legal C identifier, and Id
can be an integer between 2 and 59999 inclusive.
The Id
value can be written as decimal or hexadecimal.
Executing a command
When the user clicks a button, the Ribbon calls the associated IUICommandHandler::Execute()
method.
The parameters to Execute()
are:
uCmdID
: The command ID.
nVerb
: A constant that indicates what kind of action the user is taking. In the sample app, the
verb is always UI_EXECUTIONVERB_EXECUTE
.
pKey
, pCurrVal
: For some commands, the Ribbon passes a property and its new value
in these parameters.
pCmdProperties
: A IUISimplePropertySet
collection containing additional properties
about the command. The sample app does not use this parameter.
Depending on the type of command, the Ribbon may pass Execute()
some information about the state
of the command. For example, when the user clicks a toggle button, the Ribbon passes the new state of the button
(toggled off or on) in the pCurrVal
parameter. We'll see an example later that uses this info. A simple
push button has no extra info, so we just look at the command ID to know which command is being executed:
STDMETHODIMP CMainFrame::Execute (
UINT32 uCmdID, UI_EXECUTIONVERB nVerb, const PROPERTYKEY* pKey,
const PROPVARIANT* pCurrVal, IUISimplePropertySet* pCmdProperties )
{
switch ( uCmdID )
{
case RIDC_CLICK_ME:
MessageBox ( _T("Thanks for clicking me!"), _T("Hello Ribbon!") );
break;
}
return S_OK;
}
Notice that the case
statement uses the RIDC_CLICK_ME
identifier that we set in the
<Command>
tag.
Using Toggle Buttons
Another type of control you can put in the Ribbon is a toggle button. These buttons function like check
boxes, in that each click toggles the state between on (pressed) and off (not pressed).
Adding a ToggleButton control
We'll use a toggle button to show and hide the status bar, similar to the View|Status Bar command that's
created by the WTL AppWizard. We'll need two new commands, one for a new group, and one for the button:
<Application.Commands>
<Command Name="grpStatusBar" LabelTitle="Status bar" />
<Command Name="cmdToggleStatusBar" Symbol="RIDC_TOGGLE_STATUS_BAR"
LabelTitle="Show status bar" />
</Application.Commands>
Then we add a new group to the Main tab that contains a ToggleButton
control:
<Tab CommandName="tabMain">
<Group CommandName="grpHello" SizeDefinition="OneButton">
<Button CommandName="cmdClickMe" />
</Group>
<Group CommandName="grpStatusBar" SizeDefinition="OneButton">
<ToggleButton CommandName="cmdToggleStatusBar" />
</Group>
</Tab>
And here's what the new button looks like, in the toggled-on state:
Writing IUICommandHandler::Execute for a ToggleButton
The implementation of IUICommandHandler::Execute()
is passed a key/value pair in the form of a
PROPERTYKEY
and a PROPVARIANT
. When Execute()
is called because the user
clicked a toggle button, the key is UI_PKEY_BooleanValue
, and the value is a boolean that indicates
the new state of the button: true means the button is now toggled on, and false means it's toggled off.
The header file UIRibbonPropertyHelpers.h contains some helper functions that make it easier to get and
set values in PROPVARIANT
s. Since the value sent for a toggle button is a boolean, we can use the
UIPropertyToBoolean()
function to read the value from the PROPVARIANT
:
#include <UIRibbonPropertyHelpers.h>
STDMETHODIMP CMainFrame::Execute (
UINT32 uCmdID, UI_EXECUTIONVERB nVerb, const PROPERTYKEY* pKey,
const PROPVARIANT* pCurrVal, IUISimplePropertySet* pCmdProperties )
{
switch ( uCmdID )
{
case RIDC_CLICK_ME:
break;
case RIDC_TOGGLE_STATUS_BAR:
{
BOOL bShowBar;
if ( NULL != pKey && UI_PKEY_BooleanValue == *pKey && NULL != pCurrVal )
{
BOOL bToggledOn;
UIPropertyToBoolean ( *pKey, *pCurrVal, &bToggledOn );
bShowBar = bToggledOn;
}
else
bShowBar = !::IsWindowVisible ( m_hWndStatusBar );
ShowStatusBar ( !bShowBar );
}
break;
}
}
void CMainFrame::ShowStatusBar ( BOOL bShow )
{
CWindow(m_hWndStatusBar).ShowWindow ( bShow ? SW_HIDE : SW_SHOW );
UpdateLayout();
}
Once you add this code to CMainFrame::Execute()
, you can click the toggle button and the status
bar will be shown or hidden with every click.
Setting command properties
Since a toggle button is toggled off by default, the initial state of our button isn't what we want; the status
bar starts out visible, so the button should start in the toggled-on state. However, there is no element in the
XML file for setting the button's initial state. Instead, the Ribbon queries for that property right after the
IUICommandHandler
for that button is created.
When the Ribbon needs to query a property of a command, it calls that command's IUICommandHandler::UpdateProperty()
method, which can return the property's new value in a PROPVARIANT
. Here's how we set the initial
state of the toggle button:
STDMETHODIMP CMainFrame::UpdateProperty (
UINT32 uCmdID, REFPROPERTYKEY key, const PROPVARIANT* pCurrVal,
PROPVARIANT* pNewVal )
{
if ( RIDC_TOGGLE_STATUS_BAR == uCmdID && UI_PKEY_BooleanValue == key &&
NULL != pNewVal )
{
UIInitPropertyFromBoolean ( key, TRUE, pNewVal );
return S_OK;
}
return E_NOTIMPL;
}
This is essentially doing the opposite of the code we just saw in Execute()
. It uses another helper
function in UIRibbonPropertyHelpers.h, UIInitPropertyFromBoolean()
, to store a boolean value
in a PROPVARIANT
. All of the UIInitPropertyFromXxx()
helpers take the PROPERTYKEY
and the new value so they can check that the value is compatible with the property's data type. If you pass an
incompatible data type, the compiler will issue an error.
If you set a breakpoint at the beginning of CMainFrame::UpdateProperty()
, you'll see that it gets
called a lot. There are many optional properties in the Ribbon XML that we haven't set. The Ribbon calls UpdateProperty()
to query for all of those properties, and falls back to reasonable defaults if the function returns E_NOTIMPL
.
That is why we have to check the key
parameter so we know when the Ribbon is querying for the button's
toggle state, and not some other property whose default value is sufficient.
Additional Properties of Commands
Adding tooltips and keytips
The Ribbon has built-in support for keyboard navigation. If you press the Alt key, you'll see tooltips
that tell you how to navigate through the Ribbon's elements:
These navigation tooltips are called keytips, and can be customized with the Keytip
attribute
of the <Command>
tag. For example, to change the Main tab's navigation key to "M",
make this change to the tab's <Command>
tag:
<Command Name="tabMain" Symbol="RIDT_MAIN" LabelTitle="Main" Keytip="M" />
This string, along with other string attributes, all show up as resources in the ribbon.rc file that
uicc creates. That way, the strings can be localized. The string resource IDs begin at 60001, but the IDs can be
customized just like the command IDs. To customize a string attribute, you create a sub-tag called <Command.Keytip>
to hold the keytip properties. Within that tag, create a <String>
tag that contains the C identifier
and resource ID:
<Command Name="tabMain" Symbol="RIDT_MAIN" LabelTitle="Main">
<Command.Keytip>
<String Symbol="RIDS_MAIN_KEYTIP" Id="54321">M</String>
</Command.Keytip>
</Command>
Each command can also have a tooltip that's shown when the mouse hovers over the button. The tooltip can have
a title, which is shown in bold, and a description. These strings are set through the TooltipTitle
and TooltipDescription
attributes of a <Command>
tag. Here's how to add a tooltip
to the toggle button:
<Command Name="cmdToggleStatusBar" Symbol="RIDC_TOGGLE_STATUS_BAR"
Keytip="T" LabelTitle="Show status bar"
TooltipTitle="Toggle the status bar"
TooltipDescription="Show or hide the status bar" />
As with the Keytip
, you can change the IDs of the tooltip strings by using a <String>
tag. Here's what the customized tooltip looks like:
Assigning images to buttons
Our buttons look pretty boring without any graphics, so let's see how to add some. The Ribbon supports only
two graphics formats: 32bpp BMP files for normal images, and 4bpp BMP files for high-contrast images. uicc will
issue an error if you use any other type of graphic. (I must apologize up-front for my lame-looking graphics. I
am galactically incapable of drawing anything good myself, so I've borrowed graphics from the sample Ribbon apps
in the Windows SDK.)
Each command can have four sets of images: large, small, large high-contrast, and small high-contrast. The Ribbon
chooses whether to use large or small images based on where the command appears. The two buttons we've added so
far are large buttons, so they will use large images. If you add those commands to the QAT, those buttons will
use small images. The two sets of high-contrast images are used when the system is using a high-contrast visual
theme; the choice about which image size to use remains the same.
The images are set via properties of the <Command>
tag. Here is the XML that sets the large
and small images to use for the Show status bar button:
<Command Name="cmdToggleStatusBar" ... >
<Command.LargeImages>
<Image Source="res/StatusBar_lg.bmp" />
</Command.LargeImages>
<Command.SmallImages>
<Image Source="res/StatusBar_sm.bmp" />
</Command.SmallImages>
</Command>
The Source
attributes are file paths relative to where the XML file is, so in this case the files
are in a res
subdirectory. uicc will include these images in the ribbon.rc file and generate
IDs for them. As with strings, you can customize the resource ID and the C identifier for an image by adding attributes
to the <Image>
tag:
<Image Source="res/StatusBar_lg.bmp" Id="12345" Symbol="RIDI_STATUS_BAR_LG" />
Within each of the four sets of images, there can be up to four <Image>
tags for use at various
DPI settings. In this example program, we have just one image for the default DPI of 96. This is fine, because
the Ribbon will scale the images as necessary if the system is using a higher DPI setting.
Here's how the Ribbon looks with images on the two buttons:
Setting up the Application Menu
The Applications menu is the Ribbon element that's analogous to the File menu in apps with a regular
menu. The Applications menu is accessed through the dropdown button to the left of the Main tab. Its contents
are listed in a <Ribbon.ApplicationMenu>
tag that is a child of the <Ribbon>
tag.
The Applications menu contains two sub-elements, a most-recently-used file list and a menu of commands. The
MRU list is set with a <ApplicationMenu.RecentItems>
child tag, and the menu's contents are
controlled by one or more <MenuGroup>
child tags.
Here's the XML that shows the contents of the Applications menu in our sample app:
<Application.Commands>
<!-- New commands: -->
<Command Name="cmdAbout" Symbol="RIDC_ABOUT" LabelTitle="&About" />
<Command Name="cmdExit" Symbol="RIDC_EXIT" LabelTitle="E&xit" />
<Command Name="cmdMRUList" LabelTitle="(MRU goes here)" />
</Application.Commands>
<Application.Views>
<Ribbon>
<Ribbon.ApplicationMenu>
<ApplicationMenu CommandName="cmdApplicationMenu">
<ApplicationMenu.RecentItems>
<RecentItems CommandName="cmdMRUList" />
</ApplicationMenu.RecentItems>
<MenuGroup>
<Button CommandName="cmdAbout" />
<Button CommandName="cmdExit" />
</MenuGroup>
</ApplicationMenu>
</Ribbon.ApplicationMenu>
</Ribbon>
</Application.Views>
And here's what the menu looks like:
There are a few things to note about the menu:
- The MRU list is just an empty placeholder, to show what it looks like.
- The menu contains two commands in one group. You can create separators in the menu by closing one
<MenuGroup>
tag and starting another.
- The mnemonic for a command that appears in the menu is set by putting an ampersand in the menu item text, as
with traditional menus. The ampersand must be escaped in XML and written as "&". If the command
is used elsewhere in the Ribbon, the ampersand is ignored and the
Keytip
attribute is used instead.
Saving Ribbon Settings
The last thing we'll add to the sample app is the ability to save Ribbon settings when the app closes. If you
change the contents of the QAT or minimize the Ribbon, those changes are lost when you close the app. To fix that,
we'll use two IUIRibbon
methods for loading and saving settings. First, add a string member to CMainFrame
that holds the data file path:
CString m_sSettingsFilePath;
Since this path won't change while the app is running, we can initialize it in the CMainFrame
constructor:
CMainFrame::CMainFrame()
{
TCHAR szTempDir[MAX_PATH] = {0};
GetTempPath ( _countof(szTempDir), szTempDir );
PathAddBackslash ( szTempDir );
m_sSettingsFilePath.Format ( _T("%sHelloRibbonSettings.dat"), szTempDir );
}
We then load the settings when the Ribbon is created, and save them when the Ribbon is destroyed. This is done
by adding a couple of lines to CMainFrame::OnViewChanged()
:
STDMETHODIMP CMainFrame::OnViewChanged(...)
{
switch ( nVerb )
{
case UI_VIEWVERB_CREATE:
m_pRibbon = pView;
LoadRibbonSettings();
break;
case UI_VIEWVERB_DESTROY:
m_pRibbon.Release();
SaveRibbonSettings();
break;
}
return S_OK;
}
LoadRibbonSettings()
calls IUIRibbon::LoadSettingsFromStream()
to load the settings.
Since that method takes an IStream
interface on the data, we call SHCreateStreamOnFileEx()
to get an IStream
interface that can be used to read the file.
void CMainFrame::LoadRibbonSetings()
{
HRESULT hr;
CComPtr<IStream> pStrm;
hr = SHCreateStreamOnFileEx ( m_sSettingsFilePath, STGM_READ,
0, FALSE, NULL, &pStrm );
if ( SUCCEEDED(hr) )
m_pRibbon->LoadSettingsFromStream ( pStrm );
}
If the file does not exist or can't be opened, we don't call LoadSettingsFromStream()
, and the
Ribbon will start out in the default state.
SaveRibbonSettings()
is similar. We get an IStream
interface that can be used to write
to the file and reset the file size to zero so that any existing contents are erased. Then we call IUIRibbon::SaveSettingsToStream()
to save the settings to the file.
void CMainFrame::SaveRibbonSetings()
{
HRESULT hr;
CComPtr<IStream> pStrm;
hr = SHCreateStreamOnFileEx ( m_sSettingsFilePath, STGM_WRITE|STGM_CREATE,
FILE_ATTRIBUTE_NORMAL, TRUE, NULL, &pStrm );
if ( SUCCEEDED(hr) )
{
LARGE_INTEGER liPos;
ULARGE_INTEGER uliSize;
liPos.QuadPart = 0;
uliSize.QuadPart = 0;
pStrm->Seek ( liPos, STREAM_SEEK_SET, NULL );
pStrm->SetSize ( uliSize );
m_pRibbon->SaveSettingsToStream ( pStrm );
}
}
Now, when you close the app, any changes you make to the Ribbon size and QAT will be preserved the next time
you run the app.
Conclusion
In this article, we've seen the basic steps necessary to include the Ribbon in an application. Next time, we'll
get an in-depth look at how commands are organized in groups, and how to customize a group's layout.
Revision History
February 22, 2011: Article first published