Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

Relook your Old and New Native Applications with a Ribbon UI under Vista or Windows 7 (WTL)

4.97/5 (83 votes)
12 Apr 2010CPL29 min read 251.9K   4.8K  
Including a Ribbon UI Implementation Guide with examples and a dual UI enabled legacy application

Image 1

Introduction

Windows 7 introduces a new Ribbon control, a system update makes it available to Vista and Windows Server 2008 platforms.

This article concentrates on a C++ application programer's point of view in the process of adapting existing or new applications to give the user a reversible choice between a Ribbon or traditional UI, using atlribbon.h and WTL 8.

References

The main documentation about the Windows 7 Ribbon is the MSDN Windows Ribbon Framework section.

You can find in CodeProject Michael Chourdakis's article : Windows 7 Ribbon: The Time Has Come, Your Win32 Application Will Change, and in Arik Poznanski's Blog detailed descriptions of the Ribbon features, and Windows Ribbon for WinForms to operate it in C# applications.

Prerequisites

To follow this article you need to have correctly installed on your computer:

  • Visual Studio or VCExpress 2008 with all service packs or Visual Studio 2010 Beta2.

  • The Windows 7 platform SDK: you need the uicc tool which is not part of Visual Studio 2010 Beta2 distribution.

  • WTL 8.0.

  • With VCExpress you need ATL 3 that can be found in the Windows® Server 2003 SP1 Platform SDK or ATL 7.1 as indicated in this post.

Obviously you should test on an updated Vista or a Windows 7 platform, you can compile and run everything under Vista, Windows 7 or XP.


Preview

Download MTPad7Exe.zip and run MTPad.exe.

On an updated Vista or a Windows 7 platform MTPad opens with a Ribbon UI.

Image 2

Open some text files, check the font selection preview feature, try the Print Preview mode, use the recent files in the Application Menu, open some new windows.

Uncheck the Ribbon checkbox to switch to traditional UI, use the View menu Ribbon entry to switch back to ribbon UI in Edit or Preview mode.

Image 3

Change the layout settings using the built-in dropdown at right of the QAT (Quick Access Toolbar), then switch back and forth to traditional UI and notice that when you switch back to Ribbon UI the ribbon layout settings are persisted.

Image 4

Close the last window in traditional UI and reopen MTPad. It reopens traditional.


First Experience

Using atlribbon.h, in some minutes you can build a dual (ribbon and traditional) user selectable UI on top of a traditional UI WTL application.

This first experience performs the necessary steps, with many execution details, as they apply to any Ribbon UI application.

Step 0: Create a WTL Application with minimal functionality

If you use Visual Studio 2010 Beta2 or for some reason have not installed the WTL AppWizard, download FirstRibbon.zip , unzip it and open FirstRibbon.vcproj.

Otherwise with the WTL AppWizard, create a FirstRibbon WTL application with SDI and no cpp...

Image 5

... default User Interface Features and Rich Edit view window.

Image 6

To handle the Edit commands add CRichEditCommands inheritance and message map chaining to CFirstRibbonView...

// FirstRibbonView.h : interface of the CFirstRibbonView class
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

class CFirstRibbonView : 
    public CWindowImpl<CFirstRibbonView, CRichEditCtrl>, // 0
    public CRichEditCommands<CFirstRibbonView> // 0
{
public:
    DECLARE_WND_SUPERCLASS(NULL, CRichEditCtrl::GetWndClassName())

    BOOL PreTranslateMessage(MSG* pMsg)
    {
        pMsg;
        return FALSE;
    }

    BEGIN_MSG_MAP(CFirstRibbonView)
        CHAIN_MSG_MAP_ALT(CRichEditCommands<CFirstRibbonView>, 1) // 0
    END_MSG_MAP()

};

… and chain the CMainFrame commands to CFirstRibbonView:

// MainFrm.h : interface of the CMainFrame class
// ...
    BEGIN_MSG_MAP(CMainFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
        COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
        COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
        COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        CHAIN_COMMANDS_MEMBER(m_view) // 0
        CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
    END_MSG_MAP()

Compile (any compilation error here means that your development environment is not correctly set up), run and check the edit functions with the toolbar buttons or the Edit menu.

Image 7

Step 1: Adapt the C++ code to host a Ribbon UI

Change the constants in stdafx.h to access the SDK Windows 7 features.

// stdafx.h : include file for standard system include files,
//...
// Change these values to use different versions
#define WINVER          0x0601 // 1
#define _WIN32_WINNT    0x0601 // 1
#define _WIN32_IE       0x0700 // 1
#define _RICHEDIT_VER   0x0200

Download atlribbon.zip, extract atlribbon.h to your <FirstRibbon> directory, and include it in FirstRibbon.cpp.

// FirstRibbon.cpp : main source file for FirstRibbon.exe
//

#include "stdafx.h"

#include <atlframe.h>
#include <atlctrls.h>
#include <atldlgs.h>
#include <atlctrlw.h>

#include "atlribbon.h" // 1

#include "resource.h"

Derive CMainFrame from WTL::CRibbonFrameWindowImpl; as CRibbonFrameWindowImpl derives from WTL::CRibbonUpdateUI, remove the CUpdateUI parent in the class definition and the message map and chain only CRibbonFrameWindowImpl<CMainFrame> in the message map.

// MainFrm.h : interface of the CMainFrame class
// ...
class CMainFrame : 
        public CRibbonFrameWindowImpl<CMainFrame>, // 1
        public CMessageFilter, public CIdleHandler
// ... 
    BEGIN_MSG_MAP(CMainFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
        COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
        COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
        COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        CHAIN_COMMANDS_MEMBER(m_view) // 0
        CHAIN_MSG_MAP(CRibbonFrameWindowImpl<CMainFrame>) // 1 
    END_MSG_MAP()

To run on pre-Vista or non updated Vista platforms, in Project->FirstRibbon Properties->Linker->Input set propsys.dll and dwmapi.dll as Delay Loaded.

Image 8

Compile to check that the step is completed.

Step 2: Create a ribbon ressource and embed it in the application

Add to your project a new xml file (for VCExpress a new C++ header file), name it FirstRibbonRibbon.xml and paste what follows into it:

<!-- FirstRibbonRibbon.xml -->

<Application xmlns="http://schemas.microsoft.com/windows/2009/Ribbon">

    <Application.Commands>

        <!-- FirstTab.rc Menu Commands-->
        <Command Name="wtl_FILE_NEW" Symbol="ID_FILE_NEW" Id="0xE100"/>
        <Command Name="wtl_FILE_OPEN" Symbol="ID_FILE_OPEN" Id="0xE101"/>
        <Command Name="wtl_FILE_SAVE" Symbol="ID_FILE_SAVE" Id="0xE103"/>
        <Command Name="wtl_FILE_SAVE_AS" Symbol="ID_FILE_SAVE_AS" Id="0xE104"/>

        <Command Name="wtl_FILE_PRINT" Symbol="ID_FILE_PRINT" Id="0xE107"/>
        <Command Name="wtl_FILE_PRINT_PREVIEW" Symbol="ID_FILE_PRINT_PREVIEW" Id="0xE109"/>
        <Command Name="wtl_FILE_PRINT_SETUP" Symbol="ID_FILE_PRINT_SETUP" Id="0xE106"/>

        <Command Name="wtl_APP_EXIT" Symbol="ID_APP_EXIT" Id="0xE141"/>

        <Command Name="wtl_EDIT_CUT" Symbol="ID_EDIT_CUT" Id="0xE123"/>
        <Command Name="wtl_EDIT_COPY" Symbol="ID_EDIT_COPY" Id="0xE122"/>
        <Command Name="wtl_EDIT_PASTE" Symbol="ID_EDIT_PASTE" Id="0xE125"/>

        <Command Name="wtl_VIEW_TOOLBAR" Symbol="ID_VIEW_TOOLBAR" Id="0xE800"/>
        <Command Name="wtl_VIEW_STATUS_BAR" Symbol="ID_VIEW_STATUS_BAR" Id="0xE801"/>
        <Command Name="wtl_VIEW_RIBBON" Symbol="ID_VIEW_RIBBON" Id="0xE804"/> 

        <Command Name="wtl_APP_ABOUT" Symbol="ID_APP_ABOUT" Id="0xE140"/>

        <!-- Tabs -->
        <Command Name="TabHome" Symbol="ID_TAB_HOME"
                 LabelTitle="Home" />

        <!-- Groups -->
        <Command Name="GroupClipboard" Symbol="ID_GROUP_CLIPBOARD"
                 LabelTitle="Clipboard" />
        <Command Name="GroupView" Symbol="ID_GROUP_VIEW"
                 LabelTitle="View" />

        <!-- App Menu, MRU list, Help button and Quick Access Toolbar -->
        <Command Name="AppMenu" Symbol="ID_RIBBON_APP_MENU"/>
        <Command Name="SaveMore" Symbol="ID_SAVEMORE"/>
        <Command Name="PrintMore" Symbol="ID_PRINTMORE"/>
      
        <Command Name="QAT" Symbol="ID_RIBBON_QAT"/>
    </Application.Commands>

    <Application.Views>
        <Ribbon>
            <!-- Application Menu -->
            <Ribbon.ApplicationMenu >
                <ApplicationMenu CommandName="AppMenu" >
                    <MenuGroup Class="StandardItems" >
                        <Button CommandName="wtl_FILE_NEW"/>
                        <Button CommandName="wtl_FILE_OPEN"/>
                        <!-- Saving SplitButton -->
                        <SplitButton CommandName="SaveMore">
                            <SplitButton.ButtonItem>
                                <Button CommandName="wtl_FILE_SAVE" />
                            </SplitButton.ButtonItem>
                            <SplitButton.MenuGroups>
                                <MenuGroup Class="StandardItems">
                                    <Button CommandName="wtl_FILE_SAVE" />
                                    <Button CommandName="wtl_FILE_SAVE_AS" />
                                </MenuGroup>
                            </SplitButton.MenuGroups>
                        </SplitButton>
                        <!-- Printing SplitButton -->
                        <SplitButton CommandName="PrintMore">
                            <SplitButton.ButtonItem>
                                <Button CommandName="wtl_FILE_PRINT"/>
                            </SplitButton.ButtonItem>
                            <SplitButton.MenuGroups>
                                <MenuGroup Class="StandardItems">
                                    <Button CommandName="wtl_FILE_PRINT" />
                                    <Button CommandName="wtl_FILE_PRINT_PREVIEW"/>
                                    <Button CommandName="wtl_FILE_PRINT_SETUP"/>
                                </MenuGroup>
                            </SplitButton.MenuGroups>
                        </SplitButton>
                    </MenuGroup>
                    <MenuGroup >
                        <Button CommandName="wtl_APP_EXIT"/>
                    </MenuGroup>
                </ApplicationMenu>
            </Ribbon.ApplicationMenu >

            <!-- Help button -->
            <Ribbon.HelpButton>
                <HelpButton CommandName="wtl_APP_ABOUT" />
            </Ribbon.HelpButton>

            <!-- QAT (Quick Access Toolbar) -->
            <Ribbon.QuickAccessToolbar>
                <QuickAccessToolbar CommandName="QAT">
                    <QuickAccessToolbar.ApplicationDefaults>
                        <Button CommandName="wtl_FILE_NEW"/>
                        <Button CommandName="wtl_FILE_OPEN"/>
                        <Button CommandName="wtl_FILE_SAVE"/>
                        <Button CommandName="wtl_FILE_PRINT"/>
                    </QuickAccessToolbar.ApplicationDefaults>
                </QuickAccessToolbar>
            </Ribbon.QuickAccessToolbar>

            <Ribbon.Tabs>

                <Tab CommandName="TabHome">
                    <Tab.ScalingPolicy>
                        <ScalingPolicy>
                            <ScalingPolicy.IdealSizes>
                                <Scale Group="GroupClipboard" Size="Medium"/>
                                <Scale Group="GroupView" Size="Large"/>
                            </ScalingPolicy.IdealSizes>
                        </ScalingPolicy>
                    </Tab.ScalingPolicy>

                    <Group CommandName="GroupClipboard" SizeDefinition="ThreeButtons">
                        <Button CommandName="wtl_EDIT_PASTE"/>
                        <Button CommandName="wtl_EDIT_CUT"/>
                        <Button CommandName="wtl_EDIT_COPY"/>
                    </Group>

                    <Group CommandName="GroupView">
                        <SizeDefinition>
                            <ControlNameMap>
                                <ControlNameDefinition Name="wtl_VIEW_RIBBON"/>
                                <ControlNameDefinition Name="wtl_VIEW_STATUS_BAR"/>
                            </ControlNameMap>
                            <GroupSizeDefinition Size="Large">
                                <ControlSizeDefinition ControlName="wtl_VIEW_RIBBON"
                                                       ImageSize="Small"
                                                       IsLabelVisible="true" /> 
                                <ControlSizeDefinition ControlName="wtl_VIEW_STATUS_BAR"
                                                       ImageSize="Small"
                                                       IsLabelVisible="true" />
                            </GroupSizeDefinition>
                            <GroupSizeDefinition Size="Medium">
                                <ControlSizeDefinition ControlName="wtl_VIEW_RIBBON"
                                                       ImageSize="Small"
                                                       IsLabelVisible="true" />
                                <ControlSizeDefinition ControlName="wtl_VIEW_STATUS_BAR"
                                                       ImageSize="Small"
                                                       IsLabelVisible="true" />
                            </GroupSizeDefinition>
                            <GroupSizeDefinition Size="Small">
                                <ControlSizeDefinition ControlName="wtl_VIEW_RIBBON"
                                                       ImageSize="Small"
                                                       IsLabelVisible="false" />
                                <ControlSizeDefinition ControlName="wtl_VIEW_STATUS_BAR"
                                                       ImageSize="Small"
                                                       IsLabelVisible="false" />
                            </GroupSizeDefinition>
                        </SizeDefinition>
                        <CheckBox CommandName="wtl_VIEW_RIBBON"/>
                        <CheckBox CommandName="wtl_VIEW_STATUS_BAR"/>
                    </Group>
                </Tab>
            </Ribbon.Tabs>
        </Ribbon>
    </Application.Views>
</Application>

In the VC Solution Explorer window right-click on FirstRibbonRibbon.xml and select Properties in the popup menu. Go to Custom Build Step->General and paste in the input fields:

    • CommandLine: uicc FirstRibbonRibbon.xml FirstRibbonRibbon.bml /header:FirstRibbonRibbon.h /res:FirstRibbonRibbon.rc

    • Description: Compiling FirstRibbonRibbon.xml

    • Outputs: FirstRibbonRibbon.bml;FirstRibbonRibbon.rc;FirstRibbonRibbon.h

    • Click OK to validate.

In the same VC Solution Explorer window right-click again on FirstRibbonRibbon.xml and select Compile: the output should be

1>------ Build started: Project: FirstRibbon, Configuration: Debug Win32 ------
1>Compiling FirstRibbonRibbon.xml
1>Header file generation successful: 'FirstRibbonRibbon.h'.
1>Ribbon markup file validation successful: 'FirstRibbonRibbon.xml'.
1>Ribbon resource file generation successful: 'FirstRibbonRibbon.rc'.
1>Build log was saved at "file://<...>\FirstRibbon\Debug\BuildLog.htm"
1>FirstRibbon - 0 error(s), 0 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Add the uicc generated files to the application FirstRibbon.rc file:

    • With Visual Studio in the Resource View window right click on FirstRibbon.rc and add #include "FirstRibbonRibbon.h" to the Read only symbol directives box and #include "FirstRibbonRibbon.rc" to the Compile time directives box.

Image 9

    • With VCExpress in the VC Solution Explorer window right-click on FirstRibbon.rc and select View Code in the popup menu. Edit three places in FirstRibbon.rc:

 // Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "atlres.h"
#include "FirstRibbonRibbon.h" // 2

//

/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""atlres.h""\r\n"
    "#include ""FirstRibbonRibbon.h""\r\0" // 2
END

3 TEXTINCLUDE 
BEGIN
    "#include ""FirstRibbonRibbon.rc\0" // 2
END

#endif    // APSTUDIO_INVOKED

// ...
// at the end of the file
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#include "FirstRibbonRibbon.rc" // 2
/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
// nothing more in the file
  • #include the uicc generated FirstRibbonRibbon.h in FirstRibbon.cpp:

// FirstRibbon.cpp : main source file for FirstRibbon.exe
//

#include "stdafx.h"

#include <atlframe.h>
#include <atlctrls.h>
#include <atldlgs.h>
#include <atlctrlw.h>

#include <atlmisc.h> // 1
#include "atlribbon.h" // 1

#include "FirstRibbonRibbon.h" // 2
#include "resource.h"
  • Build the project, FirstRibbon.rc should compile and you should have no error. Run it, there is no difference.

Step 3: Operate the Ribbon

Create a Ribbon command in the application View menu:

    • With Visual Studio in the Resource View window edit the IDR_MAINFRAME menu and add a &Ribbon entry with ID_VIEW_RIBBON id on top of the View menu.
      Edit the String Table to add an ID_VIEW_RIBBON string with Show or hide the ribbon\nToggle Ribbon as content.

    • With VCExpress in the VC Solution Explorer window right-click on FirstRibbon.rc and select View Code in the popup menu. Edit the POPUP "&View" block, and the STRINGTABLE block with the ID_VIEW_xxx strings.

// Menu
//

IDR_MAINFRAME MENU 
BEGIN
    // ...
    POPUP "&View"
    BEGIN
        MENUITEM "&Ribbon",                     ID_VIEW_RIBBON // 3
        MENUITEM "&Toolbar",                    ID_VIEW_TOOLBAR
        MENUITEM "&Status Bar",                 ID_VIEW_STATUS_BAR
    ENDPOPUP "&View"
// ...
STRINGTABLE 
BEGIN
    ID_VIEW_TOOLBAR         "Show or hide the toolbar\nToggle ToolBar"
    ID_VIEW_STATUS_BAR      "Show or hide the status bar\nToggle StatusBar"
    ID_VIEW_RIBBON          "Show or hide the ribbon\nToggle Ribbon" // 3
END

Add a command handler for ID_VIEW_RIBBON in the CMainFrame message map ...

// MainFrm.h : interface of the CMainFrame class
// ...
BEGIN_MSG_MAP(CMainFrame)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
    COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
    COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
    COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
    COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
    COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
    COMMAND_ID_HANDLER(ID_VIEW_RIBBON, OnViewRibbon) // 3
    CHAIN_COMMANDS_MEMBER(m_view) // 0

… and define it at the end of the class definition.

// MainFrm.h : interface of the CMainFrame class
// ...

LRESULT OnViewRibbon(WORD /*wNotifyCode*/, WORD /*wID*/, // 3
    HWND /*hWndCtl*/, BOOL& /*bHandled*/) // 3 
{ // 3
    ShowRibbonUI(!IsRibbonUI()); // 3 
    UISetCheck(ID_VIEW_RIBBON, IsRibbonUI()); // 3 
    return 0; // 3 
} // 3 

To remove the ID_VIEW_RIBBON command when Ribbon UI is not supported, and otherwise set the Ribbon Command Labels with the menu texts, add to CMainFrame::OnCreate().

// MainFrm.h : interface of the CMainFrame class
// ...

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // create command bar window
    HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL, 
        ATL_SIMPLE_CMDBAR_PANE_STYLE);
    // attach menu
    m_CmdBar.AttachMenu(GetMenu());
    // load command bar images
    m_CmdBar.LoadImages(IDR_MAINFRAME);
    // remove old menu
    SetMenu(NULL);

    bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable(); // 3
    if (bRibbonUI) // 3
        UIAddMenu(m_CmdBar.GetMenu(), true); // 3
    else // 3 
        CMenuHandle(m_CmdBar.GetMenu()).DeleteMenu(ID_VIEW_RIBBON, MF_BYCOMMAND); // 3 

    HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME, FALSE, 
        ATL_SIMPLE_TOOLBAR_PANE_STYLE);
// ...

Compile and run, see no changes, click Ribbon in the View menu:

Image 10

Yes you can!

Image 11

It's time to play around, change ribbon settings, switch UI modes, check commands, see ribbon tooltips, try running under XP, enjoy ...

Additional Step: contextual UI startup and enabling of edit commands

At the end of CMainFrame::OnCreate() ...

// ...
    ShowRibbonUI(bRibbonUI); // 4
    UISetCheck(ID_VIEW_RIBBON, bRibbonUI); // 4

    return 0;
}

… and in CMainFrame::OnIdle().

virtual BOOL OnIdle()
{
        UIEnable(ID_EDIT_UNDO, m_view.CanUndo()); // 4
        UIEnable(ID_EDIT_PASTE, m_view.CanPaste(CF_TEXT)); // 4
        UIEnable(ID_EDIT_CUT, m_view.CanCut()); // 4
        UIEnable(ID_EDIT_COPY, m_view.CanCopy()); // 4

        UIUpdateToolBar();
        return FALSE;
}

Compile, run and check that FirstRibbon starts in Ribbon UI when supported and that in both UI the edit commands are disabled when irrelevant.


Ribbon UI Implementation Guide

This guide describes with examples the implementation of a Ribbon UI in a CMainFrame frame window deriving from CRibbonFrameWindowImplBase such as CRibbonFrameWindowImpl or CRibbonMDIFrameWindowImpl.

Review of our First Experience

class CMainFrame : 
        public CRibbonFrameWindowImpl<CMainFrame>, // 1
        public CMessageFilter, public CIdleHandler 
{
// ...

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // ...
    bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable(); // 2
    if (bRibbonUI) 
        UIAddMenu(m_CmdBar.GetMenu(), true); // 4
    else 
        CMenuHandle(m_CmdBar.GetMenu()).DeleteMenu(ID_VIEW_RIBBON, MF_BYCOMMAND);  
    // ...

    ShowRibbonUI(bRibbonUI); // 5
    UISetCheck(ID_VIEW_RIBBON, bRibbonUI); 
// ...

LRESULT OnViewRibbon(WORD /*wNotifyCode*/, WORD /*wID*/, 
        HWND /*hWndCtl*/, BOOL& /*bHandled*/) 
    { 
        ShowRibbonUI(!IsRibbonUI()); // 5 & 3 
        UISetCheck(ID_VIEW_RIBBON, IsRibbonUI()); 
        return 0; 
    } 
  1. CRibbonFrameWindowImpl<CMainFrame> inherits from WTL::CFrameWindowImpl<CMainFrame> which implements the traditional UI , and from WTL::RibbonUI::CRibbonImpl<CMainFrame> which implements the Ribbon UI. So CMainFrame is a dual UI window, in runtime environments which support the Ribbon UI.

  2. bool WTL::RunTimeHelper::IsRibbonUIAvailable() performs a runtime test and returns true if running under updated Vista or Win7. If false, the View->Ribbon menu entry is removed so that the Ribbon UI code is never called.

  3. bool CRibbonImpl::IsRibbonUI() returns true if the Ribbon UI is currently shown, false otherwise

  4. CRibbonImpl inherits from WTL::CRibbonUpdateUI<CMainFrame> which extends WTL::CAutoUpdateUI<CMainFrame> described in this article and handles an additional UPDUI_RIBBON type.
    bool WTL::CAutoUpdateUI<CMainFrame>::UIAddMenu(HMENU hm, bool bSetText) adds all (non popup) menu items of the hm menu to it's UPDATE_UI_MAP and calls UISetText(ID, text) with each menu item ID and text.

  5. bool WTL::CRibbonFrameWindowImplBase<CMainFrame>::ShowRibbonUI(bool bShow, INT32 imodes = UI_MAKEAPPMODE(0), LPCWSTR sResName = L"APPLICATION_RIBBON") returns the Ribbon UI activity state after the call: bShow is the requested state, imodes is the requested Ribbon Modes and defaults to Mode 0, sResName is the string ID of the UIFILE resource in the Ribbon resource file.

ShowRibbonUI(true) operation

ShowRibbonUI(true) hides the Rebar containing the traditional UI CommandBar and Toolbar, and calls CRibbonImpl::CreateRibbon(sResName) which performs the steps described here.

The newly created Ribbon calls CRibbonImpl::OnCreateUICommand() for each ID in it's markup file. Each ID is added to the UPDATE_UI_MAP with UPDUI_RIBBON type.

So the common UI elements have both UPDUI_RIBBON and -for instance- UPDUI_MENUPOPUP types in the UPDATE_UI_MAP. Ribbon elements label text, enabled and checked states can be managed through overrides of the usual UISetText(), UISetCheck() and UIEnable() calls.

ShowRibbonUI(false) operation

ShowRibbonUI(false) calls CRibbonImpl::DestroyRibbon() which in turn calls IUIFramework::Destroy().

The Ribbon calls CRibbonImpl::OnDestroyUICommand() for each ID in it's markup file. The UPDUI_RIBBON type is removed from the UPDATE_UI_MAP for that ID.

The ID itself is removed from the UPDATE_UI_MAP if it has no other UPDUI_type, which can be prevented by a call to CRibbonUpdateUI::UIPersistElement(UINT nID, true).

ShowRibbonUI(false) then restores the Rebar containing the traditional UI CommandBar and Toolbar.

Default Ribbon UI behavior

When the Ribbon is active it calls CRibbonImpl::UpdateProperty() with an element ID and a Property Key reference.

This table summarizes the default CRibbonImpl behavior.

Key

Setting

Overrideable Callback

Default processing

UI_PKEY_Label

UISetText(ID, LPCWSTR)

LPCWSTR OnRibbonQueryText(ID, key)

returns UIGetText(ID)

UI_PKEY_Enabled

UIEnable(ID, bool)

bool OnRibbonQueryState(ID, key)

returns UIGetState(ID) & ~UPDUI_DISABLED

UI_PKEY__BooleanValue

UISetCheck(ID, bool)

bool OnRibbonQueryState(ID, key)

returns UIGetState(ID) & UPDUI_CHECKED

UI_PKEY_LabelDescription UI_PKEY_TooltipDescription

LPCWSTR OnRibbonQueryText(ID, key)

part of the ressource string with ID preceding '\n' (or NULL)

UI_PKEY_TooltipTitle

LPCWSTR OnRibbonQueryText(ID, key)

part of the ressource string with ID following '\n' (or NULL)

UI_PKEY_Keytip

LPCWSTR OnRibbonQueryText(ID, key)

1 character substring of the ressource string with ID following '&' (or NULL)

UI_PKEY_SmallImage

HBITMAP OnRibbonQueryImage(ID, key)

HBITMAP of ID in m_CmdBar.m_arrVistaBitmap or HBITMAP of the ressource bitmap with ID (or NULL)

UI_PKEY_LargeImage UI_PKEY_SmallHighContrastImage UI_PKEY_LargeHighContrastImage

HBITMAP OnRibbonQueryImage(ID, key)

HBITMAP of the ressource bitmap with ID (or NULL)

When the user acts on a Ribbon UI element CRibbonImpl::Execute is called by the Ribbon with an element ID a UI_EXECUTIONVERB and a Property Key reference.

  • CRibbonImpl::Execute() calls the overrideable OnRibbonCommandExecute(UINT32 uCmdID, ...).

  • If not overridden CRibbonImpl::OnRibbonCommandExecute() posts a WM_COMMAND message with WPARAM set to uCmdID.

Practical summary

  • Any item of a Menu resource (inserted in the UI_MAP with UIAddMenu(IdMenu, true)) with a same id two parts string resource as "Create a new document\nNew" gets the menu name, resource string tooltip title and tooltip body in the Ribbon.

  • If an item is part of a toolbar added to a m_CmdBar (through m_CmdBar.LoadImages(IdToolBar)) it's UI_PKEY_SmallImage in the Ribbon is the same as in the CommandBar Menu and the bitmap resource with same Id (if existing) is used for all other (all if no CCommandBarCtrl bitmap) Image properties.

  • For any Ribbon defined Command UI_PKEY_Label, UI_PKEY_Enabled and UI_PKEY_BooleanValue are handled through UISetText(), UIEnable() and UISetCheck().

  • WM_COMMAND messages with the Ribbon element ID are posted when the user clicks or checks/unchecks a Ribbon element.

That's how our FirstRibbon Ribbon UI could work at once.

Example : add an Undo Ribbon Button with default processing

ID_EDIT_UNDO is present in the IDR_MAINFRAME menu and has a string resource in FirstRibbon.rc, add it to the Commands declaration and the GroupClipboard Group in FirstRibbonRibbon.xml.

    <Application.Commands>

        <!-- FirstTab.rc Menu Commands-->
        <Command Name="wtl_EDIT_UNDO" Symbol="ID_EDIT_UNDO" Id="0xE12B"/>
...
      <Ribbon.Tabs>
...
        <Tab CommandName="TabHome">
          <Group CommandName="GroupClipboard" SizeDefinition="FourButtons">
            <Button CommandName="wtl_EDIT_UNDO"/>
            <Button CommandName="wtl_EDIT_PASTE"/>
            <Button CommandName="wtl_EDIT_CUT"/>
            <Button CommandName="wtl_EDIT_COPY"/>
          </Group>

Download FirstRibbonBitmaps.zip extract Undo.bmp to FirstRibbon\res and add it to FirstRibbon.rc with ID_EDIT_UNDO id.

// Bitmap
//

IDR_MAINFRAME           BITMAP                  "res\\Toolbar.bmp"
ID_EDIT_UNDO            BITMAP                  "res\\Undo.bmp"

Image 12


CRibbonImpl control classes and RIBBON_CONTROL_MAP() macros

The CRibbonImpl::CRibbonXXXCtrl control classes handle the Windows Ribbon Framework Control Library supported controls.

CRibbonImpl and all CRibbonXXXCtrl classes implement the WTL::RibbonUI::ICtrl abstract interface. The BEGIN_RIBBON_CONTROL_MAP(), RIBBON_CONTROL() and END_RIBBON_CONTROL_MAP() macros build a ICtrl& GetRibbonCtrl(UINT ID) function which returns a reference to a control's ICtrl if it's ID is in the map or to CRibbonImpl::ICtrl otherwise.

Implementation of a control requires 3 steps:

  1. Identify and insert the control in a Group with adequate properties in the Ribbon markup.

<Command Name="TestColor" Symbol="ID_TEST_COLOR"
             LabelTitle="Color" />
...
<Command Name="GroupColor" Symbol="ID_GROUP_COLOR"
             LabelTitle="Background" />
...
<Group CommandName="GroupColor" SizeDefinition="OneButton">
      <DropDownColorPicker
          CommandName="TestColor"
          ColorTemplate="StandardColors"/>
</Group> 
  1. Instanciate a CRibbonImpl::CRibbonXXXCtrl with matching ID CMainFrame member.

CRibbonColorCtrl<ID_TEST_COLOR> m_color;
  1. Insert the member in the RIBBON_CONTROL_MAP.

BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_color)
    ...
END_RIBBON_CONTROL_MAP() 

CRibbonImpl control classes common API

All CMainFrame::CRibbonImpl control classes derive from WTL::RibbonUI::CtrlImpl<ID, CMainFrame> and expose:

  • HRESULT Invalidate() and HRESULT Invalidate(key, UI_INVALIDATIONS flags = UI_INVALIDATIONS_PROPERTY) which Invalidates all (or one) Property Key for this ID.

  • HRESULT SetText(key&, LPCWSTR sText, bool bUpdate = false): copies sText to an internal string associated to key and if bUpdate is true calls Invalidate(key).

At startup or when a key is uninitialized or invalidated the Ribbon calls CRibbonImpl::UpdateProperty() which uses the RIBBON_CONTROL_MAP to select the affected control class instance and call it's relevant member function (ie OnGetText() for text keys).

If the Condition for callback is met:

  • value for this key is considered invalid,

  • the relevant CRibbonImpl overrideable callback is called,

  • If not overridden Default processing is performed ,

  • the return value is stored.

  • The stored value is returned to the Ribbon.

CRibbonImpl control classes common Property Keys

Key

Setting

CRibbonImpl overrideable callback

Condition for callback

Default processing

UI_PKEY_LabelDescription UI_PKEY_TooltipDescription

SetText(key, LPCWSTR, bool bUpdate = false)

LPCWSTR OnRibbonQueryText(nCmdID, key)

Current string value is empty

part of the ressource string with ID preceding a '\n' (or the whole string)

UI_PKEY_TooltipTitle

SetText(key, LPCWSTR, bool bUpdate = false)

LPCWSTR OnRibbonQueryText(nCmdID, key)

Current string value is empty

part of the ressource string with ID following a '\n' (or NULL)

UI_PKEY_Keytip

SetText(key, LPCWSTR, bool bUpdate = false)

LPCWSTR OnRibbonQueryText(nCmdID, key)

Current string value is empty

1 character of the ressource string with ID following a '&' if found or a '\n' (or NULL)


The default processing is the same as CRibbonImpl, but values can be set and are persistent when the user switches UI.

Preliminary step for the examples

Some of the examples use the status bar to output user actions on the Ribbon. To enable their usage of UISetText(ID_DEFAULT_PANE, someText), define _ATL_USE_CSTRING_FLOAT in stdafx.h ...

// stdafx.h : include file for standard system include files,
//  ...
#define _RICHEDIT_VER   0x0200

#define _ATL_USE_CSTRING_FLOAT

... and add to CMainFrame calls to UIUpdateStatusBar() in OnIdle() and to UIAddStatusBar() in OnCreate().

//...
virtual BOOL OnIdle()
{
    //...
    UIUpdateToolBar();
    UIUpdateStatusBar();
    return FALSE;
}
// ...
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
//...
    CreateSimpleStatusBar();
    UIAddStatusBar(m_hWndStatusBar);
//...

Ribbon Single Control classes

The CRibbonImpl single control classes handle all Ribbon Controls except Galleries, Combos and Recent Items.

CRibbonCommandCtrl

  • Declaration in CRibbonImpl derived class: CRibbonCommandCtrl<ID> m_Ctrl;

  • User action message map macro: COMMAND_ID_HANDLER(wID, OnCommand)

  • Handler prototype: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled)

CRibbonImpl::CRibbonCommandCtrl operates on Ribbon Buttons, Drop-Down Buttons, Toggle Buttons, Groups, Menu Groups, Tabs and Tab Groups to implement a specific handling of the CRibbonImpl control classes common Property Keys and if relevant:

Key

Setting

CRibbonImpl overrideable callback

Condition for callback

Default processing

UI_PKEY_SmallImage

SetImage(key, HBITMAP, bool bUpdate = false)

HBITMAP OnRibbonQueryImage(nCmdID, key)

Current HBITMAP is NULL

HBITMAP of ID in m_CmdBar.m_arrVistaBitmap or HBITMAP of the ressource bitmap with ID (or NULL)

UI_PKEY_LargeImage UI_PKEY_SmallHighContrastImage UI_PKEY_LargeHighContrastImage

SetImage(key, HBITMAP, bool bUpdate = false)

HBITMAP OnRibbonQueryImage(nCmdID, key)

Current HBITMAP is NULL

HBITMAP of the ressource bitmap with ID (or NULL)

Example : handle a Ribbon Group Control

In CMainFrame declare a CRibbonCommandCtrl with the Ribbon GroupClipboard Control ID and insert it in the RIBBON_CONTROL_MAP,

// Ribbon controls
CRibbonCommandCtrl<ID_GROUP_CLIPBOARD> m_clipboard;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_clipboard)

Set some values in CMainFrame::OnCreate()

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // ...
    bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable(); 
    if (bRibbonUI) 
    {
        // ...
        m_clipboard.SetImage(UI_PKEY_SmallImage, GetCommandBarBitmap(ID_EDIT_PASTE)); 
        m_clipboard.SetText(UI_PKEY_TooltipTitle, L"Clipboard Commands"); 
        m_clipboard.SetText(UI_PKEY_TooltipDescription, 
            L"Transfer selection to and from the clipboard");

When the Ribbon is more populated if the GroupClipboard collapses to Button:

Image 13


CRibbonColorCtrl

  • Declaration in CRibbonImpl derived class: CRibbonColorCtrl<wID> m_Ctrl;
  • User action message map macro: RIBBON_COLOR_CONTROL_HANDLER(wID, OnRibbonColorCtrl)

  • Handler prototype: LRESULT OnRibbonColorCtrl(UI_EXECUTIONVERB verb, WORD wID, COLORREF color, BOOL& bHandled);

CRibbonImpl::CRibbonColorCtrl operates on Ribbon DropDownColorPicker Controls and handles:

Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_Color

m_Ctrl.SetColor(COLORREF color,
bool bUpdate = false)

COLORREF OnRibbonQueryColor(ID)

Current color is MAGENTA

returns 0x800080 MAGENTA (no change)

UI_PKEY_ColorType

m_Ctrl.SetColorType(
UI_SWATCHCOLORTYPE type,
bool bUpdate = false)

UI_SWATCHCOLORTYPE_NOCOLOR if not set by SetColorType() or SetColor()

UI_PKEY_ThemeColorsCategoryLabel UI_PKEY_StandardColorsCategoryLabel UI_PKEY_RecentColorsCategoryLabel UI_PKEY_AutomaticColorLabel UI_PKEY_NoColorLabel UI_PKEY_MoreColorsLabel

m_Ctrl.SetColorLabel(key, LPCWSTR sLabel,
bool bUpdate = false)

LPCWSTR OnRibbonQueryColorLabel(ID, key)

Current string value is empty

Returns NULL (no change)

UI_PKEY_ThemeColors UI_PKEY_StandardColors

m_Ctrl.SetColorArray(key, COLORREF* pColor,
bool bUpdate = false)

COLORREF* OnRibbonQueryColorArray(ID, key)

Current color array is empty

Returns NULL (no change)

UI_PKEY_ThemeColorsTooltips UI_PKEY_StandardColorsTooltips

m_Ctrl.SetColorTooltips(key, LPCWSTR*,
bool bUpdate = false)

LPCWSTR* OnRibbonQueryColorTooltips(ID, key)

Current string array is empty

Returns NULL (no change)

The COLORREF* returned by OnRibbonQueryColorArray() or passed to SetColorArray() is the address of element 0 of an array of COLORREF ending with 0x800080 MAGENTA.

The LPCWSTR* returned by OnRibbonQueryColorTooltips() or passed to SetColorTooltips() is the address of element 0 of an array of LPCWSTR ending with NULL.

Example : add and handle a Color Control

In the markup file declare group and command IDs and declare a DropDownColorPicker in a Tab

    <!--Controls-->
    <Command Name="TestColor" Symbol="ID_TEST_COLOR"
             LabelTitle="Color" />
      

     <Command Name="GroupColor" Symbol="ID_GROUP_COLOR"
             LabelTitle="Background" />

      <Ribbon.Tabs>
...
        <Tab CommandName="TabHome">
...
            <Group CommandName="GroupColor" SizeDefinition="OneButton">
                <DropDownColorPicker
                  CommandName="TestColor"
                  ColorTemplate="StandardColors"/>
            </Group>

        </Tab>

In CMainFrame declare a CRibbonColorCtrl with the same ID and insert it in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonColorCtrl<ID_TEST_COLOR> m_color;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_color)
END_RIBBON_CONTROL_MAP()

CRibbonImpl calls OnRibbonQueryColor() when the Ribbon needs to know which color it should display as selected. CRibbonImpl::OnRibbonQueryColor() returns 0x800080 (MAGENTA) so override it to return the current background color.

// Ribbon controls queries handling
COLORREF OnRibbonQueryColor(UINT nID)
{
    COLORREF color = m_view.SetBackgroundColor();
    m_view.SetBackgroundColor(color);
    return color;
}

When the user selects or hovers a color in the control CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to the UI_EXECUTIONVERB, LOWORD(wParam) set to the control ID, and lParam set to the selected COLORREF.

Handle this message in CFirstRibbonView with the RIBBON_COLOR_CONTROL_HANDLER macro.

// FirstRibbonView.h : interface of the CFirstRibbonView class
//
    BEGIN_MSG_MAP(CFirstRibbonView)
        CHAIN_MSG_MAP_ALT(CRichEditCommands<CFirstRibbonView>, 1) // 0
        RIBBON_COLOR_CONTROL_HANDLER(ID_TEST_COLOR, OnRibbonColor)
    END_MSG_MAP()

    LRESULT OnRibbonColor(UI_EXECUTIONVERB verb, WORD wID, COLORREF color, BOOL& bHandled)
    {
        SetBackgroundColor(color);
        return 0;
    }

No image is associated to the Color Control button. Extract DDCP.bmp from FirstRibbonBitmaps.zip to FirstRibbon\res and add it to FirstRibbon.rc with ID_TEST_COLOR id.

// Bitmap
//

ID_TEST_COLOR           BITMAP                  "res\\DDCP.bmp"

Image 14


CRibbonFontCtrl

  • Declaration in CRibbonImpl derived class: CRibbonFontCtrl<wID> m_Ctrl;

  • User action message map macro: RIBBON_FONT_CONTROL_HANDLER(wID, OnRibbonFontCtrl)

  • Handler prototype: LRESULT OnRibbonFontCtrl(UI_EXECUTIONVERB verb, WORD wID, CHARFORMAT2* pcf, BOOL& bHandled);

CRibbonImpl::CRibbonFontCtrl operates on Ribbon Font Controls and handles:

Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_FontProperties

Invalidate(UI_PKEY_FontProperties)

bool OnRibbonQueryFont(ID, CHARFORMAT2&)

always

returns false (no change)

Example : add and handle a Font Control

In the markup file declare group and command IDs and declare a FontControl in a Tab

    <!--Controls-->
    <Command Name="TestFont" Symbol="ID_TEST_FONT"
             LabelTitle="Font" />

    <!-- Groups -->
    <Command Name="GroupFont" Symbol="ID_GROUP_FONT"
             LabelTitle="Font" />

      <Ribbon.Tabs>
...
        <Tab CommandName="TabHome">
...
            <Group CommandName="GroupFont" SizeDefinition="OneFontControl">
                <FontControl CommandName="TestFont" 
                             FontType="FontWithColor"
                             ShowTrueTypeOnly="false"
                             ShowVerticalFonts="false"/>
            </Group>

        </Tab>

In CMainFrame declare a CRibbonFontCtrl with the same ID and insert it in the RIBBON_CONTROL_MAP.

    // Ribbon controls
    CRibbonFontCtrl<ID_TEST_FONT> m_font;
// ...
    // Ribbon control map
    BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
        RIBBON_CONTROL(m_font)

CRibbonImpl calls OnRibbonQueryFont() when the Ribbon needs to know which font features it should display as selected. CRibbonImpl::OnRibbonQueryFont() returns false (no change) so override it to return the current font's CHARFORMAT2.

bool OnRibbonQueryFont(UINT /*nId*/, CHARFORMAT2& cf)
{
    m_view.GetDefaultCharFormat(cf);
    return true;
}

When the user selects a feature in the control CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to the UI_EXECUTIONVERB, LOWORD(wParam) set to the control ID, and lParam set to a CHARFORMAT2*.

Handle this message in CFirstRibbonView with the RIBBON_FONT_CONTROL_HANDLER macro.

// FirstRibbonView.h : interface of the CFirstRibbonView class
//
        BEGIN_MSG_MAP(CFirstRibbonView)
                CHAIN_MSG_MAP_ALT(CRichEditCommands<CFirstRibbonView>, 1) // 0
                RIBBON_FONT_CONTROL_HANDLER(ID_TEST_FONT, OnRibbonFont)
        END_MSG_MAP()

    LRESULT OnRibbonFont(UI_EXECUTIONVERB verb, WORD /*wID*/, CHARFORMAT2* pcf, BOOL& /*bHandled*/)
    {
        SetDefaultCharFormat(*pcf);
        return 0;
    }

Image 15


CRibbonSpinnerCtrl, CRibbonFloatSpinnerCtrl

  • Declarations in CRibbonImpl derived class:

  • CRibbonSpinnerCtrl<wID> m_Ctrl;

  • CRibbonFloatSpinnerCtrl<wID> m_Ctrl;

  • User action message map macros:

  • RIBBON_SPINNER_CONTROL_HANDLER(wID, OnRibbonSpinnerCtrl)

  • RIBBON_FLOATSPINNER_CONTROL_HANDLER(wID, OnRibbonFloatSpinnerCtrl)

  • Handler prototypes:

  • LRESULT OnRibbonSpinnerCtrl(WORD wID, LONG lVal, BOOL& bHandled);

  • LRESULT OnRibbonFloatSpinnerCtrl(WORD wID, DOUBLE* pdVal, BOOL& bHandled);CRibbonImpl:: CRibbonSpinnerCtrl and CRibbonImpl:: CRibbonFloatSpinnerCtrl both operate on Ribbon Spinner Controls and handle V beeing a LONG (CRibbonSpinnerCtrl) or a DOUBLE (CRibbonFloatSpinnerCtrl):

Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_DecimalPlaces

SetDecimalPlaces(V vPlaces, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
orOnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

always

returns false (no change)

UI_PKEY_DecimalValue

SetVal(V vVal, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
orOnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

always

returns false (no change)

UI_PKEY_MinValue

SetMin(V vMin, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
orOnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

always

returns false (no change)

UI_PKEY_MaxValue

SetMax(V vMax, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
orOnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

always

returns false (no change)

UI_PKEY_Increment

SetIncrement(V vIncrement, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
orOnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

always

returns false (no change)

UI_PKEY_FormatString

SetFormatString(LPCWSTR sFormat, bool bUpdate = false)

LPCWSTR OnRibbonQueryText(UINT nCmdID, REFPROPERTYKEY key)

Current string is empty

Returns NULL (no change)

UI_PKEY_RepresentativeString

SetRepresentativeString(LPCWSTR sRepresentative, bool bUpdate = false)

LPCWSTR OnRibbonQueryText(UINT nCmdID, REFPROPERTYKEY key)

Current string is empty

Returns NULL (no change)

Example : add and handle Spinner Controls

In the markup file declare group and command IDs and declare the Spinners in a Tab

    <!--Controls-->
    <Command Name="TestSpinner" Symbol="ID_TEST_SPINNER"
             LabelTitle="Spinner" />
    <Command Name="TestFloatSpinner" Symbol="ID_TEST_FLOATSPINNER"
             LabelTitle="Float Spinner" />

    <!-- Groups -->
    <Command Name="GroupSpinner" Symbol="ID_GROUP_SPINNER"
             LabelTitle="Spinners" />

      <Ribbon.Tabs>
...
        <Tab CommandName="TabHome">
...
            <Group CommandName="GroupSpinner">
                <Spinner CommandName="TestSpinner" />
                <Spinner CommandName="TestFloatSpinner" />
            </Group>

        </Tab>

In CMainFrame declare a CRibbonSpinnerCtrl and a CRibbonFloatSpinnerCtrl with the matching Ids and insert them in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonSpinnerCtrl<ID_TEST_SPINNER> m_spinner;
CRibbonFloatSpinnerCtrl<ID_TEST_FLOATSPINNER> m_spinnerFloat;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_spinner)
    RIBBON_CONTROL(m_spinnerFloat)

When the user sets a value in a Spinner:

  • For a CRibbonSpinnerCtrl, CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to 0, LOWORD(wParam) set to the control ID, and lParam set to the LONG value.

  • For a CRibbonFloatSpinnerCtrl, CRibbonImpl posts a WM_COMMAND message with the same wParam, and lParam set to a DOUBLE* pointing to the value.

… Handle these messages in CMainFrame with the RIBBON_SPINNER_CONTROL_HANDLER and RIBBON_FLOATSPINNER_CONTROL_HANDLER macros.

BEGIN_MSG_MAP(CMainFrame)
// ...
    RIBBON_SPINNER_CONTROL_HANDLER(ID_TEST_SPINNER, OnSpinner)
    RIBBON_FLOATSPINNER_CONTROL_HANDLER(ID_TEST_FLOATSPINNER, OnFloatSpinner)
//

    LRESULT OnSpinner(WORD wID, LONG lVal, BOOL& bHandled)
    {
        CString sVal;
        sVal.Format(L"%d", lVal);
        UISetText(ID_DEFAULT_PANE, sVal);
        return 0;
    }

    LRESULT OnFloatSpinner(WORD wID, DOUBLE* pdVal, BOOL& bHandled)
    {
        CString sVal;
        sVal.Format(L"%.2f", *pdVal);
        UISetText(ID_DEFAULT_PANE, sVal);
        return 0;
    }

Image 16

Example : Three ways to initialize Ribbon Controls

The default initial values for CRibbonImpl::CRibbonSpinnerCtrl and CRibbonImpl::CRibbonSpinnerFloatCtrl are a range of 0 to 100, an increment of 1, a start value of 1, 0 decimal places (1 for CRibbonSpinnerFloatCtrl).

Initialize the controls to:

  • ID_TEST_FLOATSPINNER a range of 10 to 20, an increment of .25, a start value of 14.75, 2 decimal places, and adjust the width for 4 digits and 1 decimal point.

  • ID_TEST_SPINNER the same width and a start value of 51.

using one of the following methods:

  • The easiest, initialize in CMainFrame::OnCreate():
    LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
    // ...
        bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable(); 
        if (bRibbonUI) 
        {
            // ...
            m_spinnerFloat.SetRepresentativeString(L"99.99");
            m_spinnerFloat.SetDecimalPlaces(2);
            m_spinnerFloat.SetIncrement(.25);
            m_spinnerFloat.SetMin(10);
            m_spinnerFloat.SetMax(20);
            m_spinnerFloat.SetVal(14.75);
    
            m_spinner.SetRepresentativeString(L"99.99");
            m_spinner.SetVal(51);
    }
  • If the values may change at runtime, override the relevant CRibbonImpl::OnRibbonQueryxxx() members:
    // Ribbon controls queries handling
    bool OnRibbonQuerySpinnerValue(UINT nCmdID, REFPROPERTYKEY key, LONG* pVal)
    {
        if (key == UI_PKEY_DecimalValue)
        {
            *pVal = 50;
            return true;
        }
        return false;
    }
    
    bool OnRibbonQueryFloatSpinnerValue(UINT nCmdID, REFPROPERTYKEY key, DOUBLE* pdVal)
    {
        if (key == UI_PKEY_DecimalPlaces)
            *pdVal = 2;
        else if (key == UI_PKEY_MinValue)
            *pdVal = 10;
        else if (key == UI_PKEY_MaxValue)
            *pdVal = 20;
        else if (key == UI_PKEY_DecimalValue)
            *pdVal = 14.75;
        else if (key == UI_PKEY_Increment)
            *pdVal = .25;
    
        return true;
    }
    
    LPCWSTR OnRibbonQueryText(UINT nCmdID, REFPROPERTYKEY key)
    {
        if ((nCmdID == ID_TEST_SPINNER) || (nCmdID == ID_TEST_FLOATSPINNER))
            return key == UI_PKEY_RepresentativeString ? L"99.99" : NULL;
        else
            return DefRibbonQueryText(nCmdID, key);
    }
  • The most object oriented, specialize the constructors (not available for simple and toolbar gallery and combo controls):
    // MainFrm.h : interface of the CMainFrame class
    // ...
    
    class CMainFrame
    {
    // ...
    };
    
    CMainFrame::CRibbonSpinnerCtrl<ID_TEST_SPINNER>::CRibbonSpinnerCtrl()
    {
        SetRepresentativeString(L"99.99");
        SetVal(51);
    }
    
    CMainFrame::CRibbonFloatSpinnerCtrl<ID_TEST_FLOATSPINNER>::CRibbonFloatSpinnerCtrl()
    {
        SetRepresentativeString(L"99.99");
        SetDecimalPlaces(2);
        SetIncrement(.25);
        SetMin(10);
        SetMax(20);
        SetVal(14.75);
    }
  • Any mix of 1 to 3 which meets your liking or needs.


Ribbon Collection Controls

The CRibbonImpl collection control classes handle Ribbon Galleries, Combos and Recent Items Ribbon Controls.

The full classes, CRibbonItemGalleryCtrl, CRibbonCommandGalleryCtrl and CRibbonComboCtrl handle categories, dynamic resizing and item configuration.

The simple classes CRibbonSimpleGalleryCtrl, CRibbonSimpleComboCtrl and CRibbonToolBarGalleryCtrl don't handle categories and are configured once at Ribbon Creation.

Ribbon Full Collection classes

Common Property Keys for Full Collection classes:

Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_ItemsSource

InvalidateItems() or
Resize(size_t size, bool bUpdate = false)

Item keys are queried for all items.

UI_PKEY_Categories

InvalidateCategories()

UI_PKEY_Label is queried for each category and UI_PKEY_CategoryId for each item.

Category Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_Label

SetCategoryText(UINT uCat, LPCWSTR sText, bool bUpdate = false)

LPCWSTR OnRibbonQueryCategoryText(ID, UINT32 uCat)

Current string is empty

Returns « Category »

Item Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_CategoryId

SetItemCategory(UINT uItem, UINT uCat, bool bUpdate = false)

UINT32 OnRibbonQueryItemCategory(ID, UINT32 uItem)

Current category index is UI_COLLECTION_INVALIDINDEX

returns 0

Unlike the single controls the collection controls have a uItems, and an optionnal uCategories template parameter.

CRibbonItemGalleryCtrl

  • Declaration in CRibbonImpl derived class: CRibbonItemGalleryCtrl<wID, uItems, uCategories = 0> m_Ctrl: uItems is the maximum number of items in the gallery, uCategory the number of categories.

  • User action message map macro: RIBBON_GALLERY_CONTROL_HANDLER(wID, OnRibbonGalleryCtrl)

  • Handler prototype: LRESULT OnRibbonGalleryCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled). If uSel is UI_COLLECTION_INVALIDINDEX the Button part of a SplitButton Gallery has been clicked.

CRibbonImpl::CRibbonItemGalleryCtrl operates on Ribbon Drop-Down Gallery, In-Ribbon Gallery, Split Button Gallery which do not have the Type = "Commands" property.

Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_SelectedItem

Select(UINT uItem, bool bUpdate = false)

bool OnRibbonQuerySelectedItem(ID, UINT32& uSel)

Current selection is UI_COLLECTION_INVALIDINDEX

Returns false (no change)

Item Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_Label

SetItemText(UINT uItem, LPCWSTR sText, bool bUpdate = false)

LPCWSTR OnRibbonQueryItemText(ID, UINT32 uItem)

Current string is empty

Returns resource string with ID + 1 + uItem (or NULL)

UI_PKEY_ItemImage

SetItemImage(UINT uIndex, HBITMAP hbm, bool bUpdate = false)

HBITMAP OnRibbonQueryItemImage(ID, UINT32 uItem)

Current HBITMAP is NULL

HBITMAP of the ressource bitmap with ID + 1 + uItem (or NULL)

Example : add and handle a Item InRibbonGallery Control

In the markup file declare new Tab, Group and ID_TEST_GALLERY Command ID and declare a InRibbonGallery in a Group:

    <Command Name="Gallery" Symbol="ID_TEST_GALLERY" 
             Id="200" 
             LabelTitle ="Shapes" />
    
    <!-- Tabs -->
    <Command Name="TabGalleries" Symbol="ID_TAB_GALLERIES"
             LabelTitle="Galleries" />

    <!-- Groups -->
    <Command Name="GroupGallery" Symbol="ID_GROUP_GALLERY"
             LabelTitle="Galleries" />

      </Ribbon.Tabs>
...
        <Tab CommandName="TabGalleries">
            <Group CommandName="GroupGallery"  SizeDefinition="OneInRibbonGallery">
              <InRibbonGallery CommandName="Gallery" 
                              ItemHeight="32" ItemWidth="32" 
                              MinColumnsLarge="2"
                              TextPosition="Hide" />
            </Group>
        </Tab>
      </Ribbon.Tabs>

Create the IDs for the Gallery items following ID_TEST_GALLERY in resource.h to enable default loading of the item bitmaps.

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by FirstRibbon.rc
// ...
#define ID_DIAMOND                      201 // == ID_TEST_GALLERY + 1
#define ID_RECT                         202
#define ID_ROUNDRECT                    203
#define ID_ELLIPSE                      204

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        205

Extract the 4 shape bitmaps from FirstRibbonBitmaps.zip to FirstRibbon\res, add the bitmaps and their title strings to FirstRibbon.rc with the proper IDs.

// Bitmap
//

ID_DIAMOND              BITMAP                  "res\\DIAMOND.BMP"
ID_RECT                 BITMAP                  "res\\RECTANGLE.BMP"
ID_ROUNDRECT            BITMAP                  "res\\ROUNDEDRECTANGLE.BMP"
ID_ELLIPSE              BITMAP                  "res\\ELLIPSE.BMP"

STRINGTABLE 
BEGIN
    ID_DIAMOND              "Diamond"
    ID_RECT                 "Rectangle"
    ID_ROUNDRECT            "Round Rectangle"
    ID_ELLIPSE              "Ellipse"
END

In CMainFrame declare a CRibbonItemGalleryCtrl with ID_TEST_GALLERY, 4 items, 2 categories and insert it in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonItemGalleryCtrl<ID_TEST_GALLERY, 4, 2> m_shapes;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_shapes)

The default initial values for CRibbonImpl:: CRibbonItemGalleryCtrl are 'Category' for category names, all items belong to Category 0, no selection.

Initialize m_shapes in CMainFrame::OnCreate() member:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, 
    BOOL& /*bHandled*/)
{
    // ...
    // remove old menu
    SetMenu(NULL);

    bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable(); // 3
    if (bRibbonUI) // 3
    {
        UIAddMenu(m_CmdBar.GetMenu(), true); // 3

        m_shapes.SetCategoryText(0, L"Angles");
        m_shapes.SetCategoryText(1, L"Rounded");
        m_shapes.Select(1);

        for (UINT uItem = 0; uItem < 4; uItem++)
            m_shapes.SetItemCategory(uItem, uItem > 1);
    // ...

Or alternatively in a specialized constructor:

CMainFrame::CRibbonItemGalleryCtrl<ID_TEST_GALLERY, 4, 2>::CRibbonItemGalleryCtrl()
{
    SetCategoryText(0, L"Angles");
    SetCategoryText(1, L"Curves");
    Select(1);
        
    for (UINT uItem = 0; uItem < 4; uItem++)
        SetItemCategory(uItem, uItem > 1);
}

When the user selects or hovers an item in the Gallery CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to the UI_EXECUTIONVERB, LOWORD(wParam) set to the Gallery ID, and lParam set to the selected item.

Handle this message in CMainFrame with the RIBBON_GALLERY_CONTROL_HANDLER macro.

BEGIN_MSG_MAP(CMainFrame)
    RIBBON_GALLERY_CONTROL_HANDLER(ID_TEST_GALLERY, OnGalleryShape)
// ...
LRESULT OnGalleryShape(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)
{
    UISetText(ID_DEFAULT_PANE, CString(MAKEINTRESOURCE(wID + uSel + 1)));

    return 0;
}

Image 17 Image 18

Example : handle a DropDownGallery Control

In the markup file change the InRibbonGallery to a DropDownGallery with a change in the Group SizeDefinition:

<Tab CommandName="TabGalleries">
    <Group CommandName="GroupGallery"  SizeDefinition="OneButton">
      <DropDownGallery CommandName="Gallery"
                      ItemHeight="32" ItemWidth="32"
                      TextPosition="Hide" />
    </Group>
  </Tab>

In the initialization code, set the Button Image to the initially selected shape:

CMainFrame::CRibbonItemGalleryCtrl<ID_TEST_GALLERY, 4, 2>::CRibbonItemGalleryCtrl()
{
    SetCategoryText(0, L"Angles");
    SetCategoryText(1, L"Curves");
    Select(1);
        
    SetImage(UI_PKEY_LargeImage, AtlLoadBitmapImage(ID_RECT, LR_CREATEDIBSECTION));

    for (UINT uItem = 0; uItem < 4; uItem++)
        SetItemCategory(uItem, uItem > 1);
}

To update the Button Image to the currently selected shape:

LRESULT OnGalleryShape(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)
{
    UISetText(ID_DEFAULT_PANE, OnRibbonQueryItemText(wID, uSel));

    if (verb == UI_EXECUTIONVERB_EXECUTE)
        m_shapes.SetImage(UI_PKEY_LargeImage, OnRibbonQueryItemImage(wID, uSel), true);
    
    return 0;
}

Image 19

Example : handle a SplitButtonGallery Control

In the markup file change the DropDownGallery to a SplitButtonGallery:

<Tab CommandName="TabGalleries">
<Group CommandName="GroupGallery"  SizeDefinition="OneButton">
     <SplitButtonGallery CommandName="Gallery"
                    ItemHeight="32" ItemWidth="32"
                    TextPosition="Hide" />
  </Group>

When a CRibbonItemGalleryCtrl is attached to a SplitButtonGallery, if the user clicks the main Button part, CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to UI_EXECUTIONVERB_EXECUTE, LOWORD(wParam) set to the Gallery ID, and lParam set to UI_COLLECTION_INVALIDINDEX.

CMainFrame::OnGalleryShape() must handle this case:

LRESULT OnGalleryShape(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)
{
    if (uSel == UI_COLLECTION_INVALIDINDEX)
        uSel = m_shapes.GetSelected();

    ATLASSERT(uSel != UI_COLLECTION_INVALIDINDEX);

    UISetText(ID_DEFAULT_PANE, OnRibbonQueryItemText(wID, uSel));

    if (verb == UI_EXECUTIONVERB_EXECUTE)
        m_shapes.SetImage(UI_PKEY_LargeImage, OnRibbonQueryItemImage(wID, uSel), true);
    
    return 0;
}

CRibbonCommandGalleryCtrl

  • Declaration in CRibbonImpl derived class: CRibbonCommandGalleryCtrl<wID, uItems, uCategories = 0> m_Ctrl: uItems is the maximum number of items in the gallery, uCategories the number of categories.

  • User action message map macro: COMMAND_ID_HANDLER(nCmdID, OnCommand)for the Gallery items IDs.

  • Handler prototype: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled);

CRibbonImpl::CRibbonItemGalleryCtrl operates on Ribbon Drop-Down Gallery, In-Ribbon Gallery, Split Button Gallery which have the Type = "Commands" property.

Item Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_CommandId

SetItemCommand(uItem, ID, bool bUpdate = false)

UINT32 OnRibbonQueryItemCommand(ID, uItem)

Current CommandId is NULL

returns ID + 1 + uItem

UI_PKEY_CommandType

SetItemCommandType(uItem, UI_COMMANDTYPE, bool bUpdate = false)

UI_COMMANDTYPE OnRibbonQueryItemCommandType(ID, uItem)

Current CommandType is UI_COMMANDTYPE_UNKNOWN

returns UI_COMMANDTYPE_ACTION

Example : Implement a Command InRibbonGallery Control

In the markup file declare the SplitButtonGallery as a Command Gallery:

</Tab>
<Tab CommandName="TabGalleries">
  <Group CommandName="GroupGallery"  SizeDefinition="OneInRibbonGallery">
    <InRibbonGallery CommandName="Gallery"
                   Type = "Commands"
                   MinColumnsLarge="4"
                   ItemHeight="32" ItemWidth="32"
                   TextPosition="Hide" />
  </Group>
</Tab>

CRibbonImpl::CRibbonCommandGalleryCtrl default initialization sets the command IDs to GalleryID + 1 + index which are already inserted in FirstRibbon.h.

In FirstRibbon.rc insert a Menu ressource with ID_TEST_GALLERY, and change the shapes string ressource:

ID_TEST_GALLERY MENU 
BEGIN
    MENUITEM "Diamond",                     ID_DIAMOND
    MENUITEM "Rectangle",                   ID_RECT
    MENUITEM "Round Rectangle",             ID_ROUNDRECT
    MENUITEM "Ellipse",                     ID_ELLIPSE
END

STRINGTABLE 
BEGIN
    ID_DIAMOND              "Angled shape\nDiamond"
    ID_RECT                 "Angled shape\nRectangle"
    ID_ROUNDRECT            "Curved shape\nRound Rectangle"
    ID_ELLIPSE              "Curved shape\nEllipse"
END

In CMainFrame declare a CRibbonCommandGalleryCtrl and insert it in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonCommandGalleryCtrl<ID_TEST_GALLERY, 4, 2> m_cmdshapes;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_cmdshapes)

m_cmdShapes requires the same category initialization that m_shapes (here in CMainFrame::OnCreate(), could be in a specialized constructor), SetItemCommandType(1, UI_COMMANDTYPE_BOOLEAN) sets the item at index 1 (ID_RECT) as a Ribbon ToggleButton:

if (bRibbonUI) // 3
{
    // ...
    UIAddMenu(ID_TEST_GALLERY);
    m_cmdshapes.SetItemCommandType(1, UI_COMMANDTYPE_BOOLEAN);

    m_cmdshapes.SetCategoryText(0, L"Angles");
    m_cmdshapes.SetCategoryText(1, L"Curves");

    for (UINT uItem = 0; uItem < 4; uItem++)
        m_cmdshapes.SetItemCategory(uItem, uItem > 1);

A CRibbonCommandGalleryCtrl Posts WM_COMMAND messages with the ID of the clicked item: handle them with a COMMAND_RANGE_HANDLER macro:

BEGIN_MSG_MAP(CMainFrame)
    COMMAND_RANGE_HANDLER(ID_DIAMOND, ID_ELLIPSE, OnCommandShape)
// ...
LRESULT OnCommandShape(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, 
    BOOL& /*bHandled*/)
{
    UISetText(ID_DEFAULT_PANE, CString(MAKEINTRESOURCE(wID)));

    return 0;
}

Image 20 Image 21

Simple Galleries classes

CRibbonToolbarGalleryCtrl

CRibbonToolBarGalleryCtrl is a Ribbon Command Gallery built from a Toolbar Resource. There is no category handling and no callback to the application.

  • Declaration in CRibbonImpl derived class: CRibbonToolBarGalleryCtrl<ID, IDToolBar, uItems> m_Ctrl: uItems is the number of items in the gallery.

  • User action message map macro: COMMAND_ID_HANDLER(nCmdID, OnCommand) for the toolbar Resource IDs.

  • Handler prototype: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled);

CRibbonImpl::CRibbonItemGalleryCtrl operates on Ribbon Drop-Down Gallery, In-Ribbon Gallery, Split Button Gallery which have the Type = "Commands" property.

Item Key

Processing

UI_PKEY_CategoryId

returns UI_COLLECTION_INVALIDINDEX

UI_PKEY_CommandId

Returns IDToolBar resource command ID at uItem position

UI_PKEY_CommandType

Returns UI_COMMANDTYPE_ACTION

Example : Implement a InRibbonGallery CRibbonToolbarGalleryCtrl.

In the markup file:

  • Declare a Ribbon Command named ToolbarGallery with ID_RIBBON_TOOLBAR Id,

  • Add it to the TabGalleries Tab as a InRibbonGallery with Commands Type.

<!--Controls-->
<Command Name="ToolbarGallery" Symbol="ID_RIBBON_TOOLBAR" 
         LabelTitle ="Toolbar Gallery" />

<Tab CommandName="TabGalleries">
  <Group CommandName="GroupGallery"  SizeDefinition="OneInRibbonGallery">
      <InRibbonGallery CommandName="ToolbarGallery"
                     Type = "Commands"
                     MaxRows = "3"
                     MinColumnsLarge="3"
            ItemHeight="15" ItemWidth="16" 
                     HasLargeItems = "false"
                     TextPosition="Hide" >
          <InRibbonGallery.MenuLayout>
              <FlowMenuLayout
                    Columns = "4"
                    Rows = "2"/>
          </InRibbonGallery.MenuLayout>
      </InRibbonGallery> 
  </Group>
</Tab>

In CMainFrame declare a CRibbonToolbarGalleryCtrl using the IDR_MAINFRAME toolbar ressource, with 7 elements (the Ribbon won't insert ID_APP_ABOUT in a Command Gallery as it conflicts with the Ribbon.HelpButton setting), and insert it in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonToolbarGalleryCtrl<ID_RIBBON_TOOLBAR, IDR_MAINFRAME, 7> m_toolbar;
CRibbonCommandGalleryCtrl<ID_TEST_GALLERY, 4, 2> m_cmdshapes;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_toolbar)
    RIBBON_CONTROL(m_cmdshapes)

m_toolbar requires no initialization and the included commands are already handled so compile and run.

Image 22

Example : Implement a DropDownGallery CRibbonToolbarGalleryCtrl.

In the markup file change the TabGalleries Tab SizeDefinition to accomodate both galleries:

  • The previous InRibbonGallery Gallery

  • The ToolbarGallery as a DropDownGallery.

<Tab CommandName="TabGalleries">
  <Group CommandName="GroupGallery"  SizeDefinition="InRibbonGalleryAndBigButton">
     <InRibbonGallery CommandName="Gallery"
                    Type = "Commands"
                    MinColumnsLarge="4"
                    ItemHeight="32" ItemWidth="32" 
                    TextPosition="Hide" />
    <DropDownGallery CommandName="ToolbarGallery"
                    Type = "Commands"
                    HasLargeItems = "false"
                    TextPosition="Hide" >
      <DropDownGallery.MenuLayout>
        <FlowMenuLayout Columns = "4"/>
      </DropDownGallery.MenuLayout>
    </DropDownGallery>
    </Group>
</Tab>

Extract Tools.bmp from FirstRibbonBitmaps.zip to FirstRibbon\res and add it to FirstRibbon.rc with ID_RIBBON_TOOLBAR Id.

ID_RIBBON_TOOLBAR       BITMAP                  "res\\Tools.bmp"

Image 23

CRibbonSimpleGalleryCtrl

CRibbonSimpleGalleryCtrl is a Ribbon (Command or Item) Gallery built from a set of resources. There is no category handling and no callback to the application.

  • Declaration in CRibbonImpl derived class: CRibbonSimpleGalleryCtrl<ID, uItems, UI_COMMANDTYPE type = UI_COMMANDTYPE_ACTION> m_Ctrl. uItems is the number of items in the gallery.

  • User action message map macros:

  • RIBBON_GALLERY_CONTROL_HANDLER(ID, OnRibbonGalleryCtrl) for Item Galleries

  • COMMAND_ID_HANDLER(nCmdID, OnCommand) for Command Galleries.

  • Handler prototypes:

  • LRESULT OnRibbonGalleryCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled);

  • LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled);

CRibbonImpl::CSimpleGalleryCtrl operates on Ribbon Drop-Down Gallery, In-Ribbon Gallery, Split Button Gallery.

Key

Setting

Processing

UI_PKEY_SelectedItem

Select(UINT uItem, bool bUpdate = false)

Returns 0 or last selected item

Item Key

Processing

UI_PKEY_CategoryId

returns UI_COLLECTION_INVALIDINDEX

UI_PKEY_Label

Returns string ressource with ID + 1 + uItem (or NULL)

UI_PKEY_ItemImage

HBITMAP of the ressource bitmap with ID + 1 + uItem (or NULL)

UI_PKEY_CommandId

Returns ID + 1 + uItem

UI_PKEY_CommandType

Returns t_CommandType

Example : Implement a Command CRibbonSimpleGalleryCtrl.

In CMainFrame declare a CRibbonSimpleGalleryCtrl with ID_TEST_GALLERY Id, and 4 items and insert it in the RIBBON_CONTROL_MAP (remove m_cmdshapes from the map):

// Ribbon controls
CRibbonSimpleGalleryCtrl<ID_TEST_GALLERY, 4> m_simpleshapes;
CRibbonToolbarGalleryCtrl<ID_RIBBON_TOOLBAR, IDR_MAINFRAME, 7> m_toolbar;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_simpleshapes)
    RIBBON_CONTROL(m_toolbar)
    //RIBBON_CONTROL(m_cmdshapes)

m_simpleshapes is already declared in the markup file as a Command Gallery, just compile and run.

Image 24

Example : Implement a Item CRibbonSimpleGalleryCtrl.

In the markup file remove the Type = "Commands" line in the Gallery declaration.

<Tab CommandName="TabGalleries">
  <Group CommandName="GroupGallery"  SizeDefinition="InRibbonGalleryAndBigButton">
     <InRibbonGallery CommandName="Gallery"
                    MinColumnsLarge="4"
                    ItemHeight="32" ItemWidth="32"
                    TextPosition="Hide" />
    <DropDownGallery CommandName="ToolbarGallery"
                    Type = "Commands"
                    HasLargeItems = "false"
                    TextPosition="Hide" >
      <DropDownGallery.MenuLayout>
        <FlowMenuLayout Columns = "4"/>
      </DropDownGallery.MenuLayout>
    </DropDownGallery>
    </Group>
</Tab>

In FirstRibbon.rc revert the strings to what they were for a shapes Item Gallery:

STRINGTABLE 
BEGIN
    ID_DIAMOND              "Diamond"
    ID_RECT                 "Rectangle"
    ID_ROUNDRECT            "Round Rectangle"
    ID_ELLIPSE              "Ellipse"
END

Image 25

CRibbonComboCtrl

  • Declaration in CRibbonImpl derived class: CRibbonComboCtrl<wID, uItems, uCategories = 0> m_Ctrl: uItems is the maximum number of items in the gallery, uCategories the number of categories.

  • User action message map macro: RIBBON_COMBO_CONTROL_HANDLER(wID, OnRibbonComboCtrl)

  • Handler prototype: LRESULT OnRibbonComboCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)

CRibbonImpl::CRibbonComboCtrl operates on Ribbon Combo Box Controls, handles the keys of CRibbonItemGalleryCtrl and:

Key

Getting

Setting

UI_PKEY_StringValue

LPCWSTR GetComboText()

SetComboText(LPCWSTR sText)

Example : add and handle a Combo Control

In the markup file declare Tab, Group and Command IDs and declare a ComboBox in a Group:

    <Command Name="Combo" Symbol="ID_TEST_COMBO" 
             LabelTitle ="Combo" />
    
    <!-- Tabs -->
    <Command Name="TabGalleries" Symbol="ID_TAB_GALLERIES"
             LabelTitle="Galleries" />

    <!-- Groups -->
    <Command Name="GroupCombo" Symbol="ID_GROUP_COMBO"
             LabelTitle="Combos" />

        <Tab CommandName="TabGalleries">
...
            <Group CommandName="GroupCombo">
                <ComboBox CommandName="Combo" />
            </Group>
        </Tab>
      </Ribbon.Tabs>

In CMainFrame declare a CRibbonComboCtrl with ID_TEST_COMBO ID, 6 items, 2 categories and insert it in the RIBBON_CONTROL_MAP.

// Ribbon controls
CRibbonComboCtrl<ID_TEST_COMBO, 6, 2> m_combo;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_combo)

The default initial values for CRibbonImpl::CRibbonComboCtrl are Category' for category names, all items belong to Category 0, no selection.

To initialize m_combo override the relevant CRibbonImpl::OnRibbonQueryxxx() members:

// Ribbon controls queries handling
LPCWSTR OnRibbonQueryCategoryText(UINT32 uCtrlID, UINT32 uCat)
{
    if (uCtrlID == ID_TEST_COMBO)
        return uCat ? L"End" : L"Begin";
    return L"Category";
}

UINT32 OnRibbonQueryItemCategory(UINT32 uCtrlID, UINT32 uItem)
{
    if (uCtrlID == ID_TEST_COMBO)
        return uItem > 3;
    return 0;
}

LPCWSTR OnRibbonQueryItemText(UINT32 uCtrlID, UINT32 uItem)
{
    if (uCtrlID == ID_TEST_COMBO)
    {
        static LPCWSTR text[] = 
        {
            L"First", L"Second", L"Third", L"Fourth", L"Fifth", L"Last"
        };
        return text[uItem];
    }
    return L"Item";
}

bool OnRibbonQuerySelectedItem(UINT32 uCtrlID, UINT32& uSel)
{
    if (uCtrlID == ID_TEST_COMBO)
    {
        uSel = 1;
        return true;
    }
    return false;
}

You can as well initialize m_combo in CMainFrame::OnCreate() as shown earlier.

When the user selects or hovers an item in the Combo CRibbonImpl posts a WM_COMMAND message with HIWORD(wParam) set to the UI_EXECUTIONVERB, LOWORD(wParam) set to the Combo ID, and lParam set to the selected item.

Handle this message in CMainFrame with the RIBBON_COMBO_CONTROL_HANDLER macro.

BEGIN_MSG_MAP(CMainFrame)
    RIBBON_COMBO_CONTROL_HANDLER(ID_TEST_COMBO, OnCombo)
//...
LRESULT OnCombo(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)
{
    static LPCWSTR text[] = 
    {
        L"First", L"Second", L"Third", L"Fourth", L"Fifth", L"Last"
    };
    UISetText(ID_DEFAULT_PANE, text[uSel]);
    return 0;
}

Image 26

CRibbonSimpleComboCtrl

CRibbonSimpleComboCtrl is a Ribbon Combo Box built from a set of resources. There is no category handling and no callback to the application.

  • Declaration in CRibbonImpl derived class: CRibbonSimpleComboCtrl<ID, uItems> m_Ctrl. uItems is the number of items in the combo.

  • User action message map macro: RIBBON_COMBO_CONTROL_HANDLER(ID, OnRibbonComboCtrl).

  • Handler prototype: LRESULT OnRibbonComboCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled);

  • CRibbonImpl::CRibbonSimpleComboCtrl operates on Ribbon Combo Box Controls.

Example : handle a CRibbonSimpleComboCtrl Combo Control

In the Markup file declare a Command with ID_RIBBON_SIMPLECOMBO, and insert it in the Galleries Tab:

<Command Name="SimpleCombo" Symbol="ID_RIBBON_SIMPLECOMBO" 
             Id="300" 
             LabelTitle ="Simple Combo" />

<Group CommandName="GroupCombo">
    <ComboBox CommandName="SimpleCombo" />
</Group>

In FirstRibbon.h, declare some identifiers following ID_TEST_COMBO.

#define ID_COMBO1                       301
#define ID_COMBO2                       302
#define ID_COMBO3                       303
#define ID_COMBO4                       304
#define ID_COMBO5                       305

In FirstRibbon.rc add matching string ressources.

STRINGTABLE 
BEGIN
    ID_COMBO1                "Simple 1"
    ID_COMBO2                "Simple 2"
    ID_COMBO3                "Simple 3"
    ID_COMBO4                "Simple 4"
    ID_COMBO5                "Simple 5"
END

In CMainFrame declare a CRibbonSimpleComboCtrl with 5 Items and same Id, and insert it in the RIBBON_CONTROL_MAP, reuse the OnGalleryShape() handler to handle the selection messages with the RIBBON_COMBO_CONTROL_HANDLER macro.

// Ribbon controls
CRibbonSimpleComboCtrl<ID_RIBBON_SIMPLECOMBO, 5> m_simplecombo;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_simplecombo)

BEGIN_MSG_MAP(CMainFrame)
    RIBBON_COMBO_CONTROL_HANDLER(ID_RIBBON_SIMPLECOMBO, OnGalleryShape)

Image 27


CRibbonRecentItemsCtrl

  • Declaration in CRibbonImpl derived class: CRibbonRecentItemsCtrl<ID, class TDocList = CRecentDocumentList> m_Ctrl. TDocList is any class deriving from WTL::CRecentDocumentListBase.

CRibbonImpl:: CRibbonRecentItemsCtrl operates on the Ribbon Recent Items Control.

Item Key

Setting

Overrideable Callback

Condition for callback

Default processing

UI_PKEY_LabelDescription

TDocList members

returns TDocList::m_arrDocs[uItem].szDocName

UI_PKEY_Label

LPCWSTR OnRibbonQueryRecentItemName(LPCWSTR sPath)

always

returns ::PathFindFileName(sPath)

Example : Add and handle a Ribbon Recent Items

In the Markup file declare a RecentFiles Command and insert a RecentItems using it in the Application.Menu section.

    <!--Controls-->
    <Command Name="RecentFiles" Symbol="ID_RIBBON_RECENT_FILES"
            LabelTitle="Recent Files" />
...
      <!-- Application Menu -->
      <Ribbon.ApplicationMenu >
        <ApplicationMenu CommandName="AppMenu" >
            <ApplicationMenu.RecentItems>
                <RecentItems CommandName="RecentFiles" MaxCount="16" 
                    EnablePinning="false"/>
            </ApplicationMenu.RecentItems>

In CMainFrame add

// Ribbon controls
CRibbonRecentItemsCtrl<ID_RIBBON_RECENT_FILES> m_recent;

// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_recent)

The RecentItems Ribbon Control is now operated through the WTL::CRecentDocumentList m_recent member.

Add a message map entry for ID_FILE_OPEN and a matching CMainFrame::OnFileOpen() member.

BEGIN_MSG_MAP(CMainFrame)
    COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
...

LRESULT OnFileOpen(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, 
    BOOL& /*bHandled*/)
{
    CFileDialog dlg(TRUE);
    if (dlg.DoModal() == IDOK)
        m_recent.AddToList(dlg.m_ofn.lpstrFile);

    return 0;
}

Compile, run and 'open' some files.

Image 28


Other Ribbon features handling

Ribbon Application modes

CRibbonImpl member: HRESULT SetRibbonModes(INT32 iModes);

Ribbon context menu

CRibbonImpl members:

  • bool HasRibbonMenu(UINT32 uID) returns true if uID is a Ribbon Context Popup ID.

  • HRESULT TrackRibbonMenu(UINT32 uID, INT32 x, INT32 y) pops the Context Menu at (x, y) screen coordinates.

  • HRESULT TrackRibbonMenu(UINT32 uID, LPARAM lParam) extracts the (x, y) coordinates from a WM_CONTEXTMENU lParam and calls TrackRibbonMenu(uID, x, y).

Example: Implement a Ribbon Context Menu

In the Markup file declare ContextMap, ContextMenu and MiniToolbar Commands, and insert a ContextPopup description in the Application.Views section.

<Command Name="MiniToolbar" Symbol="ID_MINITOOLBAR"/>
<Command Name="ContextMenu" Symbol="ID_CONTEXTMENU"/>
<Command Name="ContextMap" Symbol="ID_CONTEXTMAP" />
...
      </Ribbon.Tabs>
    </Ribbon>
    
    <ContextPopup>
        <ContextPopup.MiniToolbars>
            <MiniToolbar Name="MiniToolbar">
               <MenuGroup>
                    <Button CommandName="wtl_FILE_OPEN" />
                    <Button CommandName="wtl_FILE_NEW" />
                    <Button CommandName="wtl_FILE_SAVE" />
                </MenuGroup>
            </MiniToolbar>
        </ContextPopup.MiniToolbars>
        <ContextPopup.ContextMenus>
            <ContextMenu Name="ContextMenu">
                <MenuGroup>
                    <Button CommandName="wtl_EDIT_UNDO" />
                </MenuGroup>
                <MenuGroup>
                    <Button CommandName="wtl_EDIT_CUT" />
                    <Button CommandName="wtl_EDIT_COPY" />
                    <Button CommandName="wtl_EDIT_PASTE" />
                </MenuGroup>
            </ContextMenu>
        </ContextPopup.ContextMenus>
        <ContextPopup.ContextMaps>
            <ContextMap CommandName="ContextMap"
                        ContextMenu="ContextMenu"
                        MiniToolbar="MiniToolbar"/>
        </ContextPopup.ContextMaps>
    </ContextPopup>
  </Application.Views>

In CMainFrame add a WM_CONTEXTMENU handler, and

BEGIN_MSG_MAP(CMainFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)

// ...
LRESULT OnContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    if (IsRibbonUI())
        TrackRibbonMenu(ID_CONTEXTMAP, lParam);
    return 0;
}

Image 29

Ribbon contextual Tabs

Key

Getting

Setting

UI_PKEY_ContextAvailable

UI_CONTEXTAVAILABILITY GetRibbonContextAvail(ID)

SetRibbonContextAvail(ID, UI_CONTEXTAVAILABILITY cav)

Example : Add and handle a Ribbon contextual Tab

<!-- Tabs -->
<Command Name="TabContext" Symbol="ID_TAB_CONTEXT"
     LabelTitle="Context" />
...
<!-- Groups -->
<Command Name="GroupTabContext" Symbol="ID_GROUP_CONTEXT"
         LabelTitle='Context Tabs'/>
...
<Ribbon.ContextualTabs>
    <TabGroup CommandName="GroupTabContext">
        <Tab CommandName="TabContext">
            <Group CommandName="GroupFont" SizeDefinition="OneFontControl">
                <FontControl CommandName="TestFont"
                             FontType="FontWithColor"
                             ShowTrueTypeOnly="false"
                             ShowVerticalFonts="false"/>
            </Group>
        </Tab>
    </TabGroup>
</Ribbon.ContextualTabs>  

In CMainFrame::OnIdle()

virtual BOOL OnIdle()
{
    // ...        
    SetRibbonContextAvail(ID_GROUP_CONTEXT, m_view.HasSelection() ? 
        UI_CONTEXTAVAILABILITY_ACTIVE :
        UI_CONTEXTAVAILABILITY_NOTAVAILABLE);

}

Image 30

Ribbon colors

Key

Getting

Setting

UI_PKEY_GlobalBackgroundColor UI_PKEY_GlobalHighlightColor UI_PKEY_GlobalTextColor

UI_HSBCOLOR GetRibbonColor(key)

bool SetRibbonColor(key, UI_HSBCOLOR color)

Example : Handle Ribbon colors

In CMainFrame::OnFileNew()

LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI() && 
        (GetRibbonColor(UI_PKEY_GlobalBackgroundColor) != UI_HSB(0x14, 0x38, 0x54)))
    {
        SetRibbonColor(UI_PKEY_GlobalBackgroundColor, UI_HSB(0x14, 0x38, 0x54));
        SetRibbonColor(UI_PKEY_GlobalHighlightColor, UI_HSB(0x00, 0x36, 0x87));
        SetRibbonColor(UI_PKEY_GlobalTextColor, UI_HSB(0x2B, 0xD6, 0x00));
    }
    return 0;
}

Image 31

QAT (QuickAccessToolbar) docking

Key

Getting

Setting

UI_PKEY_QuickAccessToolbarDock

UI_CONTROLDOCK GetQATDock()

bool SetQATDock(UI_CONTROLDOCK dockState)

Example : Handle QuickAccessToolbar docking

In CMainFrame::OnFileNew()

LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI())
    {
        if (GetQATDock() == UI_CONTROLDOCK_TOP)
            SetQATDock(UI_CONTROLDOCK_BOTTOM);
        else 
            SetQATDock(UI_CONTROLDOCK_TOP);
    }
    return 0;
}

Image 32

Ribbon visibility and size

Key

Getting

Setting

UI_PKEY_Viewable

bool IsRibbonHidden()

HideRibbon(bool bHide = true)

UI_PKEY_Minimized

bool IsRibbonMinimized()

MinimizeRibbon(bool bMinimize = true)

Example : Handle Ribbon size

In CMainFrame::OnFileNew()

LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI())
    {
        MinimizeRibbon(!IsRibbonMinimized());
    }
    return 0;
}

Image 33


Ribbon persistency

When switching from Ribbon to traditional UI:

  • All CRibbonImpl controls are not affected. The UpdateUI 'ribbon only' IDs are destroyed. To keep them, their text, enabled state and checked state call UIPersistElement(ID) before Ribbon destruction.

  • HRESULT <code>CRibbonImpl::SaveRibbonSettings() saves the Ribbon Settings (Ribbon size and colors, QAT position and content) in a <code><code>HGLOBAL m_hgRibbonSettings memory zone.

When switching from traditional to Ribbon UI:

  • If m_hgRibbonSettings is not NULL, the Ribbon Settings were persisted and they are restored by HRESULT CRibbonImpl::RestoreRibbonSettings().

  • The newly created Ribbon calls CRibbonImpl::UpdateProperty() on each CommandId with the relevant Property Keys. CRibbonImpl returns the stored values and the Ribbon is restored as it was.

For Ribbon Settings persistency between sessions, the helper CRibbonPersist class exposes members to save and restore the Ribbon Settings to a Registry key under HKCU:

  • CRibbonPersist(LPCWSTR sAppKey) constructs a CRibbonPersist object from a registry key name

  • LONG CRibbonPersist::Save(bool bRibbonUI, HGLOBAL hgSettings = NULL) saves a bool and a HGLOBAL to the registry.

  • LONG CRibbonPersist::Restore(bool& bRibbonUI, HGLOBAL& hgSettings) restores the saved bool and HGLOBAL.

MTPad7 uses them in CMainFrame::OnCreate() to reopen in the last UI with the last Ribbon Settings ...

// Ribbon UI state and settings restoration
CRibbonPersist(lpcstrMTPadRegKey).Restore(bRibbonUI, m_hgRibbonSettings);

... which were saved by CMainFrame::OnClose().

bool bRibbonUI = IsRibbonUI();
if (bRibbonUI)
   SaveRibbonSettings();
CRibbonPersist(lpcstrMTPadRegKey).Save(bRibbonUI, m_hgRibbonSettings);

Relooking MTPad with a Ribbon UI

MTPad is one of the first WTL samples, it is extensively described in this article. The relooking changes no existing functionality. The Ribbon Spinner adds a direct page selection in Preview mode.

The project settings are changed as in First Experience step 1 and 2.

7 bitmaps are added to MTPad.rc.

CMainFrame changes

Derive from CRibbonFrameWindowImpl.

class CMainFrame : 
        public CRibbonFrameWindowImpl<CMainFrame>
#ifndef _WIN32_WCE
        , public CPrintJobInfo
#endif // _WIN32_WCE
{

Add the ribbon controls declaration and control map.

// Ribbon Controls and map
CRibbonRecentItemsCtrl<ID_RIBBON_RECENT_FILES> m_mru;
CRibbonFontCtrl<ID_RIBBON_FONT> m_font;
CRibbonSpinnerCtrl<ID_PAGE_SPINNER> m_spinner;
CRibbonCommandCtrl<ID_GROUP_FONT> m_groupFont;

BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_font)
    RIBBON_CONTROL(m_spinner)
    RIBBON_CONTROL(m_groupFont)
    RIBBON_CONTROL(m_mru)
END_RIBBON_CONTROL_MAP()

Handle the Ribbon queries.

// Ribbon queries
DWORD OnRibbonQueryFont(UINT /*nId*/, CHARFORMAT2& cf)
{
    return m_view.GetDefaultCharFormat(cf);
}

bool OnRibbonQuerySpinnerValue(UINT /*nCmdID*/, REFPROPERTYKEY key, LONG* pVal)
{
    if (key == UI_PKEY_DecimalValue)
    {
        *pVal = prev.m_nCurPage + 1;
        return true;
    }
    return false;
}

Handle the Ribbon commands.

BEGIN_MSG_MAP(CMainFrame)
// ...
    RIBBON_SPINNER_CONTROL_HANDLER(ID_PAGE_SPINNER, OnSpinnerPage)
    COMMAND_ID_HANDLER(ID_VIEW_RIBBON, OnViewRibbon)

Initialize the UI settings, Ribbon Controls and restore the Ribbon Settings in OnCreate().

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
#ifndef _WIN32_WCE
        // create command bar window
        HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE);
        // atach menu
        m_CmdBar.AttachMenu(GetMenu());
        // load command bar images
        m_CmdBar.LoadImages(IDR_MAINFRAME);
        // remove old menu
        SetMenu(NULL);

    bool bRibbonUI = RunTimeHelper::IsRibbonUIAvailable();
    if (bRibbonUI) 
    {
        // UI Setup and adjustments
        UIAddMenu(m_CmdBar.GetMenu(), true);
        UIRemoveUpdateElement(ID_FILE_MRU_FIRST);
        UIPersistElement(ID_GROUP_PAGE);

        // Ribbon Controls initialization
        m_groupFont.SetImage(UI_PKEY_SmallImage, GetCommandBarBitmap(ID_FORMAT_FONT));
        m_spinner.SetValue(UI_PKEY_MinValue, 1);

        // Ribbon UI state and settings restoration
        CRibbonPersist(lpcstrMTPadRegKey).Restore(bRibbonUI, m_hgRibbonSettings);    }
    else 
        CMenuHandle(m_CmdBar.GetMenu()).DeleteMenu(ID_VIEW_RIBBON, MF_BYCOMMAND); 

// ...

    ShowRibbonUI(bRibbonUI); 
    UISetCheck(ID_VIEW_RIBBON, bRibbonUI);
    
        return 0;
}

Save the Ribbon Settings in OnClose().

LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
    if (RunTimeHelper::IsRibbonUIAvailable())
    {
        bool bRibbonUI = IsRibbonUI();
        if (bRibbonUI)
            SaveRibbonSettings();
        CRibbonPersist(lpcstrMTPadRegKey).Save(bRibbonUI, m_hgRibbonSettings);
    }
    bHandled = !m_view.QueryClose();
    return 0;
}

Implement the UI switching in OnViewRibbon().

LRESULT OnViewRibbon(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) 
{
    ShowRibbonUI(!IsRibbonUI(), UI_MAKEAPPMODE(prev.IsWindow())); 
    UISetCheck(ID_VIEW_RIBBON, IsRibbonUI());
    return 0; 
} 

Implement the Ribbon Context Popup in OnContextMenu().

LRESULT OnContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
        if((HWND)wParam == m_view.m_hWnd)
        {
                CMenu menuContext;
                menuContext.LoadMenu(IDR_CONTEXTMENU);
                CMenuHandle menuPopup(menuContext.GetSubMenu(0));
#ifndef _WIN32_WCE
        if (IsRibbonUI())
            TrackRibbonMenu(ID_CONTEXTMAP, lParam);
        else
             m_CmdBar.TrackPopupMenu(menuPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
                 LOWORD(lParam), HIWORD(lParam));
#else
             TrackPopupMenuEx(menuPopup, TPM_LEFTALIGN, LOWORD(lParam), 
                  HIWORD(lParam), m_hWndCECommandBar, NULL);
#endif // _WIN32_WCE
        }

Set the Ribbon Application Mode 1 and the Page Group Label in OnFilePrintPreview().

LRESULT OnFilePrintPreview(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
// ...

    m_spinner.SetValue(UI_PKEY_MaxValue, m_arrPages.GetSize(), true);
    CString sPageGroup = L"1 Page";
    INT nPage = m_arrPages.GetSize();
    if (nPage > 1)
        sPageGroup.Format(L"%d Pages", nPage);
    UISetText(ID_GROUP_PAGE, sPageGroup);

    if (IsRibbonUI())
    {
       SetRibbonModes(UI_MAKEAPPMODE(1));
    }
        return 0;
}

Set the Ribbon Application Mode 0 in OnPrintPreviewClose().

LRESULT OnPrintPreviewClose(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI())
        SetRibbonModes(UI_MAKEAPPMODE(0));
}

Update the Ribbon Spinner value in OnPrintPreviewForward() and OnPrintPreviewBack().

LRESULT OnPrintPreviewForward(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
        prev.NextPage();
    m_spinner.SetVal(prev.m_nCurPage + 1, true);
        return 0;
}

LRESULT OnPrintPreviewBack(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
        prev.PrevPage();
    m_spinner.SetVal(prev.m_nCurPage + 1, true);
        return 0;
}

Handle direct preview page selection in a new OnSpinnerPage() member.

LRESULT OnSpinnerPage(WORD /*wID*/, LONG lVal, BOOL& /*bHandled*/)
{
    prev.SetPage(lVal - 1);
    prev.Invalidate();
        return 0;
}

CEditView changes

Handle the Ribbon Font Control messages.

BEGIN_MSG_MAP(CEditView)
// ...
#ifndef _WIN32_WCE
    // ...
    RIBBON_FONT_CONTROL_HANDLER(ID_RIBBON_FONT, OnRibbonFont)

Preview a hovered font, reverse to the internal m_font member when Preview is cancelled, update it when the font is changed through the Ribbon, .

LRESULT OnRibbonFont(UI_EXECUTIONVERB verb, WORD /*wID*/, CHARFORMAT2* pcf, BOOL& /*bHandled*/)
{
    if (verb == UI_EXECUTIONVERB_CANCELPREVIEW)
        SetFont(m_font);
    else
        SetDefaultCharFormat(*pcf);

    if (verb == UI_EXECUTIONVERB_EXECUTE)
        UpdateFont(*pcf);

    return 0;
}

void UpdateFont(CHARFORMAT2& cf)
{
    CLogFont lf(m_font);

    if (cf.dwMask & CFM_SIZE)
    {
        lf.lfHeight = -MulDiv(cf.yHeight, 
            CWindowDC(NULL).GetDeviceCaps(LOGPIXELSY), 1440);
        lf.lfWidth = 0;
    }
    
    if (cf.dwMask & CFM_BOLD)
        lf.lfWeight = cf.dwEffects & CFE_BOLD ? FW_BOLD : 0;

    if (cf.dwMask & CFM_WEIGHT)
        lf.lfWeight = cf.wWeight;
    
    if (cf.dwMask & CFM_ITALIC)
        lf.lfItalic = cf.dwEffects & CFE_ITALIC ? TRUE : FALSE;
    
    if (cf.dwMask & CFM_UNDERLINE)
        lf.lfUnderline = cf.dwEffects & CFE_UNDERLINE ? TRUE : FALSE;
    
    if (cf.dwMask & CFM_STRIKEOUT)
        lf.lfStrikeOut = cf.dwEffects & CFE_STRIKEOUT ? TRUE : FALSE;
    
    if (cf.dwMask & CFM_CHARSET)
        lf.lfCharSet = cf.bCharSet;
    
    if (cf.dwMask & CFM_FACE)
    {
        lf.lfPitchAndFamily = cf.bPitchAndFamily;
        SecureHelper::strcpyW_x(lf.lfFaceName, LF_FACESIZE, cf.szFaceName);
    }

        m_font.DeleteObject();
        m_font.CreateFontIndirect(&lf);
}


Conclusion

As the Ribbon UI is somehow controversial, dual UI applications may satisfy both ribbon-lovers and ribbon-haters. With the WTL library, the best of both worlds is at your hands.

Cheers!

Revision History

  • 26 Jan 2010: Released
  • 28 Jan 2010: Update
    • atlribbon.h: Fixed multiple instances issues.
    • MTPad7: Correct handling of Font Control UI_EXECUTIONVERB_CANCELPREVIEW.
    • Article: Some typos and bad links
  • 19 Feb 2010: Update
  • 13 Apr 2010: atlribbon.h and MTPad7 update
    • Better handling of UI switching,
    • Fixed Vista Basic sizing issue when switched to traditional UI,
    • Fixed IUIImage leak,
    • Context tab default initialized to UI_CONTEXTAVAILABILITY_NOTAVAILABLE.

License

This article, along with any associated source code and files, is licensed under The Common Public License Version 1.0 (CPL)