In this post, we continue our review of the Windows Ribbon Framework feature brought to us in Windows 7. For more information on the ribbon feature in general and the Windows Ribbon Framework in particular, make sure you read Part 1.
Today we will see how to create a ribbon enabled application in C++ using the Windows Ribbon Framework. To show it, we will build a small application with a ribbon that will contain two tabs, three groups, and several buttons.
As mentioned in the previous post, there are four steps we need to follow in order to create a ribbon enabled application. Let's dive into the details of these steps.
One last note before we start, this post requires knowledge in both Win32 and COM development. Also, if you wish to compile the sample application attached to this post, make sure you have Windows 7 SDK installed.
Step 1: Define the Ribbon UI
The first thing to do is to declare the ribbon user interface. This is done by creating an XML file that will contain the ribbon UI definition using a XAML-based syntax.
For our demo application, let's create a file named RibbonMarkup.xml (the XML suffix is not mandatory but is still recommended), with the following content:
='1.0'='utf-8'
<Application xmlns='http://schemas.microsoft.com/windows/2009/Ribbon'>
<Application.Commands>
<Command Name="cmdButtonNew"
Symbol="ID_CMD_NEW"
LabelTitle="New"
LabelDescription="New Description"
TooltipTitle="New (Ctrl+N)"
TooltipDescription="Create a new image.">
<Command.LargeImages>
<Image>Res/New32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonOpen"
Symbol="ID_CMD_OPEN"
LabelTitle="Open"
LabelDescription="Open Description"
TooltipTitle="Open (Ctrl+O)"
TooltipDescription="Open an existing image.">
<Command.LargeImages>
<Image>Res/Open32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonSave"
Symbol="ID_CMD_SAVE"
LabelTitle="Save"
LabelDescription="Save Description"
TooltipTitle="Save (Ctrl+S)"
TooltipDescription="Save the current image.">
<Command.LargeImages>
<Image>Res/Save32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonExit"
Symbol="ID_CMD_EXIT"
LabelTitle="Exit"
LabelDescription="Exit Description"
TooltipTitle="Exit (Ctrl+X)"
TooltipDescription="Exit application.">
<Command.LargeImages>
<Image>Res/Exit32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonMoreA"
Symbol="ID_CMD_MORE_A"
LabelTitle="More A"
LabelDescription="Sub button A">
<Command.LargeImages>
<Image>Res/MoreA32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonMoreB"
Symbol="ID_CMD_MORE_B"
LabelTitle="More B"
LabelDescription="Sub button B">
<Command.LargeImages>
<Image>Res/MoreB32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdButtonMoreC"
Symbol="ID_CMD_MORE_C"
LabelTitle="More C"
LabelDescription="Sub button C">
<Command.LargeImages>
<Image>Res/MoreC32.bmp</Image>
</Command.LargeImages>
</Command>
<Command Name="cmdTabMain"
Symbol="ID_CMD_TAB_MAIN"
LabelTitle="Main">
</Command>
<Command Name="cmdTabMore"
Symbol="ID_CMD_TAB_MORE"
LabelTitle="More">
</Command>
<Command Name="cmdGroupFileActions"
Symbol="ID_CMD_GROUP_FILE"
LabelTitle="File Actions">
</Command>
<Command Name="cmdGroupExit"
Symbol="ID_CMD_GROUP_EXIT"
LabelTitle="">
</Command>
<Command Name="cmdGroupMore"
Symbol="ID_CMD_MORE"
LabelTitle="">
</Command>
</Application.Commands>
<Application.Views>
<Ribbon>
<Ribbon.Tabs>
<Tab CommandName="cmdTabMain">
<Group CommandName="cmdGroupFileActions" SizeDefinition="ThreeButtons">
<Button CommandName="cmdButtonNew" />
<Button CommandName="cmdButtonOpen" />
<Button CommandName="cmdButtonSave" />
</Group>
<Group CommandName="cmdGroupExit" SizeDefinition="OneButton">
<Button CommandName="cmdButtonExit" />
</Group>
</Tab>
<Tab CommandName ="cmdTabMore">
<Group CommandName="cmdGroupMore" SizeDefinition="ThreeButtons">
<Button CommandName="cmdButtonMoreA" />
<Button CommandName="cmdButtonMoreB" />
<Button CommandName="cmdButtonMoreC" />
</Group>
</Tab>
</Ribbon.Tabs>
</Ribbon>
</Application.Views>
</Application>
OK, this may look intimidating at first glance, but it is rather easy to understand. The ribbon UI definition file has two major sections, Commands
and Views
.
For now, suffice to say that the Commands
section defines the different actions a user can invoke using the ribbon UI controls. Each such command is defined with its required resources (strings and images). Later, we will write command handlers in the application code.
The Views
section defines the UI controls we want to use and their layout in the ribbon. Each UI control is bound to a command and thus will trigger it when the UI control executes (e.g., button clicked).
More details on the Commands
and Views
sections will be reviewed on the next post.
A look on the Views
section reveals that in our demo application the ribbon has two tabs; the first tab has two groups and the second tab contains one group. Each group contains some buttons. In other words:
Figure 1: First tab
Figure 2: Second tab
If you try to follow the steps yourself, make sure the images you use are bitmaps (BMP format) that have 32 bits per pixel (32BPP). This is a mandatory need for all images used with the Windows Ribbon Framework.
Step 2: Compile the Ribbon UI
In this step, we will see how to use the Ribbon Markup Compiler provided with the Windows 7 SDK to compile the ribbon markup that we wrote in the previous step into a compact binary representation of the ribbon UI.
The compiler executable name is UICC.exe and it can be found at:
%ProgramFiles%\Microsoft SDKs\Windows\v7.0\Bin\UICC.exe
To manually compile the markup, open the Windows SDK "CMD Shell" and run:
UICC.exe RibbonMarkup.xml RibbonUI.bml /header:RibbonIDs.h /res:RibbonResource.rc
UICC does the following upon run:
- Validates the ribbon markup syntax.
- Generates a compressed binary representation of the markup along with an RC file that describes it. In our demo, these are the files RibbonUI.bml and RibbonResource.rc, respectively.
- Generates a C++ header file containing constants for the ribbon identifiers. This will be proved useful when we we'll implement the command handlers. In our demo, it is the file RibbonIDs.h.
Note that since our ribbon markup is rather light, the compiler will generate warnings about some missing markup elements. These warnings are not important at this point and we can safely ignore them.
Later, in step 4, we will setup the ribbon markup compilation as a custom build event in our project.
Step 3: Write Command Handlers Code
In this step, we will see how to write code that initializes the Windows Ribbon Framework, loads the previously compiled ribbon UI, and respond to events generated by the ribbon UI controls.
Before we dive into the code, let's get to know the main players participating in a ribbon enabled application, namely: UIRibbonFramework, IUIApplication and IUICommandHandler.
UIRibbonFramework Class
UIRibbonFramework
is a COM class provided by the Windows Ribbon Framework, which implements the IUIFramework
interface. This class is the entry point to the Windows Ribbon Framework. It provides functions for both initializing and destroying the ribbon framework within your application as well as other functions to control the ribbon behavior.
Its most important functions are:
Initialize(HWND topWindow, IUIApplication* application)
This function initializes the Windows Ribbon Framework within your application. It receives an IUIApplication
interface which we should implement to supply callbacks that the ribbon framework will invoke. More on this interface later.
LoadUI (HINSTANCE instance, LPCWSTR resourceName)
This function loads the ribbon UI resource. After this function gets called, the ribbon control should be visible.
Destroy()
This function releases all the object references held by the Windows Ribbon Framework.
IUIApplication Interface
As mentioned before, IUIApplication
is an interface that our application should implement. It provides three callbacks that the ribbon framework will invoke when needed.
OnCreateUICommand(UINT32 commandId, UI_COMMANDTYPE typeId, IUICommandHandler** commandHandler)
This function is called by the ribbon framework when a command, defined in the ribbon markup, should bind to a command handler. The IUICommandHandler
is yet another interface we should implement to handle commands execution. More on this interface later.
OnDestroyUICommand(UINT32 commandId, UI_COMMANDTYPE typeId, IUICommandHandler* commandHandler)
This function is called by the ribbon framework when the application window gets destroyed for each command defined in the ribbon markup.
OnViewChanged(UINT32 viewId, UI_VIEWTYPE typeId, IUnknown* view, UI_VIEWVERB verb, INT32 uReasonCode)
This function is called by the ribbon framework when the ribbon view has changed. For example, when the ribbon is resized, created, or destroyed.
IUICommandHandler Interface
The IUICommandHandler
interface should also be implemented by our application. It is used to execute our code in response to user actions on the ribbon UI controls. You can think of it as the place to write your "OnButtonClicked
" method.
This interface provides two callbacks that the ribbon framework will invoke when needed:
Execute(UINT32 commandId, UI_EXECUTIONVERB verb, const PROPERTYKEY* key, const PROPVARIANT* currentValue, IUISimplePropertySet* commandExecutionProperties)
This function is called by the ribbon framework when the user performs an action on a UI control which is bound to the command identified by the commandId
parameter. An example for such an action is clicking a button or selecting an item in a combobox.
UpdateProperty(UINT32 commandId, REFPROPERTYKEY key, const PROPVARIANT* currentValue, PROPVARIANT* newValue)
This function is called by the ribbon framework when it needs to know the value of a property of a UI control which is bound to the command identified by the commandId
parameter. This can be used to update properties that change dynamically, like the items list in a combobox control or the enabled state of a button.
Initializing the Ribbon
Now that we are familiar with the main players, we can better understand the flow of a ribbon enabled application.
To initialize the ribbon in your application, do the following:
- On application load, call
CoCreateInstance
with CLSID_UIRibbonFramework
. The UIRibbonFramework
class implements the IUIFramework
interface. - Call
IUIFramework::Initialize
and pass it a reference to your implementation of the IUIApplication
interface along with the HWND
of your application window. - Call
IUIFramework::LoadUI
which loads the pre-compiled resource and shows the real ribbon.
The following code shows how to initialize the ribbon framework; it should be called when the application loads. A good place would be when handling the message WM_CREATE
:
Note: error handling code was removed for clarity.
CoCreateInstance(CLSID_UIRibbonFramework,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&g_pFramework));
CApplication::CreateInstance(&g_pApplication);
g_pFramework->Initialize(hWnd, g_pApplication);
g_pFramework->LoadUI(GetModuleHandle(NULL), L"APPLICATION_RIBBON");
The class CApplication
implements IUIApplication
. The important part of it is to supply the implementation of IUICommandHandler
when asked by the ribbon framework. This is done by implementing IUIApplication::OnCreateUICommand
as follows:
STDMETHODIMP CApplication::OnCreateUICommand(
UINT nCmdID,
UI_COMMANDTYPE typeID,
IUICommandHandler** ppCommandHandler)
{
if (NULL == m_pCommandHandler)
{
CCommandHandler::CreateInstance(&m_pCommandHandler);
}
return m_pCommandHandler->QueryInterface(IID_PPV_ARGS(ppCommandHandler));
}
Note that our implementation of IUIApplication
returns the same CCommandHandler
object regardless of the command ID. Since the IUICommandHandler::Execute
function receives the executing command ID, we can avoid creating a separate command handler class for each command.
The following diagram (from MSDN) shows the interactions between the different components when initializing a ribbon enabled application:
Figure 3: Initializing a ribbon enabled application
Implementing Command Handlers
In order to respond to user actions on the ribbon, we must implement IUICommandHandler::Execute
:
STDMETHODIMP CCommandHandler::Execute(
UINT nCmdID,
UI_EXECUTIONVERB verb,
const PROPERTYKEY* key,
const PROPVARIANT* ppropvarValue,
IUISimplePropertySet* pCommandExecutionProperties)
{
HWND hwnd = GetForegroundWindow();
switch (nCmdID)
{
case ID_CMD_NEW:
MessageBox(hwnd, L"New button was clicked", L"New Clicked", 0);
break;
case ID_CMD_EXIT:
PostMessage(hwnd, WM_CLOSE, NULL, NULL);
break;
}
return S_OK;
}
In this example, we respond to the "New" button click event and present a message box. We respond to the "Exit" button click event by posting the WM_CLOSE
message.
You might be asking yourself who declared the ID_CMD_NEW
, ID_CMD_EXIT
constants (and if not, you should!). Well, if you recall, in step 2, one of the products of the ribbon markup compiler was a RibbonIDs.h file. This file contains exactly those constants. The name of the constant is what we have defined in the Symbol
attribute of the command element, in the ribbon markup. The value of the constant is auto-generated, but can be manually specified by using the Id
attribute in the ribbon markup.
Note that it is critical to use PostMessage
and not SendMessage
, otherwise the application will crash when closing using this exit command. This is a classic case of cutting the branch you sit on. The reason for the crash when using SendMessage
is that the handling of WM_CLOSE
invokes IUIFramework::Destroy()
which releases all the COM objects used by the ribbon framework, including CCommandHandler
. Since the CCommandHandler
is still in use (we called SendMessage
from CCommandHandler::Execute
), we get a memory corruption.
The following diagram (from MSDN) shows the interactions between the different components when the user interacts with the ribbon control:
Figure 4: Ribbon framework interaction with command handler
Step 4: Compile Entire Application
There is only one addition to the normal compilation process of our Win32 application. We need to add a custom build step that runs the ribbon markup compiler and we need to add the generated resource to the application.
To have your project automatically compile the ribbon markup XML file, do the following:
- First, add the RibbonMarkup.xml file to the project.
- Right click the added file in Solution Explorer and select Properties, Custom Build Step.
- Fill the fields according to the following values:
Command line:
"%ProgramFiles%\Microsoft SDKs\Windows\v7.0\Bin\UICC.exe" RibbonMarkup.xml
$(IntDir)\RibbonUI.bml /header:RibbonIDs.h /res:RibbonResource.rc
Note, on 64 bit Operating Systems, the "%ProgramFiles% environment variable should be replaced with %ProgramW6432%. So the command line should be:
"%ProgramW6432%\Microsoft SDKs\Windows\v7.0\Bin\UICC.exe" RibbonMarkup.xml
$(IntDir)\RibbonUI.bml /header:RibbonIDs.h /res:RibbonResource.rc
Description: Compiling Ribbon UI markup
Outputs: RibbonUI.bml;RibbonIds.h;RibbonResource.rc
Now that we've done these steps, we need to compile the application at least once so that the RibbonResource.rc file gets generated.
Now we add RibbonResource.rc to the project. This will cause the compiled binary representation of the ribbon UI to be embedded as a resource in the application executable.
Finally, don't forget to add the images you used in the ribbon markup to the project folder. Make sure you use the same relative location as specified in the markup file.
Now sit back and enjoy your first ribbon enabled application.
Figure 5: First ribbon enabled application
In this post, we have seen in detail the necessary steps to create an application that uses the Windows Ribbon Framework.
A working demo of the code presented in this post can be found here.
That's it for now,
Arik Poznanski.