65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (71投票s)

2010 年 1 月 23 日

CPL

29分钟阅读

viewsIcon

256316

downloadIcon

4850

包含 Ribbon UI 实现指南,附带示例和双 UI 启用的遗留应用程序

引言

Windows 7 引入了新的 Ribbon 控件,系统更新使其可用于 Vista 和 Windows Server 2008 平台。

本文从 C++ 应用程序开发者的角度出发,重点介绍如何使用 atlribbon.h 和 WTL 8 来改编现有或新应用程序,使用户能够在 Ribbon 或传统 UI 之间进行可逆的选择。

参考文献

关于 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 分发版中。

  • WTL 8.0.

  • 对于 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 #include uicc 生成的 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; 
    } 
  1. CRibbonFrameWindowImpl<CMainFrame> 继承自 WTL::CFrameWindowImpl<CMainFrame>(实现传统 UI),并继承自 WTL::RibbonUI::CRibbonImpl<CMainFrame>(实现 Ribbon UI)。因此,CMainFrame 是一个双 UI 窗口,在支持 Ribbon UI 的运行时环境中。

  2. bool WTL::RunTimeHelper::IsRibbonUIAvailable() 执行运行时测试,并在运行于已更新的 Vista 或 Win7 时返回 true。如果返回 false,则会移除 View->Ribbon 菜单项,以避免调用 Ribbon UI 代码。

  3. bool CRibbonImpl::IsRibbonUI() 返回 true 表示当前显示 Ribbon UI,否则返回 false。

  4. CRibbonImpl 继承自 WTL::CRibbonUpdateUI<CMainFrame>,它扩展了 本文 中描述的 WTL::CAutoUpdateUI<CMainFrame>,并处理额外的 UPDUI_RIBBON 类型。
    bool WTL::CAutoUpdateUI<CMainFrame>::UIAddMenu(HMENU hm, bool bSetText) 将 hm 菜单的所有(非弹出)菜单项添加到其 UPDATE_UI_MAP 中,并为每个菜单项 ID 和文本调用 UISetText(ID, text)

  5. 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_LabelUI_PKEY_EnabledUI_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 个步骤

  1. 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> 
  1. 实例化一个具有匹配 ID 的 CMainFrame 成员的 CRibbonImpl::CRibbonXXXCtrl。

CRibbonColorCtrl<ID_TEST_COLOR> m_color;
  1. 将成员插入 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 ButtonsDrop-Down ButtonsToggle ButtonsGroupsMenu GroupsTabsTab 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,
bool bUpdate = false)

COLORREF OnRibbonQueryColor(ID)

当前颜色为 MAGENTA

返回 0x800080 MAGENTA(无变化)

UI_PKEY_ColorType

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

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,
bool bUpdate = false)

LPCWSTR OnRibbonQueryColorLabel(ID, key)

当前字符串值为空

返回 NULL(无变化)

UI_PKEY_ThemeColors UI_PKEY_StandardColors

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

COLORREF* OnRibbonQueryColorArray(ID, key)

当前颜色数组为空

返回 NULL(无变化)

UI_PKEY_ThemeColorsTooltips UI_PKEY_StandardColorsTooltips

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

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_EXECUTIONVERBLOWORD(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_EXECUTIONVERBLOWORD(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:: CRibbonSpinnerCtrlCRibbonImpl:: CRibbonFloatSpinnerCtrl 都操作 Ribbon Spinner 控件并处理 V 为 LONGCRibbonSpinnerCtrl)或 DOUBLECRibbonFloatSpinnerCtrl

设置

可覆盖的回调

回调条件

默认处理

UI_PKEY_DecimalPlaces

SetDecimalPlaces(V vPlaces, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
或 OnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

总是

返回 false(无变化)

UI_PKEY_DecimalValue

SetVal(V vVal, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
或 OnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

总是

返回 false(无变化)

UI_PKEY_MinValue

SetMin(V vMin, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
或 OnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

总是

返回 false(无变化)

UI_PKEY_MaxValue

SetMax(V vMax, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
或 OnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

总是

返回 false(无变化)

UI_PKEY_Increment

SetIncrement(V vIncrement, bool bUpdate = false)

bool OnRibbonQuerySpinnerValue(ID, key, LONG*)
或 OnRibbonQueryFloatSpinnerValue(ID, key, DOUBLE*)

总是

返回 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 中设置值时

  • 对于 CRibbonSpinnerCtrlCRibbonImpl 发布一个 WM_COMMAND 消息,其中 HIWORD(wParam) 设置为 0,LOWORD(wParam) 设置为控件 ID,lParam 设置为 LONG 值。

  • 对于 CRibbonFloatSpinnerCtrl,CRibbonImpl 发布一个具有相同 wParam 的 WM_COMMAND 消息,lParam 设置为指向值的 DOUBLE*

……在 CMainFrame 中使用 RIBBON_SPINNER_CONTROL_HANDLERRIBBON_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::CRibbonSpinnerCtrlCRibbonImpl::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 控件。

完整的类 CRibbonItemGalleryCtrlCRibbonCommandGalleryCtrlCRibbonComboCtrl 处理类别、动态调整大小和项配置。

简单的类 CRibbonSimpleGalleryCtrlCRibbonSimpleComboCtrlCRibbonToolBarGalleryCtrl 不处理类别,并且在 Ribbon 创建时配置一次。

Ribbon 完整集合类

完整集合类的通用 Property Keys

设置

可覆盖的回调

回调条件

默认处理

UI_PKEY_ItemsSource

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

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)。如果 uSelUI_COLLECTION_INVALIDINDEX,则表示点击了 SplitButton Gallery 的 Button 部分。

CRibbonImpl::CRibbonItemGalleryCtrl 操作 Ribbon Drop-Down GalleryIn-Ribbon GallerySplit 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_EXECUTIONVERBLOWORD(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_EXECUTELOWORD(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 GalleryIn-Ribbon GallerySplit 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 GalleryIn-Ribbon GallerySplit 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 GalleryIn-Ribbon GallerySplit 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_EXECUTIONVERBLOWORD(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_CONTEXTMENU lParam 中提取 (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 CRibbonImpl::SaveRibbonSettings() 将 Ribbon 设置(Ribbon 大小和颜色、QAT 位置和内容)保存在 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) 恢复保存的 boolHGLOBAL

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 日:更新
  • 2010 年 4 月 13 日:atlribbon.h 和 MTPad7 更新
    • 更好地处理 UI 切换,
    • 修复了 Vista Basic 切换到传统 UI 时的尺寸问题,
    • 修复了 IUIImage 泄露,
    • 上下文选项卡默认初始化为 UI_CONTEXTAVAILABILITY_NOTAVAILABLE。
© . All rights reserved.