Migrating to the Windows Ribbon Framework

An application that relies on traditional menus, toolbars, and dialogs can be migrated to the rich, dynamic, and context-driven UI of the Windows Ribbon framework Command system. This is an easy and effective way to modernize and revitalize the application while also improving the accessibility, usability, and discoverability of its functionality.

Introduction

In general, migrating an existing application to the Ribbon framework involves the following:

  • Designing a Ribbon layout and control organization that exposes the functionality of the existing application and is flexible enough to support new functionality.
  • Adapting the code of the existing application.
  • Migrating existing application resources (strings and images) to the Ribbon framework.

Note

The Ribbon User Experience Guidelines should be reviewed to determine if the application is a suitable candidate for a Ribbon UI.

 

Design the Ribbon Layout

Potential Ribbon UI designs and control layouts can be identified by performing these steps:

  1. Taking inventory of all existing functionality.
  2. Translating this functionality into Ribbon Commands.
  3. Organizing the Commands into logical groups.

Take Inventory

In the Ribbon framework, the functionality exposed by an application that manipulates the state or the view of a workspace or document is considered a command. What constitutes a command may vary and depends on the nature and domain of the existing application.

The following table lists a set of basic commands for a hypothetical text editing application:

Symbol ID Description
ID_FILE_NEW 0xE100 New document
ID_FILE_SAVE 0xE103 Save document
ID_FILE_SAVEAS 0xE104 Save As... (dialog)
ID_FILE_OPEN 0xE101 Open... (dialog)
ID_FILE_EXIT 0xE102 Exit the application
ID_EDIT_UNDO 0xE12B Undo
ID_EDIT_CUT 0xE123 Cut selected text
ID_EDIT_COPY 0xE122 Copy selected text
ID_EDIT_PASTE 0xE125 Paste text from clipboard
ID_EDIT_CLEAR 0xE120 Delete selected text
ID_VIEW_ZOOM 1242 Zoom... (dialog)

 

Look beyond existing menus and toolbars when building an inventory of commands. Consider all the ways a user can interact with the workspace. Even though not every command is suitable for inclusion in the Ribbon, this exercise may very well expose commands that were previously obscured by layers of UI.

Translate

Not every command needs to be represented in the Ribbon UI. For example, entering text, changing a selection, scrolling, or moving the insertion-point with the mouse all qualify as commands in the hypothetical text editor, however, these are not suitable to expose in a command bar as each involves a direct interaction with the UI of the application.

Conversely, some functionality may not be thought of as a command in the traditional sense. For example, instead of being buried in a dialog box, printer page-margin adjustments could be represented in the Ribbon as a group of Spinner controls in a contextual tab or application mode.

Note

Make note of the numeric ID that may be assigned to each command. Some UI frameworks, such as Microsoft Foundation Classes (MFC), define IDs for commands such as the file and edit menu (0xE100 to 0xE200).

 

Organize

Before attempting to organize the command inventory, the Ribbon User Experience Guidelines should be reviewed for best practices when implementing a Ribbon UI.

In general, the following rules can be applied to Ribbon Command organization:

  • Commands that operate on the file or the overall application most likely belong in the Application Menu.
  • Frequently used Commands such as Cut, Copy, and Paste (as in the text editor example) are typically placed on a default home tab. In more complex applications, they may be duplicated elsewhere in the Ribbon UI.
  • Important or frequently used Commands might warrant default inclusion in the Quick Access Toolbar.

The Ribbon framework also provides ContextMenu and MiniToolbar controls through the ContextPopup View. These features are not mandatory, but if your application has one or more existing context menus then migrating the commands they contain may allow the reuse of the existing codebase with automatic binding to existing resources.

Because every application is different, read the guidelines and try to apply them to the fullest extent possible. One of the goals of the Ribbon framework is to provide a familiar, consistent user experience and this goal will be more achievable if designs for new applications mirror existing Ribbon applications as much as possible.

Adapt Your Code

Once the Ribbon Commands have been identified and organized into logical groupings, the number of steps involved when incorporating the Ribbon framework into existing application code depends on the complexity of the original application. In general, there are three basic steps:

  • Create the Ribbon markup based on the Command organization and layout specification.
  • Replace legacy menu and toolbar functionality with Ribbon functionality.
  • Implement an IUICommandHandler adapter.

Create The Markup

The list of commands as well as their organization and layout are declared through the Ribbon markup file which is consumed by the Ribbon markup compiler.

Note

Many of the steps required to adapt an existing application are similar to those required to start a new Ribbon application. For more information, see the Creating a Ribbon Application tutorial for a new Ribbon application walkthrough.

 

There are two primary sections to the Ribbon markup. The first section is a manifest of Commands and their associated resources (strings and images). The second section specifies the structure and placement of controls on the Ribbon.

The markup for the simple text editor might look something like the following example:

Note

Image and string resources are covered later in this article.

 

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://schemas.microsoft.com/windows/2009/Ribbon">

  <Application.Commands>
    <Command Name="cmdNew" Id="0xE100" Symbol="ID_CMD_NEW" LabelTitle="New document" />
    <Command Name="cmdSave" Id="0xE103" Symbol="ID_CMD_SAVE" LabelTitle="Save" />
    <Command Name="cmdSaveAs" Id="0xE104" Symbol="ID_CMD_SAVEAS" LabelTitle="Save as" />
    <Command Name="cmdOpen" Id="0xE101" Symbol="ID_CMD_OPEN" LabelTitle="Open" />
    <Command Name="cmdExit" Id="0xE102" Symbol="ID_CMD_EXIT" LabelTitle="Exit" />
    <Command Name="cmdUndo" Id="0xE12B" Symbol="ID_CMD_UNDO" LabelTitle="Undo" />
    <Command Name="cmdCut" Id="0xE123" Symbol="ID_CMD_CUT" LabelTitle="Cut" />
    <Command Name="cmdCopy" Id="0xE122" Symbol="ID_CMD_COPY" LabelTitle="Copy" />
    <Command Name="cmdPaste" Id="0xE125" Symbol="ID_CMD_PASTE" LabelTitle="Paste" />
    <Command Name="cmdDelete" Id="0xE120" Symbol="ID_CMD_DELETE" LabelTitle="Delete" />
    <Command Name="cmdZoom" Id="1242" Symbol="ID_CMD_ZOOM" LabelTitle="Zoom" />
  </Application.Commands>

  <Application.Views>
    <Ribbon>
      <Ribbon.ApplicationMenu>
        <ApplicationMenu>
          <MenuGroup>
            <Button CommandName="cmdNew" />
            <Button CommandName="cmdOpen" />
            <Button CommandName="cmdSave" />
            <Button CommandName="cmdSaveAs" />
          </MenuGroup>
          <MenuGroup>
            <Button CommandName="cmdExit" />
          </MenuGroup>
        </ApplicationMenu>
      </Ribbon.ApplicationMenu>
      <Ribbon.QuickAccessToolbar>
        <QuickAccessToolbar>
          <QuickAccessToolbar.ApplicationDefaults>
            <Button CommandName="cmdSave" />
            <Button CommandName="cmdUndo" />
          </QuickAccessToolbar.ApplicationDefaults>
        </QuickAccessToolbar>
      </Ribbon.QuickAccessToolbar>
      <Ribbon.Tabs>
        <Tab>
          <Group CommandName="grpClipboard" SizeDefinition="FourButtons">
            <Button CommandName="cmdPaste" />
            <Button CommandName="cmdCut" />
            <Button CommandName="cmdCopy" />
            <Button CommandName="cmdDelete" />
          </Group>
        </Tab>
        <Tab>
          <Group CommandName="grpView" SizeDefinition="OneButton">
            <Button CommandName="cmdZoom" />
          </Group>
        </Tab>
      </Ribbon.Tabs>
    </Ribbon>
  </Application.Views>

</Application>

To avoid redefining symbols that are defined by a UI framework such as MFC, the previous example uses slightly different symbol names for each Command (ID_FILE_NEW versus ID_CMD_NEW). However, the numeric ID assigned to each Command must remain the same.

To integrate the markup file into an application, configure a custom build step to run the Ribbon markup compiler, UICC.exe. The resulting header and resource files are then incorporated into the existing project. If the example text editor Ribbon application is named "RibbonPad", a custom-build command line similar to the following is required:

UICC.exe res\RibbonPad_ribbon.xml res\RibbonPad_ribbon.bin 
         /header:res\RibbonPad_ribbon.h /res:res\RibbonPad_ribbon.rc2

The markup compiler creates a binary file, a header (H) file and a resource (RC) file. Because the existing application likely has an existing RC file, include the generated H and RC files in that RC file, as shown in the following example:

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

#define _AFX_NO_SPLITTER_RESOURCES
#define _AFX_NO_OLE_RESOURCES
#define _AFX_NO_TRACKER_RESOURCES
#define _AFX_NO_PROPERTY_RESOURCES

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE 9, 1
#pragma code_page(1252)

#include "res\RibbonPad_ribbon.h"  // Ribbon resources
#include "res\RibbonPad_ribbon.rc2"  // Ribbon resources

#include "res\RibbonPad.rc2"  // non-Microsoft Visual C++ edited resources
#include "afxres.rc"    // Standard components
#include "afxprint.rc"  // printing/print preview resources
#endif
#endif    // not APSTUDIO_INVOKED

Replace Legacy Menus and Toolbars

Replacing standard menus and toolbars with a ribbon in a legacy application requires the following:

  1. Remove toolbar and menu resource references from the application's resource file.
  2. Delete all toolbar and menu bar initialization code.
  3. Delete any code used to attach a toolbar or menu bar to the top-level window of the application.
  4. Instantiate the Ribbon framework.
  5. Attach the ribbon to the top-level window of the application.
  6. Load the compiled markup.

Important

Existing status bar and keyboard shortcut tables should be preserved as the Ribbon framework does not replace these features.

 

The following example demonstrates how to initialize the framework using IUIFramework::Initialize:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    if (!m_RibbonBar.Create(this, WS_CHILD|WS_DISABLED|WS_VISIBLE|CBRS_TOP|CBRS_HIDE_INPLACE,0))
        return -1;      // fail to create

    if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)))
        return -1;      // fail to create

    // Ribbon initialization
    InitRibbon(this, &m_spUIFramework);

    return 0;
}

The following example demonstrates how to use IUIFramework::LoadUI to load the compiled markup:

HRESULT InitRibbon(CMainFrame* pMainFrame, IUnknown** ppFramework)
{
    // Create the IUIFramework instance.
    CComPtr<IUIFramework> spFramework;
    HRESULT hr = ::CoCreateInstance(CLSID_UIRibbonFramework, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spFramework));
    if (FAILED(hr))
        return hr;
    
    // Instantiate the CApplication object.
    CComObject<CApplication>* pApplication;
    hr = CComObject<CApplication>::CreateInstance(&pApplication);   // Refcount is 0
    
    // Call AddRef on the CApplication object. The smart pointer will release the refcount when it is out of scope.
    CComPtr< CComObject<CApplication> > spApplication(pApplication);

    if (FAILED(hr))
        return hr;

    // Initialize and load the Ribbon.
    spApplication->Initialize(pMainFrame);

    hr = spFramework->Initialize(*pMainFrame, spApplication);
    if (FAILED(hr))
        return hr;

    hr = spFramework->LoadUI(GetModuleHandle(NULL), L"APPLICATION_RIBBON");
    if (FAILED(hr))
        return hr;

    // Return IUIFramework interface to the caller.
    hr = spFramework->QueryInterface(ppFramework);

    return hr;
}

The CApplication class, referred to above, must implement a pair of Component Object Model (COM) interfaces defined by the Ribbon framework: IUIApplication and IUICommandHandler.

IUIApplication provides the main callback interface between the framework and the application (for example, the height of the ribbon is communicated through IUIApplication::OnViewChanged) while the callbacks for individual Commands are provided in response to IUIApplication::OnCreateUICommand.

Tip: Some application frameworks, such as MFC, require that the height of the ribbon bar be taken into account when rendering the document space of the application. In these cases, the addition of a hidden window to overlay the ribbon bar and force the document space to the desired height is necessary. For an example of this approach, where a layout function is called based on the ribbon height returned by the IUIRibbon::GetHeight method, see the HTMLEditRibbon Sample.

The following code example demonstrates an IUIApplication::OnViewChanged implementation:

// This is the Ribbon implementation that is done by an application.
// Applications have to implement IUIApplication and IUICommandHandler to set up communication with the Windows Ribbon.
class CApplication
    : public CComObjectRootEx<CComSingleThreadModel>
    , public IUIApplication
    , public IUICommandHandler
{
public:

    BEGIN_COM_MAP(CApplication)
        COM_INTERFACE_ENTRY(IUIApplication)
        COM_INTERFACE_ENTRY(IUICommandHandler)
    END_COM_MAP()

    CApplication() : _pMainFrame(NULL)
    {
    }

    void Initialize(CMainFrame* pFrame)
    {
        // Hold a reference to the main frame.
        _pMainFrame = pFrame;
    }

    void FinalRelease()
    {
        // Release the reference.
        _pMainFrame = NULL;
        __super::FinalRelease();
    }

    STDMETHOD(OnViewChanged)(UINT32 nViewID, __in UI_VIEWTYPE typeID, __in IUnknown* pView, UI_VIEWVERB verb, INT32 uReasonCode)
    {
        HRESULT hr;

        // The Ribbon size has changed.
        if (verb == UI_VIEWVERB_SIZE)
        {
            CComQIPtr<IUIRibbon> pRibbon = pView;
            if (!pRibbon)
                return E_FAIL;

            UINT ulRibbonHeight;
            // Get the Ribbon height.
            hr = pRibbon->GetHeight(&ulRibbonHeight);
            if (FAILED(hr))
                return hr;

            // Update the Ribbon bar so that the main frame can recalculate the child layout.
            _pMainFrame->m_RibbonBar.SetRibbonHeight(ulRibbonHeight);
            _pMainFrame->RecalcLayout();
        }

        return S_OK;
    }

    STDMETHOD(OnCreateUICommand)(UINT32 nCmdID, 
                               __in UI_COMMANDTYPE typeID,
                               __deref_out IUICommandHandler** ppCommandHandler)
    {
        // This application uses one command handler for all ribbon commands.
        return QueryInterface(IID_PPV_ARGS(ppCommandHandler));
    }

    STDMETHOD(OnDestroyUICommand)(UINT32 commandId, __in UI_COMMANDTYPE typeID,  __in_opt  IUICommandHandler *commandHandler)
    {        
        return E_NOTIMPL;
    }

private:
    CMainFrame* _pMainFrame;
};

Implement an IUICommandHandler Adapter

Depending on the design of the original application, it may be easier to have multiple Command handler implementations, or a single bridging Command handler that invokes the existing-application command logic. Many applications use WM_COMMAND messages for this purpose where it is sufficient to provide a single, all-purpose command handler that simply forwards WM_COMMAND messages to the top-level window.

However, this approach requires special handling for Commands such as Exit or Close. Because the Ribbon cannot be destroyed while it's processing a window message, the WM_CLOSE message should be posted to the UI thread of the application and should not be processed synchronously, as shown in the following example:

// User action callback, with transient execution parameters.
    STDMETHODIMP Execute(UINT nCmdID,
        UI_EXECUTIONVERB verb, 
        __in_opt const PROPERTYKEY* key,
        __in_opt const PROPVARIANT* ppropvarValue,
        __in_opt IUISimplePropertySet* pCommandExecutionProperties)
    {       
        switch(nCmdID)
        {
        case cmdExit:
            ::PostMessage(*_pMainFrame, WM_CLOSE, nCmdID, 0);
            break;
        default:
            ::SendMessage(*_pMainFrame, WM_COMMAND, nCmdID, 0);
        }
        return S_OK;
    }

    STDMETHODIMP UpdateProperty(UINT32 nCmdID, 
                                __in REFPROPERTYKEY key,
                                __in_opt  const PROPVARIANT *currentValue,
                                __out PROPVARIANT *newValue) 
    {        
        return S_OK;
    }

Migrating Resources

When the manifest of commands has been defined, the structure of the Ribbon has been declared, and the application code adapted to host the Ribbon framework, the final step is the specification of string and image resources for each Command.

Note

String and image resources are typically provided in the markup file. However, they can be generated or replaced programmatically by implementing the IUICommandHandler::UpdateProperty callback method.

 

String Resources

Command.LabelTitle is the most common string property defined for a Command. These are rendered as the text labels for tabs, groups, and individual controls. A label string from a legacy menu item can typically be re-used for a Command.LabelTitle without much editing.

However, the following conventions have changed with the advent of the Ribbon:

  • The ellipsis (...) suffix, used to indicate a dialog-launching command, is no longer necessary.
  • The ampersand (&) can still be used to indicate a keyboard-shortcut for a Command that appears in a menu, but the Command.Keytip property supported by the framework fulfills a similar purpose.

Referring back to the text editor example, the following strings for LabelTitle and Keytip could be specified:

Symbol Original string LabelTitle string Keytip string
ID_FILE_NEW &New &New N
ID_FILE_SAVE &Save &Save S
ID_FILE_SAVEAS Save &As… Save &as A
ID_FILE_OPEN &Open… &Open O
ID_FILE_EXIT E&xit E&xit X
ID_EDIT_UNDO &Undo Undo Z
ID_EDIT_CUT Cu&t Cu&t X
ID_EDIT_COPY &Copy &Copy C
ID_EDIT_PASTE &Paste &Paste V
ID_EDIT_CLEAR &Delete &Delete D
ID_VIEW_ZOOM &Zoom… Zoom Z

 

The following is a list of other string properties which should be set on most Commands:

Tabs, Groups, and other Ribbon UI features can now be declared with all string and image resources specified.

The following Ribbon markup example demonstrates various string resources:

<Application.Commands>
    <!-- Tabs -->
    <Command Name="tabHome" LabelTitle="Home" Keytip="H" />
    <Command Name="tabView" LabelTitle="View" Keytip="V" />

    <!-- Groups -->
    <Command Name="grpClipboard" LabelTitle="Clipboard" />
    <Command Name="grpZoom" LabelTitle="Zoom" />

    <!-- App menu commands -->
    <Command Name="cmdNew" Id="0xE100" Symbol="ID_CMD_NEW" LabelTitle="New document" Keytip="N" >
      <Command.TooltipTitle>New (Ctrl+N)</Command.TooltipTitle>
      <Command.TooltipDescription>Create a new document.</Command.TooltipDescription>
    </Command>
    <Command Name="cmdSave" Id="0xE103" Symbol="ID_CMD_SAVE" LabelTitle="Save" Keytip="S">
      <Command.TooltipTitle>Save (Ctrl+S)</Command.TooltipTitle>
      <Command.TooltipDescription>Save the current document.</Command.TooltipDescription>
    </Command>
    <Command Name="cmdSaveAs" Id="0xE104" Symbol="ID_CMD_SAVEAS" LabelTitle="Save as" Keytip="A">
      <Command.TooltipDescription>Save the current document with a new name.</Command.TooltipDescription>
    </Command>
    <Command Name="cmdOpen" Id="0xE101" Symbol="ID_CMD_OPEN" LabelTitle="Open" Keytip="O">
      <Command.TooltipTitle>Open (Ctrl+O)</Command.TooltipTitle>
      <Command.TooltipDescription>Open a document.</Command.TooltipDescription>
    </Command>
    <Command Name="cmdExit" Id="0xE102" Symbol="ID_CMD_EXIT" LabelTitle="Exit" Keytip="X">
      <Command.TooltipDescription>Exit the application.</Command.TooltipDescription>
    </Command>

    <!-- ...other commands -->

  </Application.Commands>

Image Resources

The Ribbon framework supports image formats that provide a much richer look and feel than the image formats supported by previous menu and toolbar components.

For Windows 8 and later, the Ribbon framework supports the following graphics formats: 32-bit ARGB bitmap (BMP) files and Portable Network Graphics (PNG) files with transparency.

For Windows 7 and earlier, image resources must conform to the standard BMP graphics format used in Windows.

Note

Existing image files can be converted to either format. However, the results may be less than satisfactory if the image files do not support antialiasing and transparency.

 

It is not possible to specify a single default size for image resources in the Ribbon framework. However, to support adaptive layout of controls, images can be specified in two sizes (large and small) . All images in the Ribbon framework are scaled according to the dots per inch (dpi) resolution of the display with the exact rendered size dependent on this dpi setting. See Specifying Ribbon Image Resources for more information.

The following example demonstrates how a set of dpi-specific images are referenced in markup:

<Command Name="cmdNew" Id="0xE100" Symbol="ID_CMD_NEW" LabelTitle="New document" Keytip="N" >
      <Command.TooltipTitle>New (Ctrl+N)</Command.TooltipTitle>
      <Command.TooltipDescription>Create a new document.</Command.TooltipDescription>
      <Command.LargeImages>
        <Image Source="cmdNew-32px.png" MinDPI="96" />
        <Image Source="cmdNew-40px.png" MinDPI="120" />
        <Image Source="cmdNew-48px.png" MinDPI="144" />
        <Image Source="cmdNew-64px.png" MinDPI="192" />
      </Command.LargeImages>
      <Command.SmallImages>
        <Image Source="cmdNew-16px.png" MinDPI="96" />
        <Image Source="cmdNew-20px.png" MinDPI="120" />
        <Image Source="cmdNew-24px.png" MinDPI="144" />
        <Image Source="cmdNew-32px.png" MinDPI="192" />
      </Command.SmallImages>
    </Command>

Specifying Ribbon Image Resources