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

Windows 7 Ribbon: 时候到了,您的 Win32 应用程序将发生改变

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (46投票s)

2009年1月22日

CPOL

9分钟阅读

viewsIcon

177438

downloadIcon

4381

了解如何在 Windows 7 中使用新的 Ribbon。

3.jpg

引言

在 Office 2007 引入流畅的 Ribbon 后,市面上出现了许多昂贵的商业解决方案。但最终,Microsoft 在 Windows 7 中发布了原生的 Ribbon 控件。本文将解释我首次尝试在我的两个项目中使用 Ribbon 的经历,它们是我的轻量级 TurboIRC 和我的重量级 Turbo Play 的乐谱编辑器,这将帮助您快速在您的应用程序中使用 Ribbon。文中还包含一个可立即运行的示例项目。

特点

  • 将 Ribbon ActiveX 封装在一个简单的 C++ 接口中
  • 支持 x86/x64

您需要

可视化绘制

如果您不想处理 XML,或者需要一个快速的可视化设计工具,您可以尝试我的 Visual Ribbon Creator,它将为您创建 Ribbon。

概述

Ribbon 是一个附加到窗口的 ActiveX 对象。它占据窗口的整个宽度,其高度可配置。它在需要时会自动调整大小,因此您无需在 WM_SIZE 中处理任何事情。默认情况下,Ribbon 会移除菜单,但如果需要,您可以稍后重新插入它。

4.jpg

5.jpg

Ribbon 从 XML 获取其显示数据。这意味着您只需使用几个函数调用,一些用于初始化和加载数据,以及一个事件处理程序以在 Ribbon 发生变化时接收通知。

由于 Ribbon 是一个 ActiveX 接口,使用它的应用程序在 XP 下初始化时会失败,但仍会继续运行。

当您创建 XML 后(请参阅下文),您将其传递给 uicc.exe,它会创建 .BIN.H.RC 文件。只需将 .RC 文件包含在您的 RC 项目中,所需的内容(Ribbon RC 和 H 中定义的二进制文件和内容)就会被包含进来。

初始化

  • 您创建一个 IUIFramework
  • IUIFramework* u_f = 0;
    HRESULT hr = CoCreateInstance(CLSID_UIRibbonFramework,
                 0,CLSCTX_ALL,__uuidof(IUIFramework),(void**)&u_f);
  • 您实现一个 IUIApplication
  • 此接口(除了 IUnknown 成员)具有以下三个函数

    • 当命令创建时从 Ribbon 调用
    • virtual HRESULT __stdcall OnCreateUICommand(UINT32 commandId,
              UI_COMMANDTYPE typeID,IUICommandHandler **commandHandler);
    • 当命令销毁时从 Ribbon 调用
    • virtual HRESULT __stdcall OnDestroyUICommand(UINT32 commandId,
              UI_COMMANDTYPE typeID,IUICommandHandler *commandHandler);
    • 当 Ribbon 视图发生改变时调用
    • virtual HRESULT __stdcall OnViewChanged(UINT32 viewId,UI_VIEWTYPE typeID,
              IUnknown *view,UI_VIEWVERB verb,INT32 uReasonCode);
  • 您调用 IUIFramework::Initialize(),传入 Ribbon 的父窗口和您的 IUIApplication 实现。
  • 您调用 IUIFramework::LoadUI(),传入 HINSTANCE 和资源名称。

Ribbon 现在显示了!

命令处理器

  • OnCreateUICommand 会为每个“命令”调用。请参阅下文 XML 格式中什么是命令。您必须返回一个 IUICommandHandler 的实现(AddRef()+!),它将通过两个成员函数处理该命令
    • 当命令的值发生改变时调用 - 仍在待定文档中
    • HRESULT __stdcall UpdateProperty(UINT32 commandId,REFPROPERTYKEY key,
              const PROPVARIANT *currentValue,PROPVARIANT *newValue);
    • 当命令执行时调用。“verb”是 UI_EXECUTIONVERB 枚举中的一个,它通知您发生了哪种事件,例如,字体选择下拉菜单、按钮点击等。
    • HRESULT __stdcall Execute(UINT32 commandId,UI_EXECUTIONVERB verb,
              const PROPERTYKEY *key, const PROPVARIANT *currentValue,
              IUISimplePropertySet *commandExecutionProperties)
  • 当接口不再需要时,OnDestroyUICommand 会被调用,但请勿调用 Release()。Ribbon 会调用它。这只是一个通知您对象即将被销毁的通知,而不是销毁它本身的请求。
  • OnViewChanged 允许您从 IUIFramework 请求一个接口并查询其状态。例如,如果 typeId == UI_VIEWTYPE_RIBBONverb == UI_VIEWVERB_CREATEUI_VIEWVERB_SIZE,这表明 Ribbon 已创建或已调整大小(注意,在 IUIFramework::LoadUI() 返回时 Ribbon 尚未创建),因此您可以查询 IUnknown 指针以获取 IUIRibbon 并获取其高度。

使用我的库

为了避免上述所有操作,我的库封装了这些接口。有一个 RIBBON 类,您按如下方式使用它

  • RIBBON(HWND hh = 0);
  • ~RIBBON();
  • bool Initialize();
  • Ribbon 初始化成功返回 true

  • bool LoadMarkup(HINSTANCE hInst,LPCWSTR resourceName);
  • 从资源加载 Ribbon。如果已加载 Ribbon,则销毁它。

  • void DestroyMarkup();
  • 销毁已加载的 Ribbon。

我的库向您的父窗口发送一个预定义的 MESSAGE_RIBBON (const int MESSAGE_RIBBON = RegisterWindowMessage(L"{E733E4DA-904C-486b-B5FB-6201773D69DE}");),其中 WPARAM 设置为 RIBBON* 类,LPARAM 设置为 RIBBON_MESSAGE 结构

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

因此,如果您只想将消息重定向到 WM_COMMAND,您需要检查 update == falseverb == UI_EXECUTIONVERB_EXECUTEtype == UI_COMMANDTYPE_ACTION

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

理解 Ribbon 数据的 XML 格式

因为 Ribbon 本质上是 XML 映射的表示,所以您将在这里花费大部分时间来开发一个美观且功能正常的 Ribbon。

一个 Ribbon 基本上包含以下元素

  • 一组命令,每个命令都可以有 ID、符号、标签、工具提示和一组图像。您为“按钮”和其他元素(如选项卡或组)定义这些命令。
  • 一组元素,包括“应用程序菜单”、“快速访问工具栏”和包含多个选项卡的“Ribbon”。每个选项卡可以有一些组,每个组可以有一些预定义的控件。这些组必须标有特定的“大小”和布局;例如,如果您想在一个组中放置六个按钮,您只有三种预定义的方式来排列它们。

以下是一组示例命令

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

请注意,“按钮”命令需要图像(小型 16x16 或大型 32x32,或 64x64,取决于 DPI,但我发现 Ribbon 可以很好地调整大图像的大小),但其他“命令”,如选项卡或组的标签,只需要名称和标题,因为它们没有关联的命令 ID。

以下是如何创建“应用程序菜单”

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

现在,您有一个包含三个按钮的应用程序菜单:新建、打开、保存。

以下是如何创建快速访问工具栏

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

现在,您有一个快速访问工具栏,其中包含 cmdNew,以及一个由“cmdCustomize”定义的按钮,该按钮应该能够自定义快速访问工具栏。请注意,Ribbon 允许用户更改快速访问工具栏中显示的按钮。

以下是如何创建带有一些组的选项卡

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

现在请注意。选项卡引用了命令“Tab1”,其中包含选项卡的名称(他们不直接允许 <Tab Label="x" />,这很奇怪,也许他们以后可能想将图像与选项卡关联)。此选项卡有四个组,并且每个组都有一个缩放策略。组的缩放策略不是任意设置为“Large”、“Medium”或“Small”,而是取决于控件数量和布局模板(在 MSDN 这里 描述)。这意味着如果您的组是“OneButton”,它必须设置为“Large”缩放大小。

有预定义数量的模板,但您也可以使用 <SizeDefinition> 进行自定义模板。您可以在我的“g5”组中查看使用自定义模板的示例。

在缩放策略之后,这里是组定义以及 MSDN 此处 描述的布局模板。现在,每个组都可以包含许多内容,包括按钮、微调按钮、下拉菜单、字体控件、颜色选择器、对话框启动器、分隔符以及 标记元素 页面中描述的所有内容。我上面的四个组有一些按钮、一个字体选择器和一个颜色选择器。

我们如何从颜色选择器获取值?检查 type == UI_COMMANDTYPE_COLORANCHORPROPVARIANTcv”值包含一个表示 RGB 的整数。

我们如何从字体选择器获取值?typeUI_COMMANDTYPE_FONTPROPVARIANTcv”值包含一个 IUnknown,但我还没有找到如何从中获取字体!

应用程序模式

根据您的应用程序上下文,您可能需要一些选项卡和/或组可见或不可见。Ribbon 不会为每个组/选项卡显式执行此操作,而是提供了“应用程序模式”,这是一个 32 位位集,表示它们应该“激活”的模式。应用程序模式适用于组和选项卡。

例如,这是“Tab1”的定义

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

这意味着当当前选定模式中的位 0 或位 2 被设置时,选项卡就会显示。因此,当我调用 Ribbon::SetModes(0) 时,此选项卡将被隐藏。当我使用 2 或 8 或任何具有位 0 或位 2 设置的整数时,该选项卡将显示。

同样的方法也可以很容易地应用于组。

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

上下文选项卡

这些选项卡仅在您的应用程序需要时出现。将它们用于特定于上下文的应用程序 - 例如,Turbo Play 仅在加载 MIDI 轨道时显示乐谱编辑器。

要指定上下文选项卡,请在 Ribbon 中的特殊标题后添加 <Tab> 元素

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

如您所见,必须有一个选项卡组,其中包含所有需要同时可见的上下文选项卡。

要显示上下文选项卡,您必须使 Ribbon 上下文选项卡组失效

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

然后 Ribbon 会发送一个通知,以重新配置要显示的上下文选项卡

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

属性键

为了获取/设置任何 Ribbon 控件的状态,您可以使用 MSDN 此处 文档中说明的属性键。您可以选择使用 IUIFramework::GetUICommandProperty 来获取属性键,或者您可以查询传入的 RIBBON_MESSAGE 结构的“cv”IUnknown 成员以获取 IPropertyStore;使用 GetValue() / SetValue() / Commit 来读取/写入引用控件的属性。还有一些“全局”键,您可以直接向 IUIFramework 查询 IPropertyStore

例如,当我的 WndProc 收到颜色选择的通知时,它会按如下方式将其应用于 Ribbon 的背景颜色

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

对于字体控件,您可以直接查询 IUnknown* 指针以获取 IPropertyStore,并使用 UI_PKEY_FontProperties_XXXX 键。

Ribbon 实现的当前限制

  • Ribbon 位图只能是 32 位 BMP 文件。尚不支持 JPG/PNG/其他 BMP 格式。
  • Ribbon 不允许您初始化、调整大小、重新排列、删除或以其他方式与其成员交互,除了 XML 配置 - 例如,您尚不能指定启动选项卡或以编程方式更改选项卡。此外,对于非命令,例如选项卡选择更改,不提供通知。
  • Ribbon 位图必须包含在包含 .rc 文件的同一模块中。
  • 尚无多语言支持(您必须单独定义多个语言的 Ribbon)。
  • Ribbon 不允许托管除其预定义控件以外的任何控件,因此您无法将自己的 HWND 添加到 Ribbon 中。

当 Microsoft 考虑这些问题时,我将修改本文以包含新功能!

致谢

特别感谢这些 Microsoft 员工指导我理解 Ribbon 内部原理

  • Ryan Demopoulos
  • Nicolas Brun

历史

  • 2009-11-20:Win 7 最终更新,添加了上下文选项卡并添加了 Visual Ribbon Creator 参考工具
  • 2009-05-07:文章更新,包含 RC1 更新
  • 2009-02-05:添加了属性键、应用程序模式和字体检测
  • 2009-01-23:首次发布
© . All rights reserved.