使用 WTL 为 Vista 或 Windows 7 上的原生应用程序重新设计外观,增加 Ribbon UI






4.97/5 (71投票s)
包含 Ribbon UI 实现指南,附带示例和双 UI 启用的遗留应用程序

引言
Windows 7 引入了新的 Ribbon 控件,系统更新使其可用于 Vista 和 Windows Server 2008 平台。
本文从 C++ 应用程序开发者的角度出发,重点介绍如何使用 atlribbon.h 和 WTL 8 来改编现有或新应用程序,使用户能够在 Ribbon 或传统 UI 之间进行可逆的选择。
- 
预览 展示了支持双 UI 的 Ribbon MTPad WTL 示例。 
- 
初次体验 在不向 AppWizard 应用程序添加任何资源的情况下,从 WTL AppWizard 代码创建了一个简单的双 UI 应用程序。 
- 
Ribbon UI 实现指南 通过示例描述了 Ribbon UI 的实现。 
- 
使用 Ribbon UI 重新审视 MTPad 展示了现有应用程序的改编。 
参考文献
关于 Windows 7 Ribbon 的主要文档是 MSDN 的 Windows Ribbon Framework 部分。
您可以在 CodeProject 上找到 Michael Chourdakis 的文章:Windows 7 Ribbon: The Time Has Come, Your Win32 Application Will Change, and i,以及 Arik Poznanski 的博客 中有关 Ribbon 功能的详细描述,以及 Windows Ribbon for WinForms,用于在 C# 应用程序中使用它。
必备组件
要跟进本文,您需要在计算机上正确安装:
- 
Visual Studio 或 VCExpress 2008(所有服务包)或 Visual Studio 2010 Beta2。 
- 
Windows 7 平台 SDK:您需要 uicc 工具,该工具不包含在 Visual Studio 2010 Beta2 分发版中。 
- 
对于 VCExpress,您需要 ATL 3,可以在 Windows® Server 2003 SP1 Platform SDK 或 ATL 7.1 中找到,如此帖子所示。 
显然,您应该在已更新的 Vista 或 Windows 7 平台上进行测试,您可以在 Vista、Windows 7 或 XP 下编译和运行所有内容。
预览
下载 MTPad7Exe.zip 并运行 MTPad.exe。
在已更新的 Vista 或 Windows 7 平台上,MTPad 会以 Ribbon UI 打开。

打开一些文本文件,检查字体选择预览功能,尝试打印预览模式,使用应用程序菜单中的最近使用的文件,打开一些新窗口。
取消选中 Ribbon 复选框以切换到传统 UI,使用 View 菜单中的 Ribbon 条目在编辑或预览模式下切换回 Ribbon UI。

使用QAT(快速访问工具栏)右侧的内置下拉菜单更改布局设置,然后切换回传统 UI 并观察,当切换回 Ribbon UI 时,Ribbon 布局设置会被保留。

在传统 UI 中关闭最后一个窗口并重新打开 MTPad。它会以传统模式重新打开。
初次体验
使用 atlribbon.h ,您可以在几分钟内为基于传统 UI 的 WTL 应用程序构建一个用户可选择的双(Ribbon 和传统)UI。
初次体验执行了必要步骤,并提供了许多执行细节,这些步骤适用于任何 Ribbon UI 应用程序。
步骤 0:创建一个功能最少的 WTL 应用程序
如果您使用 Visual Studio 2010 Beta2 或由于某种原因未安装 WTL AppWizard,请下载 FirstRibbon.zip ,解压缩并打开 FirstRibbon.vcproj 。
否则,使用 WTL AppWizard,创建一个带有 SDI 且不包含 cpp 文件的 FirstRibbon WTL 应用程序……

……默认的用户界面功能 和富文本编辑器视图窗口。

要处理编辑命令,请将 CRichEditCommands 继承和消息映射链接添加到 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()
};
……并将 CMainFrame 命令链接到 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()
编译(此处任何编译错误意味着您的开发环境未正确设置),运行并使用工具栏按钮或“编辑”菜单检查编辑功能。

步骤 1:改编 C++ 代码以托管 Ribbon UI
更改 stdafx.h 中的常量以访问 SDK Windows 7 功能。
// 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
下载 atlribbon.zip ,将 atlribbon.h 解压到您的 <FirstRibbon> 目录,并将其包含在 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"
将 CMainFrame 派生自 WTL::CRibbonFrameWindowImpl;由于 CRibbonFrameWindowImpl 派生自 WTL::CRibbonUpdateUI,请删除类定义中的 CUpdateUI 父类以及消息映射,只在消息映射中链接 CRibbonFrameWindowImpl<CMainFrame>。
// 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()
要在预 Vista 或未更新的 Vista 平台下运行,请在 Project->FirstRibbon Properties->Linker->Input 中将 propsys.dll 和 dwmapi.dll 设置为延迟加载。

编译以检查步骤是否完成。
步骤 2:创建 Ribbon 资源并将其嵌入应用程序
向您的项目中添加一个新的 xml 文件(对于 VCExpress,是一个新的 C++ 头文件),命名为 FirstRibbonRibbon.xml ,并将以下内容粘贴进去
<!-- 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>
在 VC 解决方案资源管理器窗口 中,右键单击 FirstRibbonRibbon.xml 并选择弹出菜单中的属性 。转到自定义生成步骤->常规 ,并将以下内容粘贴到输入字段中
- 
命令行:uicc FirstRibbonRibbon.xml FirstRibbonRibbon.bml /header:FirstRibbonRibbon.h /res:FirstRibbonRibbon.rc 
- 
描述:正在编译 FirstRibbonRibbon.xml 
- 
输出:FirstRibbonRibbon.bml;FirstRibbonRibbon.rc;FirstRibbonRibbon.h 
- 
单击确定 进行验证。 
在同一个 VC 解决方案资源管理器窗口 中,再次右键单击 FirstRibbonRibbon.xml 并选择编译 :输出应为
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 ==========
将 uicc 生成的文件添加到应用程序的 FirstRibbon.rc 文件中
- 
使用 Visual Studio 在资源视图窗口 中,右键单击 FirstRibbon.rc ,并在只读符号指令 框中添加 #include "FirstRibbonRibbon.h",在编译时指令 框中添加#include "FirstRibbonRibbon.rc"。

- 
使用 VCExpress 在 VC 解决方案资源管理器窗口 中,右键单击 FirstRibbon.rc 并选择弹出菜单中的查看代码 。编辑 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
- 
在 FirstRibbon.cpp 中 #includeuicc 生成的 FirstRibbonRibbon.h
// 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"
- 
生成项目,FirstRibbon.rc 应能编译,并且您应该不会收到任何错误。运行它,没有任何区别。 
步骤 3:操作 Ribbon
在应用程序的 View 菜单中创建一个 Ribbon 命令
- 
使用 Visual Studio 在资源视图窗口 中,编辑 IDR_MAINFRAME 菜单,并在 View 菜单顶部添加一个 ID_VIEW_RIBBON id 的 &Ribbon 条目。 
 编辑字符串表以添加一个 ID_VIEW_RIBBON 字符串,其内容为 显示或隐藏 ribbon\n切换 Ribbon。
- 
使用 VCExpress 在 VC 解决方案资源管理器窗口 中,右键单击 FirstRibbon.rc 并选择弹出菜单中的查看代码 。编辑 POPUP "&View" 块,以及带有 ID_VIEW_xxx 字符串的 STRINGTABLE 块。 
// 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
在 CMainFrame 消息映射中为 ID_VIEW_RIBBON 添加一个命令处理程序……
// 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
……并在类定义的末尾定义它。
// 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 
要删除不支持 Ribbon UI 时的 ID_VIEW_RIBBON 命令,并设置 Ribbon 命令标签为菜单文本,请将以下内容添加到 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);
// ...
编译并运行,看不到任何变化,点击 View 菜单中的Ribbon

是的,你可以!

是时候玩玩了,更改 Ribbon 设置,切换 UI 模式,检查命令,查看 Ribbon 工具提示,尝试在 XP 下运行,享受……
附加步骤:上下文 UI 启动和启用编辑命令
在 CMainFrame::OnCreate() 的末尾……
// ...
    ShowRibbonUI(bRibbonUI); // 4
    UISetCheck(ID_VIEW_RIBBON, bRibbonUI); // 4
    return 0;
}
……以及在 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;
}
编译,运行并检查 FirstRibbon 是否在支持的情况下以 Ribbon UI 启动,以及在两种 UI 中,当编辑命令不相关时是否被禁用。
Ribbon UI 实现指南
本指南通过示例描述了在派生自 CRibbonFrameWindowImplBase(如 CRibbonFrameWindowImpl 或 CRibbonMDIFrameWindowImpl)的 CMainFrame 框架窗口中实现 Ribbon UI。
回顾我们的初次体验
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; 
    } 
- 
CRibbonFrameWindowImpl<CMainFrame>继承自WTL::CFrameWindowImpl<CMainFrame>(实现传统 UI),并继承自WTL::RibbonUI::CRibbonImpl<CMainFrame>(实现 Ribbon UI)。因此,CMainFrame是一个双 UI 窗口,在支持 Ribbon UI 的运行时环境中。
- 
bool WTL::RunTimeHelper::IsRibbonUIAvailable()执行运行时测试,并在运行于已更新的 Vista 或 Win7 时返回 true。如果返回 false,则会移除 View->Ribbon 菜单项,以避免调用 Ribbon UI 代码。
- 
bool CRibbonImpl::IsRibbonUI()返回true表示当前显示 Ribbon UI,否则返回 false。
- 
CRibbonImpl继承自WTL::CRibbonUpdateUI<CMainFrame>,它扩展了 本文 中描述的WTL::CAutoUpdateUI<CMainFrame>,并处理额外的UPDUI_RIBBON类型。
 boolWTL::CAutoUpdateUI<CMainFrame>::UIAddMenu(HMENU hm, bool bSetText)将 hm 菜单的所有(非弹出)菜单项添加到其 UPDATE_UI_MAP 中,并为每个菜单项 ID 和文本调用UISetText(ID, text)。
- 
bool WTL::CRibbonFrameWindowImplBase<CMainFrame>::ShowRibbonUI(bool bShow, INT32 imodes = UI_MAKEAPPMODE(0), LPCWSTR sResName = L"APPLICATION_RIBBON")在调用后返回 Ribbon UI 活动状态:bShow是请求的状态,imodes是请求的 Ribbon 模式,默认为模式 0,sResName是 Ribbon 资源文件中的 UIFILE 资源的字符串 ID。
ShowRibbonUI(true) 操作
ShowRibbonUI(true) 隐藏包含传统 UI 命令栏和工具栏的 Rebar,并调用 CRibbonImpl::CreateRibbon(sResName),该函数执行 此处 描述的步骤。
新创建的 Ribbon 会为标记文件中的每个 ID 调用 CRibbonImpl::OnCreateUICommand()。每个 ID 都以 UPDUI_RIBBON 类型添加到 UPDATE_UI_MAP 中。
因此,常见的 UI 元素在 UPDATE_UI_MAP 中同时具有 UPDUI_RIBBON 和(例如)UPDUI_MENUPOPUP 类型。可以通过重写常用的 UISetText()、UISetCheck() 和 UIEnable() 调用来管理 Ribbon 元素的标签文本、启用状态和选中状态。
ShowRibbonUI(false) 操作
ShowRibbonUI(false) 调用 CRibbonImpl::DestroyRibbon(),然后该函数调用 IUIFramework::Destroy()。
Ribbon 会为标记文件中的每个 ID 调用 CRibbonImpl::OnDestroyUICommand()。UPDUI_RIBBON 类型将从该 ID 的 UPDATE_UI_MAP 中移除。
如果 ID 没有其他 UPDUI_type,则该 ID 本身将从 UPDATE_UI_MAP 中移除,可以通过调用 CRibbonUpdateUI::UIPersistElement(UINT nID, true) 来阻止这种情况。
ShowRibbonUI(false) 然后恢复包含传统 UI 命令栏和工具栏的 Rebar。
默认 Ribbon UI 行为
当 Ribbon 激活时,它会调用 CRibbonImpl::UpdateProperty(),传入一个元素 ID 和一个 Property Key 引用。
此表总结了默认的 CRibbonImpl 行为。
| 键 | 设置 | 可覆盖的回调 | 默认处理 | 
|---|---|---|---|
| UI_PKEY_Label | UISetText(ID, LPCWSTR) | LPCWSTR OnRibbonQueryText(ID, key) | 返回 UIGetText(ID) | 
| UI_PKEY_Enabled | UIEnable(ID, bool) | bool OnRibbonQueryState(ID, key) | 返回 UIGetState(ID) & ~UPDUI_DISABLED | 
| UI_PKEY__BooleanValue | UISetCheck(ID, bool) | bool OnRibbonQueryState(ID, key) | 返回 UIGetState(ID) & UPDUI_CHECKED | 
| UI_PKEY_LabelDescription UI_PKEY_TooltipDescription | LPCWSTR OnRibbonQueryText(ID, key) | 资源字符串中 ID 前面的部分('\n' 之前,或 NULL) | |
| UI_PKEY_TooltipTitle | LPCWSTR OnRibbonQueryText(ID, key) | 资源字符串中 ID 后面的部分('\n' 之后,或 NULL) | |
| UI_PKEY_Keytip | LPCWSTR OnRibbonQueryText(ID, key) | 资源字符串中 '&' 之后(或 NULL)的 1 个字符子串 | |
| UI_PKEY_SmallImage | HBITMAP OnRibbonQueryImage(ID, key) | m_CmdBar.m_arrVistaBitmap 中的 HBITMAP,或具有 ID 的资源位图的 HBITMAP(或 NULL) | |
| UI_PKEY_LargeImage UI_PKEY_SmallHighContrastImage UI_PKEY_LargeHighContrastImage | HBITMAP OnRibbonQueryImage(ID, key) | 具有 ID 的资源位图的 HBITMAP(或 NULL) | 
当用户操作 Ribbon UI 元素时,Ribbon 会使用元素 ID、一个 UI_EXECUTIONVERB 和一个 Property Key 引用调用 CRibbonImpl::Execute。
- 
CRibbonImpl::Execute()调用可覆盖的OnRibbonCommandExecute(UINT32 uCmdID, ...)。
- 
如果未被覆盖, CRibbonImpl::OnRibbonCommandExecute()会发布一个WM_COMMAND消息,其中WPARAM设置为 uCmdID。
实用总结
- 
菜单资源中的任何项(通过 UIAddMenu(IdMenu, true)插入 UI_MAP)如果具有相同的 ID 和两个部分的字符串资源,如“创建新文档\n新建”,则会在 Ribbon 中获得菜单名称、资源字符串工具提示标题和工具提示正文。
- 
如果一个项是添加到 m_CmdBar的工具栏的一部分(通过m_CmdBar.LoadImages(IdToolBar)),那么它在 Ribbon 中的UI_PKEY_SmallImage与 CommandBar 菜单中的相同,并且具有相同 ID 的位图资源(如果存在)将用于所有其他 Image 属性(如果不存在 CCommandBarCtrl 位图,则用于所有)。
- 
对于任何 Ribbon 定义的 Command, UI_PKEY_Label、UI_PKEY_Enabled和UI_PKEY_BooleanValue都通过UISetText()、UIEnable()和UISetCheck()处理。
- 
当用户单击或选中/取消选中 Ribbon 元素时,将发布 WM_COMMAND消息,其 ID 为 Ribbon 元素 ID。
这就是我们的 FirstRibbon Ribbon UI 能够立即工作的原因。
示例:添加一个具有默认处理的撤销 Ribbon 按钮
ID_EDIT_UNDO 存在于 IDR_MAINFRAME 菜单中,并在 FirstRibbon.rc 中具有字符串资源,将其添加到 Commands 声明以及 FirstRibbonRibbon.xml 中的GroupClipboard Group 。
    <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>
下载 FirstRibbonBitmaps.zip ,将 Undo.bmp 解压到 FirstRibbon\res ,并以 ID_EDIT_UNDO ID 添加到 FirstRibbon.rc 。
// Bitmap // IDR_MAINFRAME BITMAP "res\\Toolbar.bmp" ID_EDIT_UNDO BITMAP "res\\Undo.bmp"

CRibbonImpl 控制类和 RIBBON_CONTROL_MAP() 宏
CRibbonImpl::CRibbonXXXCtrl 控制类处理 Windows Ribbon Framework Control Library 支持的控件。
CRibbonImpl 和所有 CRibbonXXXCtrl 类都实现了 WTL::RibbonUI::ICtrl 抽象接口。BEGIN_RIBBON_CONTROL_MAP()、RIBBON_CONTROL() 和 END_RIBBON_CONTROL_MAP() 宏构建一个 ICtrl& GetRibbonCtrl(UINT ID) 函数,该函数如果 ID 在映射中,则返回对控件 ICtrl 的引用,否则返回 CRibbonImpl::ICtrl。
控件的实现需要 3 个步骤
- 
在 Ribbon 标记中,识别控件并将其插入具有适当属性的组中。 
<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> 
- 
实例化一个具有匹配 ID 的 CMainFrame成员的 CRibbonImpl::CRibbonXXXCtrl。
CRibbonColorCtrl<ID_TEST_COLOR> m_color;
- 
将成员插入 RIBBON_CONTROL_MAP。 
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_color)
    ...
END_RIBBON_CONTROL_MAP() 
CRibbonImpl 控制类公共 API
所有 CMainFrame::CRibbonImpl 控制类都派生自 WTL::RibbonUI::CtrlImpl<ID, CMainFrame> 并公开
- 
HRESULT Invalidate()和HRESULT Invalidate(key, UI_INVALIDATIONS flags = UI_INVALIDATIONS_PROPERTY),它们使此 ID 的所有(或一个)Property Key 无效。
- 
HRESULT SetText(key&, LPCWSTR sText, bool bUpdate = false):将sText复制到与key关联的内部字符串,如果bUpdate为true,则调用Invalidate(key)。
启动时或当一个 key 未初始化或无效时,Ribbon 会调用 CRibbonImpl::UpdateProperty(),该函数使用 RIBBON_CONTROL_MAP 来选择受影响的控件实例并调用其相关成员函数(例如,文本 key 调用 OnGetText())。
如果满足回调条件
- 
此 key 的值被视为无效, 
- 
调用相关的CRibbonImpl 可覆盖回调, 
- 
如果未被覆盖,则执行默认处理, 
- 
返回存储的值。 
- 将存储的值返回给 Ribbon。
CRibbonImpl 控制类公共 Property Keys
| 键 | 设置 | CRibbonImpl 可覆盖回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_LabelDescription UI_PKEY_TooltipDescription | SetText(key, LPCWSTR, bool bUpdate = false) | LPCWSTR OnRibbonQueryText(nCmdID, key) | 当前字符串值为空 | 资源字符串中 ID 前面的部分(或整个字符串) | 
| UI_PKEY_TooltipTitle | SetText(key, LPCWSTR, bool bUpdate = false) | LPCWSTR OnRibbonQueryText(nCmdID, key) | 当前字符串值为空 | 资源字符串中 ID 后面的部分(或 NULL) | 
| UI_PKEY_Keytip | SetText(key, LPCWSTR, bool bUpdate = false) | LPCWSTR OnRibbonQueryText(nCmdID, key) | 当前字符串值为空 | 资源字符串中 '&' 之后(如果找到)或 '\n' 之后的 1 个字符(或 NULL) | 
默认处理与 CRibbonImpl 相同,但值可以设置,并且在用户切换 UI 时是持久的。
示例的预备步骤
一些示例使用状态栏来输出用户在 Ribbon 上的操作。要启用它们使用 UISetText(ID_DEFAULT_PANE, someText),请在 stdafx.h 中定义 _ATL_USE_CSTRING_FLOAT……
// stdafx.h : include file for standard system include files, // ... #define _RICHEDIT_VER 0x0200 #define _ATL_USE_CSTRING_FLOAT
……并在 OnIdle() 中向 CMainFrame 添加 UIUpdateStatusBar() 调用,在 OnCreate(). 中添加 UIAddStatusBar() 调用。
//...
virtual BOOL OnIdle()
{
    //...
    UIUpdateToolBar();
    UIUpdateStatusBar();
    return FALSE;
}
// ...
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
//...
    CreateSimpleStatusBar();
    UIAddStatusBar(m_hWndStatusBar);
//...
Ribbon 单个控件类
CRibbonImpl 单个控件类处理除 Galleries、Combos 和 Recent Items 之外的所有 Ribbon 控件。
CRibbonCommandCtrl
- 
在 CRibbonImpl派生类中的声明:CRibbonCommandCtrl<ID> m_Ctrl;
- 
用户操作消息映射宏: COMMAND_ID_HANDLER(wID, OnCommand)
- 
处理程序原型: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled)
CRibbonImpl::CRibbonCommandCtrl 操作 Ribbon Buttons、Drop-Down Buttons、Toggle Buttons、Groups、Menu Groups、Tabs 和 Tab Groups 以实现 CRibbonImpl 控件类公共 Property Keys 的特定处理,并在适当时
| 键 | 设置 | CRibbonImpl 可覆盖回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_SmallImage | SetImage(key, HBITMAP, bool bUpdate = false) | HBITMAP OnRibbonQueryImage(nCmdID, key) | 当前 HBITMAP 为 NULL | m_CmdBar.m_arrVistaBitmap 中的 HBITMAP,或具有 ID 的资源位图的 HBITMAP(或 NULL) | 
| UI_PKEY_LargeImage UI_PKEY_SmallHighContrastImage UI_PKEY_LargeHighContrastImage | SetImage(key, HBITMAP, bool bUpdate = false) | HBITMAP OnRibbonQueryImage(nCmdID, key) | 当前 HBITMAP 为 NULL | 具有 ID 的资源位图的 HBITMAP(或 NULL) | 
示例:处理一个 Ribbon Group 控件
在 CMainFrame 中声明一个 CRibbonCommandCtrl,其 ID 为 Ribbon GroupClipboard 控件 ID,并将其插入 RIBBON_CONTROL_MAP 中,
// Ribbon controls
CRibbonCommandCtrl<ID_GROUP_CLIPBOARD> m_clipboard;
// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_clipboard)
在 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");
当 Ribbon 更充实时,如果 GroupClipboard 折叠为 Button

CRibbonColorCtrl
- 在 CRibbonImpl派生类中的声明:CRibbonColorCtrl<wID> m_Ctrl;
- 
用户操作消息映射宏: RIBBON_COLOR_CONTROL_HANDLER(wID, OnRibbonColorCtrl)
- 
处理程序原型: LRESULT OnRibbonColorCtrl(UI_EXECUTIONVERB verb, WORD wID, COLORREF color, BOOL& bHandled);
CRibbonImpl::CRibbonColorCtrl 操作 Ribbon DropDownColorPicker 控件并处理
| 键 | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_Color | m_Ctrl.SetColor(COLORREF color, | COLORREF OnRibbonQueryColor(ID) | 当前颜色为 MAGENTA | 返回 0x800080 MAGENTA(无变化) | 
| UI_PKEY_ColorType | m_Ctrl.SetColorType( | UI_SWATCHCOLORTYPE_NOCOLOR,如果未通过 SetColorType() 或 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, | LPCWSTR OnRibbonQueryColorLabel(ID, key) | 当前字符串值为空 | 返回 NULL(无变化) | 
| UI_PKEY_ThemeColors UI_PKEY_StandardColors | m_Ctrl.SetColorArray(key, COLORREF* pColor, | COLORREF* OnRibbonQueryColorArray(ID, key) | 当前颜色数组为空 | 返回 NULL(无变化) | 
| UI_PKEY_ThemeColorsTooltips UI_PKEY_StandardColorsTooltips | m_Ctrl.SetColorTooltips(key, LPCWSTR*, | LPCWSTR* OnRibbonQueryColorTooltips(ID, key) | 当前字符串数组为空 | 返回 NULL(无变化) | 
OnRibbonQueryColorArray() 返回的 COLORREF* 或传递给 SetColorArray() 的是 COLORREF 数组元素 0 的地址,该数组以 0x800080 MAGENTA 结尾。
OnRibbonQueryColorTooltips() 返回的 LPCWSTR* 或传递给 SetColorTooltips() 的是 LPCWSTR 数组元素 0 的地址,该数组以 NULL 结尾。
示例:添加并处理一个颜色控件
在标记文件中,声明组和命令 ID,并在 Tab 中声明一个 DropDownColorPicker
    <!--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>
在 CMainFrame 中,声明一个 CRibbonColorCtrl,其 ID 相同,并将其插入 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()
当 Ribbon 需要知道显示哪个颜色作为选中状态时,CRibbonImpl 会调用 OnRibbonQueryColor()。CRibbonImpl::OnRibbonQueryColor() 返回 0x800080 (MAGENTA),因此重写它以返回当前背景色。
    // Ribbon controls queries handling
    COLORREF OnRibbonQueryColor(UINT nID)
    {
        COLORREF color = m_view.SetBackgroundColor();
        m_view.SetBackgroundColor(color);
        return color;
    }
当用户在控件中选择或将鼠标悬停在颜色上时,CRibbonImpl 会发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 UI_EXECUTIONVERB,LOWORD(wParam) 设置为控件 ID,lParam 设置为选定的 COLORREF。
使用 RIBBON_COLOR_CONTROL_HANDLER 宏在 CFirstRibbonView 中处理此消息。
// 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;
    }
颜色控件按钮没有关联的图像。从 FirstRibbonBitmaps.zip 中提取 DDCP.bmp 到 FirstRibbon\res ,并以 ID_TEST_COLOR ID 添加到 FirstRibbon.rc 。
// Bitmap // ID_TEST_COLOR BITMAP "res\\DDCP.bmp"

CRibbonFontCtrl
- 
在 CRibbonImpl派生类中的声明:CRibbonFontCtrl<wID> m_Ctrl;
- 
用户操作消息映射宏: RIBBON_FONT_CONTROL_HANDLER(wID, OnRibbonFontCtrl)
- 
处理程序原型: LRESULT OnRibbonFontCtrl(UI_EXECUTIONVERB verb, WORD wID, CHARFORMAT2* pcf, BOOL& bHandled);
CRibbonImpl::CRibbonFontCtrl 操作 Ribbon Font Controls 并处理
| 键 | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_FontProperties | Invalidate(UI_PKEY_FontProperties) | bool OnRibbonQueryFont(ID, CHARFORMAT2&) | 总是 | 返回 false(无变化) | 
示例:添加并处理一个字体控件
在标记文件中,声明组和命令 ID,并在 Tab 中声明一个 FontControl
    <!--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>
在 CMainFrame 中,声明一个 CRibbonFontCtrl,其 ID 相同,并将其插入 RIBBON_CONTROL_MAP。
    // Ribbon controls
    CRibbonFontCtrl<ID_TEST_FONT> m_font;
// ...
    // Ribbon control map
    BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
        RIBBON_CONTROL(m_font)
当 Ribbon 需要知道显示哪个字体特征作为选中状态时,CRibbonImpl 会调用 OnRibbonQueryFont()。CRibbonImpl::OnRibbonQueryFont() 返回 false(无变化),因此重写它以返回当前字体的 CHARFORMAT2。
    bool OnRibbonQueryFont(UINT /*nId*/, CHARFORMAT2& cf)
    {
        m_view.GetDefaultCharFormat(cf);
        return true;
    }
当用户在控件中选择一个特征时,CRibbonImpl 会发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 UI_EXECUTIONVERB,LOWORD(wParam) 设置为控件 ID,lParam 设置为 CHARFORMAT2*。
使用 RIBBON_FONT_CONTROL_HANDLER 宏在 CFirstRibbonView 中处理此消息。
// 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;
    }

CRibbonSpinnerCtrl, CRibbonFloatSpinnerCtrl
- 
在 CRibbonImpl派生类中的声明
- 
CRibbonSpinnerCtrl<wID> m_Ctrl;
- 
CRibbonFloatSpinnerCtrl<wID> m_Ctrl;
- 
用户操作消息映射宏 
- 
RIBBON_SPINNER_CONTROL_HANDLER(wID, OnRibbonSpinnerCtrl)
- 
RIBBON_FLOATSPINNER_CONTROL_HANDLER(wID, OnRibbonFloatSpinnerCtrl)
- 
处理程序原型 
- 
LRESULT OnRibbonSpinnerCtrl(WORD wID, LONG lVal, BOOL& bHandled);
- 
LRESULT OnRibbonFloatSpinnerCtrl(WORD wID, DOUBLE* pdVal, BOOL& bHandled);CRibbonImpl:: CRibbonSpinnerCtrl和CRibbonImpl:: CRibbonFloatSpinnerCtrl都操作 Ribbon Spinner 控件并处理 V 为LONG(CRibbonSpinnerCtrl)或DOUBLE(CRibbonFloatSpinnerCtrl)
| 键 | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_DecimalPlaces | SetDecimalPlaces(V vPlaces, bool bUpdate = false) | bool OnRibbonQuerySpinnerValue(ID, key, LONG*) | 总是 | 返回 false(无变化) | 
| UI_PKEY_DecimalValue | SetVal(V vVal, bool bUpdate = false) | bool OnRibbonQuerySpinnerValue(ID, key, LONG*) | 总是 | 返回 false(无变化) | 
| UI_PKEY_MinValue | SetMin(V vMin, bool bUpdate = false) | bool OnRibbonQuerySpinnerValue(ID, key, LONG*) | 总是 | 返回 false(无变化) | 
| UI_PKEY_MaxValue | SetMax(V vMax, bool bUpdate = false) | bool OnRibbonQuerySpinnerValue(ID, key, LONG*) | 总是 | 返回 false(无变化) | 
| UI_PKEY_Increment | SetIncrement(V vIncrement, bool bUpdate = false) | bool OnRibbonQuerySpinnerValue(ID, key, LONG*) | 总是 | 返回 false(无变化) | 
| UI_PKEY_FormatString | SetFormatString(LPCWSTR sFormat, bool bUpdate = false) | LPCWSTR OnRibbonQueryText(UINT nCmdID, REFPROPERTYKEY key) | 当前字符串为空 | 返回 NULL(无变化) | 
| UI_PKEY_RepresentativeString | SetRepresentativeString(LPCWSTR sRepresentative, bool bUpdate = false) | LPCWSTR OnRibbonQueryText(UINT nCmdID, REFPROPERTYKEY key) | 当前字符串为空 | 返回 NULL(无变化) | 
示例:添加并处理 Spinner 控件
在标记文件中,声明组和命令 ID,并在 Tab 中声明 Spinners
    <!--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>
在 CMainFrame 中,声明一个 CRibbonSpinnerCtrl 和一个 CRibbonFloatSpinnerCtrl,其 ID 匹配,并将它们插入 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)
当用户在 Spinner 中设置值时
- 
对于 CRibbonSpinnerCtrl,CRibbonImpl发布一个WM_COMMAND消息,其中HIWORD(wParam)设置为 0,LOWORD(wParam)设置为控件 ID,lParam设置为 LONG 值。
- 
对于 CRibbonFloatSpinnerCtrl,CRibbonImpl 发布一个具有相同 wParam的 WM_COMMAND 消息,lParam设置为指向值的DOUBLE*。
……在 CMainFrame 中使用 RIBBON_SPINNER_CONTROL_HANDLER 和 RIBBON_FLOATSPINNER_CONTROL_HANDLER 宏处理这些消息。
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;
    }

示例:初始化 Ribbon 控件的三种方法
CRibbonImpl::CRibbonSpinnerCtrl 和 CRibbonImpl::CRibbonSpinnerFloatCtrl 的默认初始值是 0 到 100 的范围,增量为 1,起始值为 1,小数位数为 0(CRibbonSpinnerFloatCtrl 为 1)。
将控件初始化为
- 
ID_TEST_FLOATSPINNER 范围为 10 到 20,增量为 .25,起始值为 14.75,2 位小数,并调整宽度以容纳 4 位数字和 1 位小数。 
- 
ID_TEST_SPINNER 宽度相同,起始值为 51。 
使用以下方法之一
- 最简单的方法,在 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); }
- 如果值可能在运行时更改,请重写相关的 CRibbonImpl::OnRibbonQueryxxx()成员// 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); }
- 最面向对象的方法,特化构造函数(对于简单和工具栏画廊及组合框控件不可用)// 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); }
- 任何 1 到 3 的组合,以满足您的喜好或需求。
Ribbon Collection 控件
CRibbonImpl 集合控件类处理 Ribbon Galleries、Combos 和 Recent Items Ribbon 控件。
完整的类 CRibbonItemGalleryCtrl、CRibbonCommandGalleryCtrl 和 CRibbonComboCtrl 处理类别、动态调整大小和项配置。
简单的类 CRibbonSimpleGalleryCtrl、CRibbonSimpleComboCtrl 和 CRibbonToolBarGalleryCtrl 不处理类别,并且在 Ribbon 创建时配置一次。
Ribbon 完整集合类
完整集合类的通用 Property Keys
| 键 | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_ItemsSource | InvalidateItems() 或 | Item keys 会为所有项进行查询。 | ||
| UI_PKEY_Categories | InvalidateCategories() | UI_PKEY_Label 会为每个类别查询,UI_PKEY_CategoryId 会为每个项查询。 | 
| Category Key | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_Label | SetCategoryText(UINT uCat, LPCWSTR sText, bool bUpdate = false) | LPCWSTR OnRibbonQueryCategoryText(ID, UINT32 uCat) | 当前字符串为空 | 返回 « Category » | 
| Item Key | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_CategoryId | SetItemCategory(UINT uItem, UINT uCat, bool bUpdate = false) | UINT32 OnRibbonQueryItemCategory(ID, UINT32 uItem) | 当前类别索引为 UI_COLLECTION_INVALIDINDEX | 返回 0 | 
与单个控件不同,集合控件有一个 uItems 模板参数,以及一个可选的 uCategories 模板参数。
CRibbonItemGalleryCtrl
- 
在 CRibbonImpl派生类中的声明:CRibbonItemGalleryCtrl<wID, uItems, uCategories = 0>m_Ctrl:uItems是画廊中的最大项数,uCategory是类别数。
- 
用户操作消息映射宏: RIBBON_GALLERY_CONTROL_HANDLER(wID, OnRibbonGalleryCtrl)
- 
处理程序原型: LRESULT OnRibbonGalleryCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)。如果uSel为UI_COLLECTION_INVALIDINDEX,则表示点击了 SplitButton Gallery 的 Button 部分。
CRibbonImpl::CRibbonItemGalleryCtrl 操作 Ribbon Drop-Down Gallery、In-Ribbon Gallery、Split Button Gallery,这些控件不具有 Type = "Commands" 属性。
| 键 | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_SelectedItem | Select(UINT uItem, bool bUpdate = false) | bool OnRibbonQuerySelectedItem(ID, UINT32& uSel) | 当前选择为 UI_COLLECTION_INVALIDINDEX | 返回 false(无变化) | 
| Item Key | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_Label | SetItemText(UINT uItem, LPCWSTR sText, bool bUpdate = false) | LPCWSTR OnRibbonQueryItemText(ID, UINT32 uItem) | 当前字符串为空 | 返回资源字符串,其 ID 为 ID + 1 + uItem(或 NULL) | 
| UI_PKEY_ItemImage | SetItemImage(UINT uIndex, HBITMAP hbm, bool bUpdate = false) | HBITMAP OnRibbonQueryItemImage(ID, UINT32 uItem) | 当前 HBITMAP 为 NULL | 资源位图的 HBITMAP,其 ID 为 ID + 1 + uItem(或 NULL) | 
示例:添加并处理一个 InRibbonGallery 控件
在标记文件中,声明新的 Tab、Group 和 ID_TEST_GALLERY 命令 ID,并在 Group 中声明一个 InRibbonGallery。
    <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>
在 resource.h 中为画廊项创建紧跟 ID_TEST_GALLERY 的 ID,以启用默认加载项位图。
//{{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
从 FirstRibbonBitmaps.zip 中提取 4 个形状位图到 FirstRibbon\res ,并使用适当的 ID 将位图及其标题字符串添加到 FirstRibbon.rc 。
// 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
在 CMainFrame 中,声明一个 CRibbonItemGalleryCtrl,其 ID 为 ID_TEST_GALLERY,包含 4 个项,2 个类别,并将其插入 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)
CRibbonImpl:: CRibbonItemGalleryCtrl 的默认初始值为类别名称为 'Category',所有项属于 Category 0,未选择任何项。
在 CMainFrame::OnCreate() 成员中初始化 m_shapes
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);
    // ...
或者,在特化的构造函数中
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);
}
当用户在画廊中选择或将鼠标悬停在某个项上时,CRibbonImpl 会发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 UI_EXECUTIONVERB,LOWORD(wParam) 设置为画廊 ID,lParam 设置为选定的项。
在 CMainFrame 中使用 RIBBON_GALLERY_CONTROL_HANDLER 宏处理此消息。
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;
}
示例:处理一个 DropDownGallery 控件
在标记文件中,将 InRibbonGallery 更改为 DropDownGallery ,并更改 Group 的 SizeDefinition。
        <Tab CommandName="TabGalleries">
            <Group CommandName="GroupGallery"  SizeDefinition="OneButton">
              <DropDownGallery CommandName="Gallery"
                              ItemHeight="32" ItemWidth="32"
                              TextPosition="Hide" />
            </Group>
          </Tab>
在初始化代码中,设置 Button Image 为最初选择的形状
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);
}
要将 Button Image 更新为当前选定的形状
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;
}
示例:处理一个 SplitButtonGallery 控件
在标记文件中,将 DropDownGallery 更改为 SplitButtonGallery。
          <Tab CommandName="TabGalleries">
          <Group CommandName="GroupGallery"  SizeDefinition="OneButton">
               <SplitButtonGallery CommandName="Gallery"
                              ItemHeight="32" ItemWidth="32" 
                              TextPosition="Hide" />
            </Group>
当 CRibbonItemGalleryCtrl 附加到 SplitButtonGallery 时,如果用户单击主 Button 部分,CRibbonImpl 会发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 UI_EXECUTIONVERB_EXECUTE,LOWORD(wParam) 设置为画廊 ID,lParam 设置为 UI_COLLECTION_INVALIDINDEX。
CMainFrame::OnGalleryShape() 必须处理这种情况
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
- 
在 CRibbonImpl派生类中的声明:CRibbonCommandGalleryCtrl<wID, uItems, uCategories = 0>m_Ctrl:uItems是画廊中的最大项数,uCategories是类别数。
- 
用户操作消息映射宏:对于画廊项 ID,使用 COMMAND_ID_HANDLER(nCmdID, OnCommand)。
- 
处理程序原型: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled);
CRibbonImpl::CRibbonItemGalleryCtrl 操作 Ribbon Drop-Down Gallery、In-Ribbon Gallery、Split Button Gallery,这些控件具有 Type = "Commands" 属性。
| Item Key | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_CommandId | SetItemCommand(uItem, ID, bool bUpdate = false) | UINT32 OnRibbonQueryItemCommand(ID, uItem) | 当前 CommandId 为 NULL | 返回 ID + 1 + uItem | 
| UI_PKEY_CommandType | SetItemCommandType(uItem, UI_COMMANDTYPE, bool bUpdate = false) | UI_COMMANDTYPE OnRibbonQueryItemCommandType(ID, uItem) | 当前 CommandType 为 UI_COMMANDTYPE_UNKNOWN | 返回 UI_COMMANDTYPE_ACTION | 
示例:实现一个 Command InRibbonGallery 控件
在标记文件中,将 SplitButtonGallery 声明为 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 的默认初始化将命令 ID 设置为 GalleryID + 1 + index,这些 ID 已在 FirstRibbon.h 中插入。
在 FirstRibbon.rc 中,插入一个具有 ID_TEST_GALLERY 的 Menu 资源,并更改形状的字符串资源
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
在 CMainFrame 中,声明一个 CRibbonCommandGalleryCtrl 并将其插入 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 需要与 m_shapes 相同的类别初始化(此处为 CMainFrame::OnCreate(),也可以在特化的构造函数中),SetItemCommandType(1, UI_COMMANDTYPE_BOOLEAN) 将索引为 1(ID_RECT)的项设置为 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);
CRibbonCommandGalleryCtrl 发布点击项 ID 的 WM_COMMAND 消息:使用 COMMAND_RANGE_HANDLER 宏处理它们。
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;
}
 
 
Simple Galleries 类
CRibbonToolbarGalleryCtrl
CRibbonToolBarGalleryCtrl 是一个从 Toolbar 资源构建的 Ribbon Command Gallery。没有类别处理,也没有回调到应用程序。
- 
在 CRibbonImpl 派生类中的声明: CRibbonToolBarGalleryCtrl<ID, IDToolBar, uItems>m_Ctrl:uItems是画廊中的项数。
- 
用户操作消息映射宏:对于工具栏资源 ID,使用 COMMAND_ID_HANDLER(nCmdID, OnCommand)。
- 
处理程序原型: LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled);
CRibbonImpl::CRibbonItemGalleryCtrl 操作 Ribbon Drop-Down Gallery、In-Ribbon Gallery、Split Button Gallery,这些控件具有 Type = "Commands" 属性。
| Item Key | 处理 | 
|---|---|
| UI_PKEY_CategoryId | 返回 UI_COLLECTION_INVALIDINDEX | 
| UI_PKEY_CommandId | 返回 IDToolBar 资源命令 ID 在 uItem 位置。 | 
| UI_PKEY_CommandType | 返回 UI_COMMANDTYPE_ACTION | 
示例:实现一个 InRibbonGallery CRibbonToolbarGalleryCtrl。
在标记文件中
- 
声明一个名为 ToolbarGallery 的 Ribbon Command,ID 为 ID_RIBBON_TOOLBAR,
- 
将其作为 InRibbonGallery 添加到 TabGalleries Tab 中,类型为 Commands。 
<!--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>
在 CMainFrame 中,声明一个 CRibbonToolbarGalleryCtrl,使用 IDR_MAINFRAME 工具栏资源,包含 **7** 个元素(Ribbon 不会将 ID_APP_ABOUT 插入 Command Gallery,因为它与 Ribbon.HelpButton 设置冲突),并将其插入 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 不需要初始化,并且包含的命令已得到处理,因此编译并运行。

示例:实现一个 DropDownGallery CRibbonToolbarGalleryCtrl。
在标记文件中,将 TabGalleries Tab 的 SizeDefinition 更改为容纳两个画廊
- 
前面的 InRibbonGallery Gallery 
- 
ToolbarGallery 作为 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>
从 FirstRibbonBitmaps.zip 中提取 Tools.bmp 到 FirstRibbon\res ,并以 ID_RIBBON_TOOLBAR ID 添加到 FirstRibbon.rc 。
ID_RIBBON_TOOLBAR BITMAP "res\\Tools.bmp"

CRibbonSimpleGalleryCtrl
CRibbonSimpleGalleryCtrl 是一个从资源集构建的 Ribbon(Command 或 Item)Gallery。没有类别处理,也没有回调到应用程序。
- 
在 CRibbonImpl 派生类中的声明: CRibbonSimpleGalleryCtrl<ID, uItems, UI_COMMANDTYPE type = UI_COMMANDTYPE_ACTION>m_Ctrl。uItems是画廊中的项数。
- 
用户操作消息映射宏 
- 
Item Galleries 使用 RIBBON_GALLERY_CONTROL_HANDLER(ID, OnRibbonGalleryCtrl)
- 
Command Galleries 使用 COMMAND_ID_HANDLER(nCmdID, OnCommand)。
- 
处理程序原型 
- 
LRESULT OnRibbonGalleryCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled); 
- 
LRESULT OnCommand(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& bHandled); 
CRibbonImpl::CSimpleGalleryCtrl 操作 Ribbon Drop-Down Gallery、In-Ribbon Gallery、Split Button Gallery。
| 键 | 设置 | 处理 | 
|---|---|---|
| UI_PKEY_SelectedItem | Select(UINT uItem, bool bUpdate = false) | 返回 0 或上次选择的项 | 
| Item Key | 处理 | 
|---|---|
| UI_PKEY_CategoryId | 返回 UI_COLLECTION_INVALIDINDEX | 
| UI_PKEY_Label | 返回字符串资源,ID 为 ID + 1 + uItem(或 NULL) | 
| UI_PKEY_ItemImage | 资源位图的 HBITMAP,ID 为 ID + 1 + uItem(或 NULL) | 
| UI_PKEY_CommandId | 返回 ID + 1 + uItem | 
| UI_PKEY_CommandType | 返回 t_CommandType | 
示例:实现一个 Command CRibbonSimpleGalleryCtrl。
在 CMainFrame 中,声明一个 CRibbonSimpleGalleryCtrl,ID 为 ID_TEST_GALLERY,包含 4 个项,并将其插入 RIBBON_CONTROL_MAP(从映射中移除 m_cmdshapes)。
// 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 已在标记文件中声明为 Command Gallery,只需编译并运行。

示例:实现一个 Item CRibbonSimpleGalleryCtrl。
在标记文件中,移除 Gallery 声明中的 Type = "Commands" 行。
        <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>
在 FirstRibbon.rc 中,将字符串还原为用于形状 Item Gallery 的状态。
STRINGTABLE 
BEGIN
    ID_DIAMOND              "Diamond"
    ID_RECT                 "Rectangle"
    ID_ROUNDRECT            "Round Rectangle"
    ID_ELLIPSE              "Ellipse"
END

CRibbonComboCtrl
- 
在 CRibbonImpl 派生类中的声明: CRibbonComboCtrl<wID, uItems, uCategories = 0>m_Ctrl。uItems是组合框中的最大项数,uCategories是类别数。
- 
用户操作消息映射宏: RIBBON_COMBO_CONTROL_HANDLER(wID, OnRibbonComboCtrl)
- 
处理程序原型: LRESULT OnRibbonComboCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled)
CRibbonImpl::CRibbonComboCtrl 操作 Ribbon Combo Box 控件,处理 CRibbonItemGalleryCtrl 的 key 并
| 键 | 获取 | 设置 | 
|---|---|---|
| UI_PKEY_StringValue | LPCWSTR GetComboText() | SetComboText(LPCWSTR sText) | 
示例:添加并处理一个组合框控件
在标记文件中,声明 Tab、Group 和 Command ID,并在 Group 中声明一个 ComboBox。
    <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>
在 CMainFrame 中,声明一个 CRibbonComboCtrl,ID 为 ID_TEST_COMBO,包含 6 个项,2 个类别,并将其插入 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)
CRibbonImpl::CRibbonComboCtrl 的默认初始值为类别名称为 'Category',所有项属于 Category 0,未选择任何项。
要初始化 m_combo,请重写相关的 CRibbonImpl::OnRibbonQueryxxx() 成员。
// 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;
}
您也可以像前面所示,在 CMainFrame::OnCreate() 中初始化 m_combo。
当用户在组合框中选择或将鼠标悬停在某个项上时,CRibbonImpl 会发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 UI_EXECUTIONVERB,LOWORD(wParam) 设置为组合框 ID,lParam 设置为选定的项。
在 CMainFrame 中使用 RIBBON_COMBO_CONTROL_HANDLER 宏处理此消息。
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;
}

CRibbonSimpleComboCtrl
CRibbonSimpleComboCtrl 是一个从资源集构建的 Ribbon 组合框。没有类别处理,也没有回调到应用程序。
- 
在 CRibbonImpl 派生类中的声明: CRibbonSimpleComboCtrl<ID, uItems>m_Ctrl。uItems是组合框中的项数。
- 
用户操作消息映射宏: RIBBON_COMBO_CONTROL_HANDLER(ID, OnRibbonComboCtrl)。
- 
处理程序原型: LRESULT OnRibbonComboCtrl(UI_EXECUTIONVERB verb, WORD wID, UINT uSel, BOOL& bHandled);
- 
CRibbonImpl::CRibbonSimpleComboCtrl操作 Ribbon Combo Box 控件。
示例:处理一个 CRibbonSimpleComboCtrl 组合框控件
在 Markup 文件中,声明一个 ID 为 ID_RIBBON_SIMPLECOMBO 的 Command,并将其插入 Galleries Tab。
<Command Name="SimpleCombo" Symbol="ID_RIBBON_SIMPLECOMBO" 
             Id="300" 
             LabelTitle ="Simple Combo" />
<Group CommandName="GroupCombo">
    <ComboBox CommandName="SimpleCombo" />
</Group>
在 FirstRibbon.h 中,声明一些紧跟 ID_TEST_COMBO 的标识符。
#define ID_COMBO1 301 #define ID_COMBO2 302 #define ID_COMBO3 303 #define ID_COMBO4 304 #define ID_COMBO5 305
在 FirstRibbon.rc 中,添加匹配的字符串资源。
STRINGTABLE 
BEGIN
    ID_COMBO1                "Simple 1"
    ID_COMBO2                "Simple 2"
    ID_COMBO3                "Simple 3"
    ID_COMBO4                "Simple 4"
    ID_COMBO5                "Simple 5"
END
在 CMainFrame 中,声明一个 CRibbonSimpleComboCtrl,包含 5 个项且 ID 相同,并将其插入 RIBBON_CONTROL_MAP,重用 OnGalleryShape() 处理程序以使用 RIBBON_COMBO_CONTROL_HANDLER 宏处理选择消息。
// 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)

CRibbonRecentItemsCtrl
- 
在 CRibbonImpl 派生类中的声明: CRibbonRecentItemsCtrl<ID, class TDocList = CRecentDocumentList>m_Ctrl。TDocList是任何派生自WTL::CRecentDocumentListBase的类。
CRibbonImpl:: CRibbonRecentItemsCtrl 操作 Ribbon Recent Items 控件。
| Item Key | 设置 | 可覆盖的回调 | 回调条件 | 默认处理 | 
|---|---|---|---|---|
| UI_PKEY_LabelDescription | TDocList 成员 | 返回 TDocList::m_arrDocs[uItem].szDocName | ||
| UI_PKEY_Label | LPCWSTR OnRibbonQueryRecentItemName(LPCWSTR sPath) | 总是 | 返回 ::PathFindFileName(sPath) | 
示例:添加并处理一个 Ribbon Recent Items
在 Markup 文件中,声明一个 RecentFiles Command,并在 Application.Menu 部分插入一个使用它的 RecentItems。
    <!--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>
在 CMainFrame 中添加
// Ribbon controls
CRibbonRecentItemsCtrl<ID_RIBBON_RECENT_FILES> m_recent;
// Ribbon control map
BEGIN_RIBBON_CONTROL_MAP(CMainFrame)
    RIBBON_CONTROL(m_recent)
RecentItems Ribbon 控件现在通过 WTL::CRecentDocumentList m_recent 成员进行操作。
为 ID_FILE_OPEN 添加消息映射条目和一个匹配的 CMainFrame::OnFileOpen() 成员。
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;
}
编译,运行并“打开”一些文件。

其他 Ribbon 功能处理
Ribbon 应用程序模式
CRibbonImpl 成员:HRESULT SetRibbonModes(INT32 iModes);
Ribbon 上下文菜单
CRibbonImpl 成员
- 
bool HasRibbonMenu(UINT32 uID)返回true如果uID是 Ribbon Context Popup ID。
- 
HRESULT TrackRibbonMenu(UINT32 uID, INT32 x, INT32 y)在屏幕坐标 (x, y) 处弹出上下文菜单。
- 
HRESULT TrackRibbonMenu(UINT32 uID, LPARAM lParam)从WM_CONTEXTMENUlParam 中提取 (x, y) 坐标并调用TrackRibbonMenu(uID, x, y)。
示例:实现一个 Ribbon 上下文菜单
在 Markup 文件中,声明 ContextMap、ContextMenu 和 MiniToolbar Commands,并在 Application.Views 部分插入一个 ContextPopup 描述。
<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>
在 CMainFrame 中添加一个 WM_CONTEXTMENU 处理程序,以及
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;
}

Ribbon 上下文选项卡
| 键 | 获取 | 设置 | 
|---|---|---|
| UI_PKEY_ContextAvailable | UI_CONTEXTAVAILABILITY GetRibbonContextAvail(ID) | SetRibbonContextAvail(ID, UI_CONTEXTAVAILABILITY cav) | 
示例:添加并处理一个 Ribbon 上下文选项卡
<!-- 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>  
在 CMainFrame::OnIdle() 中
virtual BOOL OnIdle()
{
    // ...        
    SetRibbonContextAvail(ID_GROUP_CONTEXT, m_view.HasSelection() ? 
        UI_CONTEXTAVAILABILITY_ACTIVE :
        UI_CONTEXTAVAILABILITY_NOTAVAILABLE);
}

Ribbon 颜色
| 键 | 获取 | 设置 | 
|---|---|---|
| UI_PKEY_GlobalBackgroundColor UI_PKEY_GlobalHighlightColor UI_PKEY_GlobalTextColor | UI_HSBCOLOR GetRibbonColor(key) | bool SetRibbonColor(key, UI_HSBCOLOR color) | 
示例:处理 Ribbon 颜色
在 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;
}

QAT(快速访问工具栏)停靠
| 键 | 获取 | 设置 | 
|---|---|---|
| UI_PKEY_QuickAccessToolbarDock | UI_CONTROLDOCK GetQATDock() | bool SetQATDock(UI_CONTROLDOCK dockState) | 
示例:处理快速访问工具栏停靠
在 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;
}
Ribbon 可视性和大小
| 键 | 获取 | 设置 | 
|---|---|---|
| UI_PKEY_Viewable | bool IsRibbonHidden() | HideRibbon(bool bHide = true) | 
| UI_PKEY_Minimized | bool IsRibbonMinimized() | MinimizeRibbon(bool bMinimize = true) | 
示例:处理 Ribbon 大小
在 CMainFrame::OnFileNew() 中
LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI())
    {
        MinimizeRibbon(!IsRibbonMinimized());
    }
    return 0;
}

Ribbon 持久性
从 Ribbon 切换到传统 UI 时
- 
所有 CRibbonImpl控件不受影响。仅限 Ribbon 的 UpdateUI ID 被销毁。要保留它们,请在 Ribbon 销毁前调用UIPersistElement(ID)来保存它们的文本、启用状态和选中状态。
- 
HRESULT将 Ribbon 设置(Ribbon 大小和颜色、QAT 位置和内容)保存在CRibbonImpl::SaveRibbonSettings()HGLOBAL m_hgRibbonSettings
从传统 UI 切换到 Ribbon UI 时
- 
如果 m_hgRibbonSettings不为NULL,则 Ribbon 设置已持久化,并通过HRESULT CRibbonImpl::RestoreRibbonSettings()恢复。
- 
新创建的 Ribbon 会为每个 CommandId 和相应的 Property Keys 调用 CRibbonImpl::UpdateProperty()。CRibbonImpl返回存储的值,Ribbon 将恢复到之前的状态。
对于会话之间的 Ribbon 设置持久性,助手类 CRibbonPersist 公开了将 Ribbon 设置保存和恢复到 HKCU 下的注册表项的成员。
- 
CRibbonPersist(LPCWSTR sAppKey)使用注册表项名称构造CRibbonPersist对象。
- 
LONG CRibbonPersist::Save(bool bRibbonUI, HGLOBAL hgSettings = NULL)将一个bool和一个HGLOBAL保存到注册表中。
- 
LONG CRibbonPersist::Restore(bool& bRibbonUI, HGLOBAL& hgSettings)恢复保存的bool和HGLOBAL。
MTPad7 在 CMainFrame::OnCreate() 中使用它们来以最后一个 UI 和最后一个 Ribbon 设置重新打开……
    // Ribbon UI state and settings restoration
    CRibbonPersist(lpcstrMTPadRegKey).Restore(bRibbonUI, m_hgRibbonSettings);
……这些设置已由 CMainFrame::OnClose() 保存。
     bool bRibbonUI = IsRibbonUI();
     if (bRibbonUI)
        SaveRibbonSettings();
     CRibbonPersist(lpcstrMTPadRegKey).Save(bRibbonUI, m_hgRibbonSettings);
使用 Ribbon UI 重新审视 MTPad
MTPad 是最早的 WTL 示例之一,在本文中有详细描述。重新审视并没有改变任何现有功能。Ribbon Spinner 在预览模式下增加了直接页面选择。
项目设置已按初次体验的步骤 1 和 2 进行了更改。
7 个位图已添加到 MTPad.rc。
CMainFrame 更改
从 CRibbonFrameWindowImpl 派生。
class CMainFrame : 
        public CRibbonFrameWindowImpl<CMainFrame>
#ifndef _WIN32_WCE
        , public CPrintJobInfo
#endif // _WIN32_WCE
{
添加 ribbon 控件声明和控件映射。
    // 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()
处理 Ribbon 查询。
    // 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;
    }
处理 Ribbon 命令。
BEGIN_MSG_MAP(CMainFrame)
// ...
    RIBBON_SPINNER_CONTROL_HANDLER(ID_PAGE_SPINNER, OnSpinnerPage)
    COMMAND_ID_HANDLER(ID_VIEW_RIBBON, OnViewRibbon)
在 OnCreate(). 中初始化 UI 设置、Ribbon 控件并恢复 Ribbon 设置。
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;
}
在 OnClose(). 中保存 Ribbon 设置。
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;
}
在 OnViewRibbon(). 中实现 UI 切换。
LRESULT OnViewRibbon(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) 
{
    ShowRibbonUI(!IsRibbonUI(), UI_MAKEAPPMODE(prev.IsWindow())); 
    UISetCheck(ID_VIEW_RIBBON, IsRibbonUI());
    return 0; 
} 
在 OnContextMenu(). 中实现 Ribbon 上下文弹出菜单。
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
        }
在 OnFilePrintPreview(). 中设置 Ribbon 应用程序模式 1 和 Page Group 标签。
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;
}
在 OnPrintPreviewClose(). 中设置 Ribbon 应用程序模式 0。
LRESULT OnPrintPreviewClose(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    if (IsRibbonUI())
        SetRibbonModes(UI_MAKEAPPMODE(0));
}
在 OnPrintPreviewForward() 和 OnPrintPreviewBack(). 中更新 Ribbon Spinner 值。
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;
}
在新的 OnSpinnerPage() 成员中处理直接预览页面选择。
LRESULT OnSpinnerPage(WORD /*wID*/, LONG lVal, BOOL& /*bHandled*/)
{
    prev.SetPage(lVal - 1);
    prev.Invalidate();
        return 0;
}
CEditView 更改
处理 Ribbon Font Control 消息。
BEGIN_MSG_MAP(CEditView)
// ...
#ifndef _WIN32_WCE
    // ...
    RIBBON_FONT_CONTROL_HANDLER(ID_RIBBON_FONT, OnRibbonFont)
    
预览悬停的字体,在预览取消时恢复到内部 m_font 成员,当字体通过 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);
}
结论
鉴于 Ribbon UI 有些争议,双 UI 应用程序可能既能满足喜爱 Ribbon 的用户,也能满足不喜欢 Ribbon 的用户。使用 WTL 库,您可以获得两全其美。
祝好!
修订历史
- 2010 年 1 月 26 日:发布
- 2010 年 1 月 28 日:更新- atlribbon.h:修复了多个实例问题。
- MTPad7:正确处理 Font Control UI_EXECUTIONVERB_CANCELPREVIEW。
- 文章:一些拼写错误和错误的链接
 
- 2010 年 2 月 19 日:更新
- 修复了 Win7 非 Aero 调整大小错误:RibbonUI.aspx?msg=3372329#xx3372329xx 并更新了下载。
- 2010 年 4 月 13 日:atlribbon.h 和 MTPad7 更新
- 更好地处理 UI 切换,
- 修复了 Vista Basic 切换到传统 UI 时的尺寸问题,
- 修复了 IUIImage 泄露,
- 上下文选项卡默认初始化为 UI_CONTEXTAVAILABILITY_NOTAVAILABLE。



 
 

