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






4.84/5 (46投票s)
了解如何在 Windows 7 中使用新的 Ribbon。

引言
在 Office 2007 引入流畅的 Ribbon 后,市面上出现了许多昂贵的商业解决方案。但最终,Microsoft 在 Windows 7 中发布了原生的 Ribbon 控件。本文将解释我首次尝试在我的两个项目中使用 Ribbon 的经历,它们是我的轻量级 TurboIRC 和我的重量级 Turbo Play 的乐谱编辑器,这将帮助您快速在您的应用程序中使用 Ribbon。文中还包含一个可立即运行的示例项目。
特点
- 将 Ribbon ActiveX 封装在一个简单的 C++ 接口中
- 支持 x86/x64
您需要
- Windows 7
- Windows 7 SDK
- MSDN 中的 Ribbon 文档
可视化绘制
如果您不想处理 XML,或者需要一个快速的可视化设计工具,您可以尝试我的 Visual Ribbon Creator,它将为您创建 Ribbon。
概述
Ribbon 是一个附加到窗口的 ActiveX 对象。它占据窗口的整个宽度,其高度可配置。它在需要时会自动调整大小,因此您无需在 WM_SIZE
中处理任何事情。默认情况下,Ribbon 会移除菜单,但如果需要,您可以稍后重新插入它。

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);
此接口(除了 IUnknown
成员)具有以下三个函数
- 当命令创建时从 Ribbon 调用
virtual HRESULT __stdcall OnCreateUICommand(UINT32 commandId,
UI_COMMANDTYPE typeID,IUICommandHandler **commandHandler);
virtual HRESULT __stdcall OnDestroyUICommand(UINT32 commandId,
UI_COMMANDTYPE typeID,IUICommandHandler *commandHandler);
virtual HRESULT __stdcall OnViewChanged(UINT32 viewId,UI_VIEWTYPE typeID,
IUnknown *view,UI_VIEWVERB verb,INT32 uReasonCode);
IUIApplication
实现。HINSTANCE
和资源名称。Ribbon 现在显示了!
命令处理器
OnCreateUICommand
会为每个“命令”调用。请参阅下文 XML 格式中什么是命令。您必须返回一个IUICommandHandler
的实现(AddRef()
+!),它将通过两个成员函数处理该命令- 当命令的值发生改变时调用 - 仍在待定文档中
HRESULT __stdcall UpdateProperty(UINT32 commandId,REFPROPERTYKEY key,
const PROPVARIANT *currentValue,PROPVARIANT *newValue);
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_RIBBON
且 verb == UI_VIEWVERB_CREATE
或 UI_VIEWVERB_SIZE
,这表明 Ribbon 已创建或已调整大小(注意,在 IUIFramework::LoadUI()
返回时 Ribbon 尚未创建),因此您可以查询 IUnknown
指针以获取 IUIRibbon
并获取其高度。使用我的库
为了避免上述所有操作,我的库封装了这些接口。有一个 RIBBON
类,您按如下方式使用它
RIBBON(HWND hh = 0);
~RIBBON();
bool Initialize();
bool LoadMarkup(HINSTANCE hInst,LPCWSTR resourceName);
void DestroyMarkup()
;
Ribbon 初始化成功返回 true
。
从资源加载 Ribbon。如果已加载 Ribbon,则销毁它。
销毁已加载的 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 == false
、verb == UI_EXECUTIONVERB_EXECUTE
和 type == 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_COLORANCHOR
,PROPVARIANT
“cv
”值包含一个表示 RGB 的整数。
我们如何从字体选择器获取值?type
是 UI_COMMANDTYPE_FONT
,PROPVARIANT
“cv
”值包含一个 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:首次发布