Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows 7 Ribbon: The Time Has Come, Your Win32 Application Will Change

0.00/5 (No votes)
21 Nov 2009 1  
Learn how to use the new Ribbon under Windows 7
3.jpg

Introduction

After Office 2007 introduced the fluent Ribbon, a lot of commercial expensive solutions were available. But finally, in Windows 7 Microsoft released the native Ribbon control. The article here explains my first try to use the ribbon in two of my projects, my light TurboIRC and my less light Turbo Play's Score Editor, and will help you use the ribbon in your application in no time at all. A sample project ready-to-run is also included.

Features

  • Wraps the ribbon ActiveX in a simple C++ interface
  • Works in x86/x64

You Need

Draw It Visually

If you do not want to mess with XML, or if you want a quick visual design tool you can try out my Visual Ribbon Creator which creates the ribbon for you.

Overview

The Ribbon is an ActiveX object that is attached to a window. It takes the entire width of the window, and its height is configurable. It sizes itself automatically when needed, so you need not handle stuff in WM_SIZE. By default, the ribbon removes the menu, but you can insert it again later if you need it.

4.jpg

5.jpg

The ribbon gets its display data from an XML. This means that you only use a few function calls, some to initialize and load the data, and an event handler to get notified when something happens to the ribbon.

Because the ribbon is an ActiveX interface, an application that uses it will simply fail to initialize it under XP, but it will continue to run.

When you create the XML (see how below), you pass it to uicc.exe which creates the .BIN, the .H, and the .RC. Just include the .RC file inside your RC project, and the necessary stuff (the binary and the stuff defined in the ribbon RC and H) will be included.

Initialization

  • You create an IUIFramework
  • IUIFramework* u_f = 0;
    HRESULT hr = CoCreateInstance(CLSID_UIRibbonFramework,
                 0,CLSCTX_ALL,__uuidof(IUIFramework),(void**)&u_f);
  • You implement an IUIApplication
  • This interface has (in addition to the IUnknown members), the following three functions:

    • To be called from the ribbon when a command is created:
    • virtual HRESULT __stdcall OnCreateUICommand(UINT32 commandId,
              UI_COMMANDTYPE typeID,IUICommandHandler **commandHandler);
    • To be called from the ribbon when a command is destroyed:
    • virtual HRESULT __stdcall OnDestroyUICommand(UINT32 commandId,
              UI_COMMANDTYPE typeID,IUICommandHandler *commandHandler);
    • To be called when the view of the ribbon has changed:
    • virtual HRESULT __stdcall OnViewChanged(UINT32 viewId,UI_VIEWTYPE typeID,
              IUnknown *view,UI_VIEWVERB verb,INT32 uReasonCode);
  • You call IUIFramework::Initialize() with the parent window of the ribbon, and your implementation of IUIApplication.
  • You call IUIFramework::LoadUI() with the HINSTANCE and the resource name.

The ribbon is now displayed!

Command Handlers

  • OnCreateUICommand is called for each "command". See what a command below is in XML format. You must return an implementation of an IUICommandHandler (AddRef()+!) that will handle the command with two member functions:
    • This is called when the value of the command is changed - still under pending documentation:
    • HRESULT __stdcall UpdateProperty(UINT32 commandId,REFPROPERTYKEY key,
              const PROPVARIANT *currentValue,PROPVARIANT *newValue);
    • This is called when the command is executed. The "verb" is one of the UI_EXECUTIONVERB enumeration, and it notifies you what sort of an event did happen, for example, a font selection drop down, a button click, etc.
    • HRESULT __stdcall Execute(UINT32 commandId,UI_EXECUTIONVERB verb,
              const PROPERTYKEY *key, const PROPVARIANT *currentValue,
              IUISimplePropertySet *commandExecutionProperties)
  • OnDestroyUICommand is called when the interface is not needed anymore, but do not call Release(). The ribbon will call it. It is just a notification for you that the object is about to be destroyed, not a request to destroy it yourself.
  • OnViewChanged allows you to request an interface from the IUIFramework and query its state. If typeId == UI_VIEWTYPE_RIBBON and verb == UI_VIEWVERB_CREATE or UI_VIEWVERB_SIZE for example, this tells you that the ribbon is created or resized (warning, the ribbon is *not* created by the time IUIFramework:: LoadUI() returns), so you can query the IUnknown pointer for an IUIRibbon and get its height.

Using My Library

To avoid all the above stuff, my library wraps these interfaces. A class RIBBON is there, and you use it as follows:

  • RIBBON(HWND hh = 0);
  • ~RIBBON();
  • bool Initialize();
  • Returns true on successful ribbon initialization.

  • bool LoadMarkup(HINSTANCE hInst,LPCWSTR resourceName);
  • Loads the ribbon from the resource. If an existing ribbon is loaded, it destroys it.

  • void DestroyMarkup();
  • Destroys the loaded ribbon.

My library sends your parent window a predefined MESSAGE_RIBBON (const int MESSAGE_RIBBON = RegisterWindowMessage(L"{E733E4DA-904C-486b-B5FB-6201773D69DE}");), with the WPARAM set to the RIBBON* class, and the LPARAM set to a RIBBON_MESSAGE structure:

struct RIBBON_MESSAGE
{
 IUIFramework * u_f;	     // Pointer to the IUIFramework of the ribbon
 UINT32 cmd;                    // Command ID
 UINT32 reason;                 // Reason code (When View is changed)
 UI_COMMANDTYPE type;           // Type of the command
 UI_VIEWTYPE vtype;             // Verb Type of the view change (When View is changed)
 UI_VIEWVERB vverb;             // Verb of the view change (When View is changed)
 UI_EXECUTIONVERB verb;         // Verb of the command
 const PROPERTYKEY* key;        // Contains the new value
 const PROPVARIANT* cv;         // Contains the current value
 IUISimplePropertySet* pset;    // Contains an interface which you can set/query values
 void* view;                    // Contains an IUnknown* of the view interface
                                // (when view is changed) which you
                                // can use to query for an IUIRibbon.
 bool update;                   // true if view is changed.
 };

So, if you simply want to redirect the message to WM_COMMAND, you check for update == false, verb == UI_EXECUTIONVERB_EXECUTE, and type == UI_COMMANDTYPE_ACTION.

LRESULT CALLBACK Main_DP(HWND hh,UINT mm,WPARAM ww,LPARAM ll)
 {
 if (mm == MESSAGE_RIBBON)
         {
        RIBBON_MESSAGE* rm = (RIBBON_MESSAGE*)ll;
        if (!rm)
        if (rm->update == false && rm->verb == UI_EXECUTIONVERB_EXECUTE
                                && rm->type == UI_COMMANDTYPE_ACTION)
            SendMessage(hh,WM_COMMAND,rm->cmd,0);
        }
...
}

Understanding the XML Format of the Ribbon Data

Because a ribbon is basically a representation of an XML map, here is where you will actually spend your most time to develop a nice and working ribbon.

A ribbon basically has these elements:

  • A set of commands, each of them can have an ID, a symbol, a label, a tooltip, and a set of images. You define these commands for both "push buttons" and other elements such as a tab or a group.
  • A set of elements, including an "application menu", a "quick access toolbar", and the "ribbon" which contains a number of tabs. Each tab can have some groups, and each group can have some predefined controls. These groups must be marked with specific "sizes" and layouts; so for example, if you want to have, say, six buttons inside a group, you have only three predefined ways to arrange them.

Here is a set of sample commands:

<Command Name="cmdNew" LabelTitle="New..." Symbol="cmdNew"
  Comment="New" Id="22001" TooltipTitle="Tooltip Title"
  TooltipDescription="Tooltip Text">
     <Command.SmallImages>
        <Image>1-32a.bmp</Image>
      </Command.SmallImages>
      <Command.LargeImages>
        <Image>1-32a.bmp</Image>
      </Command.LargeImages>
    </Command>
<Command Name="cmdOpen" LabelTitle="Open..."
   Symbol="cmdOpen" Comment="Open" Id="22002" />
<Command Name="cmdSave" LabelTitle="Save..."
   Symbol="cmdSave" Comment="Save" Id="22003" />
<Command Name="Tab1" LabelTitle="First Tab" Symbol="_44" Id="30001"/>
<Command Name="Tab2" LabelTitle="Second Tab" Id="30002"/>
<Command Name="cx1" LabelTitle="Check Box 1" />
<Command Name="Font1" LabelTitle="Font Selection1" />
<Command Name="cpick1" LabelTitle="Choose Color" />
<Command Name="cmdn1" LabelTitle="Main Menu" />
<Command Name="g1" LabelTitle="Group 1" />

Note that the "button" commands would need images (small 16x16, or large 32x32, or 64x64 depending on the DPI, but I've found out that the ribbon resizes big images pretty well), but other "commands" like labels for tabs or groups only need name and title, because there is no command ID associated with them.

Here is how to create an "application menu":

<Ribbon.ApplicationMenu>
    <ApplicationMenu CommandName="cmdn1">
      <MenuGroup Class="MajorItems">
        <Button CommandName="cmdNew" />
        <Button CommandName="cmdOpen" />
        <Button CommandName="cmdSave" />
      </MenuGroup>
    </ApplicationMenu>
</Ribbon.ApplicationMenu>

And now, you have an application menu with three buttons: New, Open, Save.

Here is how to create a quick access toolbar:

<Ribbon.QuickAccessToolbar>
     <QuickAccessToolbar CustomizeCommandName="cmdCustomize">
         <QuickAccessToolbar.ApplicationDefaults>
             <Button CommandName="cmdNew" />
         </QuickAccessToolbar.ApplicationDefaults>
     </QuickAccessToolbar>
</Ribbon.QuickAccessToolbar>

Now, you have a quick access toolbar with the cmdNew inside, plus a button defined by "cmdCustomize" that should be able to customize the quick access toolbar. Note that the ribbon allows the user to change the buttons that appear in the quick access toolbar.

And, here is how to create a tab with some groups:

<Ribbon.Tabs>
            ....
    <Tab CommandName="Tab1" >
      <Tab.ScalingPolicy>
        <ScalingPolicy>
          <ScalingPolicy.IdealSizes>
            <Scale Group="g1" Size="Large" />
            <Scale Group="g2" Size="Large" />
            <Scale Group="g3" Size="Large" />
            <Scale Group="g4" Size="Large" />
          </ScalingPolicy.IdealSizes>
        </ScalingPolicy>
      </Tab.ScalingPolicy>
      <Group CommandName="g1" SizeDefinition="OneButton">
        <Button CommandName="cmdNew" />
      </Group>
      <Group CommandName="g2" SizeDefinition="ThreeButtons">
        <Button CommandName="cmdNew" />
        <Button CommandName="cmdOpen" />
        <Button CommandName="cmdSave" />
        <DialogLauncher CommandName="cmdSave" />
      </Group>
      <Group CommandName="g3" SizeDefinition="OneFontControl">
        <FontControl CommandName = "Font1"  FontType = "RichFont" />
      </Group>
      <Group CommandName="g4">
        <DropDownColorPicker CommandName="cpick1" ChipSize = "Large" />
      </Group>
    </Tab>

Be careful now. The tab has a reference to the command "Tab1" which contains the name of the tab (very weird that they don't simply allow <Tab Label="x" />, perhaps they might want to associate images with the tab later). This tab has four groups, and there is a scaling policy for each of them. The scaling policy of the group is not arbitrary set to "Large" ,"Medium", or "Small", but it depends on the number of controls and the layout templates (described in MSDN here). That means that if your group is "OneButton", it must be set to the "Large" scaling size.

There is a predefined number of templates, but you can also use the <SizeDefinition> for custom templates. You can view a sample in my "g5" group which uses a custom template.

After the scaling policy, here are the group definitions along with the layout templates described in MSDN. Now, each group can have many things, including buttons, spin buttons, drop down, font control, color picker, dialog box starters, separators, and all the stuff described in the Markup Elements page. My four groups above have some push buttons, a font chooser, and a color picker.

How do we get the values from the color picker? Check for type == UI_COMMANDTYPE_COLORANCHOR, and the PROPVARIANT "cv" value contains an integer that represents the RGB.

How do we get the value from the font chooser? The type is UI_COMMANDTYPE_FONT and the PROPVARIANT "cv" value contains an IUnknown, but I haven't found yet how to get the font from it!

Application Modes

Depending on your application's context, you might need some tabs and/or groups to be visible or invisible. Instead of doing this explicitly for each group/tab, the ribbon provides the "Application modes", which is a 32-bit bitset of the modes that they should be "active". Application modes apply to groups and tabs.

For example, here is the definition of the "Tab1":

 <Tab CommandName="Tab1" ApplicationModes="0,2"> 

What this means is that when the bit 0 or the bit 2 in the current selected mode is set, then the tab is displayed. So when I call Ribbon::SetModes(0), this tab will be hidden. When I use 2 or 8 or any integer that has either the bit 0 or the bit 2 set, the tab will be displayed.

The same can be applied to groups easily.

 <Group CommandName="g1" SizeDefinition="OneButton" ApplicationModes="3">

Contextual Tabs

These tabs appear only when your application needs them. Use them for context-specific applications - for example Turbo Play only displays the score editor when a MIDI track is loaded.

To specify the contextual tab, add the <Tab> element after a special header in the ribbon:

 <Ribbon.ContextualTabs>
  <TabGroup CommandName='CoTab1'>
    <Tab CommandName="Tab4">

As you see, there has to be a tabgroup that contains all contextual tabs to be visible at the same time.

To show the contextual tab, you have to invalidate the ribbon contextual tab group:

IUIFramework::InvalidateUICommand(
                  51999, 
                  UI_INVALIDATIONS_PROPERTY, 
                  &UI_PKEY_ContextAvailable);

The ribbon then sends a notification to reconfigure which contextual tabs are to be shown:

 if (rm->key && *rm->key == UI_PKEY_ContextAvailable)
       {
        if (rm->cmd == 59009)
            {
            unsigned int gPR = (unsigned int)UI_CONTEXTAVAILABILITY_NOTAVAILABLE;
            if (ShouldShowCtTab(rm->cmd))
                  gPR = (unsigned int)UI_CONTEXTAVAILABILITY_ACTIVE;
            UIInitPropertyFromUInt32(*rm->key, gPR, rm->nv);
            }
       ...
       }

Property Keys

In order to get/set the state of any ribbon control, you can use its property keys documented in MSDN. You can either use the IUIFramework :: GetUICommandProperty to get a property key, or you can query the "cv" IUnknown member of the passed RIBBON_MESSAGE structure for an IPropertyStore; Use GetValue() / SetValue() / Commit to read/write properties of the referenced control. There are also a number of "global" keys, for which you query the IUIFramework directly for an IPropertyStore.

For example, when my WndProc gets notified that a color is selected, it applies it to a background color for the ribbon as follows:

PROPVARIANT val;
HRESULT hr = rm->u_f->GetUICommandProperty
	(rm->cmd,UI_PKEY_Color,&val); // Get the property of the control we pushed
IPropertyStore* st = 0;
rm->u_f->QueryInterface(__uuidof(IPropertyStore),(void**)&st);
if (st && SUCCEEDED(hr))
	{
	st->SetValue(UI_PKEY_GlobalBackgroundColor,val);
	st->Commit();
  }

In the case of a font control, you can simply query the IUnknown* pointer for an IPropertyStore and use the UI_PKEY_FontProperties_XXXX keys.

Current Limitations of the Ribbon Implementation

  • Ribbon bitmaps can only be 32-bit BMP files. No JPG/PNG/other BMP support yet.
  • The ribbon does not allow you to initialize, resize, rearrange, delete, or otherwise interact with its members apart from the XML configuration - for example, you cannot yet specify the startup tab or change the tabs programmatically. Also, no notifications are provided for non-commands, such as tab selection changing.
  • The ribbon bitmaps must be included within the same module that contains the .rc.
  • No multilingual support yet (you must define multiple language ribbons separately).
  • The ribbon does not allow hosting of any control except its predefined ones, so you can't add your own HWND to the ribbon.

When these issues are considered by Microsoft, I will modify the article to include new functions!

Acknowledgements

Special thanks to these Microsoft staff that guided me to understand the ribbon internals:

  • Ryan Demopoulos
  • Nicolas Brun

History

  • 20-11-2009: Win 7 Final update, Added contextual Tabs and added Visual Ribbon Creator reference tool
  • 07-5-2009: Article update with RC1 updates
  • 05-2-2009: Added property keys, application modes and font detection
  • 23-1-2009: First release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here