Introducing the Ribbon Framework
The Windows Ribbon control is a COM control and since it has a user interface you must initialize an STA (single threaded) apartment. The Windows Ribbon control is not an ActiveX control. This means that you do not have to provide an ActiveX control site, which simplifies considerably the code that you have to write in your application.
The Ribbon control uses adaptive layout. This means that the developer provides information about the controls that will be used and how they will be grouped, and at run time the Ribbon control determines the actual position of the controls.
To see the effect of adaptive layout you can run Windows 7 Paint and resize the window. This is shown in Figure 1. The top left image shows the Ribbon control with the View tab selected. At this width the items on the Ribbon control are shown full size. When the window width is reduced, the Ribbon control width is reduced and adaptive layout resizes the controls to enable all of them to be shown.
The bottom left image in Figure 1 shows the first change, that the Zoom group has compacted from a row of three buttons to a column of buttons. When the width is reduced further (bottom right, Figure 1) the Display group collapses to a column of buttons. At this size, there is no space to show the Customize Quick Access Toolbar button on the title bar, so instead there is a single button labeled .. and when you click on this button the toolbar pops up. The most compact arrangement (top right, Figure 1) collapses the Zoom group to a drop-down menu. If the window width is reduced further, the items on the Ribbon control cannot be shown and it disappears completely.
Adaptive layout is a consequence of the separation of presentation and logic. You design the user interface (UI) with a design tool that generates XAML, compile this to a BML file, and bind it to the application as a UIFILE resource. You do not have to write layout code, resizing code, child control creation, or initialization code. All of this is provided by the Ribbon control based on the markup information in the BML file, which is passed to the control when it is first initialized. The commands on the Ribbon control generate command messages, so you have to write code to handle these command messages. To do this you create a COM object called a command handler object.
The XAML for the presentation is made up of two sections. One section defines the command names including a name for use in the XAML and a unique ID used to identify the command in the code. The command section also allows you to define command specific properties like a label, a tooltip, or image. The other section provides information about the controls that generate the command messages. More than one control can generate a command, and the practice of defining the commands separate from the controls means that all controls that generate a command will have the same label, tooltip, image, and so on. This is illustrated in Figure 2 where you can see that the Save menu item displays a tooltip, a label, and a large icon. The Save item on the Quick Access Toolbar displays a small icon and no label on the toolbar, however, the toolbar shows the same tooltip as the menu item and the same label is used on the quick access toolbar customize menu, indicating that the two items are associated with the same command.
Controls are not created on their own, instead they are hosted in a container called a view and they may be grouped together. The Ribbon control API supports two types of view:Ribbon View and ContextPopup View. The Ribbon View contains the application menu, tabs, and the Quick Access Toolbar; the ContextPopup View supports context menus and mini toolbars. These containers can have individual controls or have groups of controls. Grouping together controls helps the user by categorizing controls that perform similar tasks, and it helps the Ribbon control adaptive layout as shown in Figure 1.
Each control will have properties that can be accessed at runtime and define the behavior of the control. Some of these properties can be set in the markup XAML code. For example the Button control has properties for the label, tooltip, and icon that are provided by the command associated with the button. The Ribbon control framework provides access to property values through code.
Adding A Ribbon Resource
A Windows Ribbon control must be initialized with presentation information provided by a BML resource bound to the executable. The BML resource is compiled from markup code provided as XAML.Writing the Markup File
The first step in creating the Ribbon control presentation is to write the XAML code. Listing 1 shows the basic format of the source XAML file. The <Application> element has two child elements, <Application.Commands> and <Application.Views>, the names of these elements indicate that they are XAML property elements which means that these child elements are actually treated as properties of the <Application> element, rather than child elements. Any attribute of a XAML element can be provided by using property elements, but they are usually used to provide complex objects to a property, and in this case <Application.Commands> provides a collection of <Command>elements and<Application.Views> provides a collection of one or more of the view elements.<Application xmlns="http://schemas.microsoft.com/windows/2009/Ribbon"> <Application.Commands> </Application.Commands> <Application.Views> </Application.Views> </Application>
<Command Name="openFileMenu" Symbol="ID_FILE_OPEN" Comment="Open"> <Command.LabelTitle> <String Id ="520">Open</String> </Command.LabelTitle> <Command.LargeImages> <Image Id ="521">res/open_32.bmp</Image> </Command.LargeImages> <Command.SmallImages> <Image Id="522">res/open_16.bmp</Image> </Command.SmallImages> </Command>
<Ribbon> <Ribbon.ApplicationMenu> <ApplicationMenu CommandName="fileMenu"> <MenuGroup> <Button CommandName="openFileMenu" /> <Button CommandName="saveFileMenu" /> <Button CommandName="saveAsFileMenu" /> </MenuGroup> <MenuGroup> <Button CommandName="exitMenu" /> </MenuGroup> </ApplicationMenu> </Ribbon.ApplicationMenu> <Ribbon.Tabs> <!-- code omitted --> </Ribbon.Tabs> <Ribbon.QuickAccessToolbar > <!-- code omitted --> </Ribbon.QuickAccessToolbar > </Ribbon>
Describing the Ribbon Tabs with XAML
The most noticeable part of the Windows Ribbon control are the tabs showing controls and these are described by using the <Ribbon.Tabs> property element. This element contains one or more <Tab> elements. Hilo Annotator defines two tabs, Home and View, Listing 4 shows an excerpt from the markup that defines the View tab. This tab has a single group called Zoom, which has three buttons and the tab uses a scaling policy to indicate how the buttons will be arranged when the Ribbon control is resized.<Ribbon.Tabs> <Tab CommandName="homeTab"> <!-- code omitted --> </Tab> <Tab CommandName="viewTab"> <Tab.ScalingPolicy> <ScalingPolicy> <!-- The following list the maximum sizes of the groups --> <ScalingPolicy.IdealSizes> <Scale Group="zoomGroup" Size="Large" /> </ScalingPolicy.IdealSizes> <!-- The following items give the shrink order of the groups --> <Scale Group="zoomGroup" Size="Medium" /> </ScalingPolicy> </Tab.ScalingPolicy> <Group CommandName="zoomGroup" SizeDefinition="ThreeButtons"> <Button CommandName="zoomInButton" /> <Button CommandName="zoomOutButton" /> <Button CommandName="previewButton" /> </Group> </Tab> </Ribbon.Tabs>
The Ribbon control supports many different controls. Some are simple like Button and Check Box, but the Ribbon control also provides more complex controls through elements called galleries. Hilo Annotator uses a DropDownGallery control to provide the drop-down list of the pencil widths. The markup code for this control is very simple, as shown in Listing 5. This markup simply indicates that the control is shown to the user as a button and the sizeButton command gives the images that will be shown on the button.
<DropDownGallery CommandName="sizeButton" TextPosition="Hide" Type="Items" ItemHeight="32" ItemWidth="128" HasLargeItems="true"> <DropDownGallery.MenuLayout> <VerticalMenuLayout Gripper="None" /> </DropDownGallery.MenuLayout> </DropDownGallery>
Binding the BML Resource to the Executable
The only way to initialize a Ribbon control with the markup information is to call the IUIFramework::LoadUI method and pass the name of a UIFILE resource bound to the executable. To do this you must compile the XAML to produce a BML file and bind this to the executable. The tool to compile the XAML to BML is called uicc.exe and is provided as part of the Windows 7 Software Development Kit (SDK). This tool produces three outputs: the BML file that contains the markup information, a header file that contains the symbols that identify the commands, and a resource script that describes the string tables, bitmaps and the UIFILE resource for the BML file. The following steps describe how to add the XAML markup file to a project and add a build step to use the uicc.exe tool. These steps are for Visual C++ Express 2010 and all versions of Visual Studio 2010.
To add the XAML markup file and build step to a project:
- Add the XML file to the project using Solution Explorer.
- In Solution Explorer, right-click the XML file, and then click the Properties item to show the property page. On the General page, change the Item Type item to Custom Build Tool, and click the Accept button. This will ensure that the Custom Build Tool page is shown on the property page.
- In the Configuration drop-down list, click All Configurations.
- Click the Custom Build Tool page, and in the Command Line item type the followinguicc.exe “%(FullPath)” %(Filename).bml /header:%(Filename).h /res: %(Filename).rc
This indicates that the uicc.exe tool is used to compile the XML file and provides names for the BML file, the header file, and resource script. - Click OK, to save the values and close the property pages.
- Now add a line to insert the generated resource script to the project’s resource script. In Solution Explorer right-click the project’s resource script file (for example in Hilo Annotator this is called Annotator.rc) and click View Code.
- Scroll to the bottom of the file and add a line like the following where RibbonRes.rc is the name of the resource script created by uicc.exe#include "RibbonRes.rc"
- Press Ctrl+S to save the resource script.
When you build the solution, if you get an error that uicc.exe is not a command (this will happen if you run Visual C++ 2010, a 32-bit application, on an Windows 7 x64-based system) then it means that you need to add the path to the SDK tools to the project’s properties.
To add the path to the SDK tools to the project properties:
- In Solution Explorer right-click the project, and then click Properties.
- In the Configuration category, click VC++ Directories.
- Edit the Executable Directories item to add the path to the Bin folder of the Windows 7 SDK.
Using the Ribbon Control
To use the Ribbon control in your application you must create it through COM, and initialize the user interface with the BML file and provide a command handler object to handle the command messages generated when the user interacts with the controls on the Ribbon control. The header for the Ribbon control is uiribbon.h. This file defines the interfaces, control property keys, and the Class Id (CLSID) needed to create and use the Ribbon control.Initializing the Ribbon
The Ribbon control has to be activated with a call to CoCreateInstance and in Hilo Annotator this occurs in the AnnotatorApplication::InitializeRibbonFramework method, which is called when the Annotator window is first created. The relevant lines are shown in Listing 6 which returns a pointer to the IUIFramework interface. This interface gives access to the core functionality of the Ribbon control.HRESULT hr = CoCreateInstance( CLSID_UIRibbonFramework, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_ribbonFramework)); HWND hWnd = nullptr; if (SUCCEEDED(hr)) { // window is the main application window hr = window->GetWindowHandle(&hWnd); } if (SUCCEEDED(hr)) { // Initilize the ribbon framework hr = m_ribbonFramework->Initialize(hWnd, this); } if (SUCCEEDED(hr)) { static const int MaxResStringLength = 100; wchar_t ribbonMarkup[MaxResStringLength] = {0}; // Obtain the name of the BML resource ::LoadString( HINST_THISCOMPONENT, IDS_RIBBON_MARKUP, ribbonMarkup, MaxResStringLength); hr = m_ribbonFramework->LoadUI(GetModuleHandle(NULL), ribbonMarkup); }
The IUIApplication interface is essentially the site interface of the application which the Ribbon control uses to initialize the handshake mechanism between the application and the control. There are three methods on this interface, the IUIApplication::OnCreateUICommand method is called for every command when the Ribbon control is first created, theIUIApplication::OnDestroyUICommand method is called for every command when the Ribbon control is destroyed, and the IUIApplication::OnViewChanged method is called when the state of one of the view objects changes.
In Hilo Annotator the implementation of OnViewChanged is used to obtain the height of the Ribbon control by calling a method called GetRibbonHeight, as shown in Listing 7. This code illustrates how you can obtain the various views on the Ribbon control (the Ribbon or a ContextPopup object).
unsigned int AnnotatorApplication::GetRibbonHeight() { unsigned int ribbonHeight = 0; if (m_ribbonFramework) { ComPtr<IUIRibbon> ribbon; HRESULT hr = m_ribbonFramework->GetView(0, IID_PPV_ARGS(&ribbon)); if (SUCCEEDED(hr)) { hr = ribbon->GetHeight(&ribbonHeight); if (FAILED(hr)) { ribbonHeight = 0; } } } return ribbonHeight; }
HRESULT AnnotatorApplication::OnCreateUICommand( unsigned int, UI_COMMANDTYPE, IUICommandHandler** commandHandler) { // The ribbon uses only one command handler return m_commandHandler->QueryInterface(IID_PPV_ARGS(commandHandler)); }
Providing the Command Object
The command object implements the IUICommandHandler interface, which has just two methods, Execute and UpdateProperty. When a user interacts with any control on the Ribbon control a command message is generated and the Ribbon control informs the application by calling the IUICommandHandler::Execute method shown in Listing 9.HRESULT Execute( UINT32 commandId, UI_EXECUTIONVERB verb, const PROPERTYKEY *key, const PROPVARIANT *currentValue, IUISimplePropertySet *commandExecutionProperties );
Commands can be quite complex since a control can be composed of several controls, each of which will have values. These values are the control properties. The Executemethod indicates the property that is the subject of the command through the key parameter and the value of the property through currentValue. The property identifier is a pointer to a PROPERTYKEY structure, and the values in the structure identify the type of the property and a unique identifier. You do not need to know the values used, all you need to know is the symbol. The uiribbon.h header file defines the properties that are used by the Ribbon control, so for example, the UI_PKEY_ENABLED symbol is used to identify the enabled property and indicates that this property is a Boolean.
The actual value of the property is accessed through the currentValue parameter which is a pointer to a VARIANT. The final parameter of the Execute method is a pointer to aIUISimplePropertySet interface that you can use to access other properties of the command.
Hilo Annotator provides an implementation of IUICommandHandler interface through the class UICommandHandler. The implementation in Annotator,UICommandHandler::Execute, has a large switch statement so that it can provide different code for each command and much of the code delegates the code to other objects in the application. For example, Listing 10 shows part of this switch statement, where the m_imageEditor variable is a pointer to the object that provides the code for the image editor child window in the lower half of the Annotator window. You will recognise the ID_FILE_OPEN symbol from Listing 2 where it is declared as the symbol for the openFileMenucommand.
HRESULT hr = S_OK; switch (commandId) { case ID_FILE_OPEN: { hr = m_imageEditor->OpenFile(); break; } case ID_FILE_SAVE: { m_imageEditor->SaveFiles(); break; } case ID_FILE_SAVE_AS: { m_imageEditor->SaveFileAs(); break; } case ID_FILE_EXIT: { m_imageEditor->SaveFiles(); ::PostQuitMessage(0); break; } // other code omitted }
Accessing Command Properties
The controls that you can use on a Ribbon control are documented in the MSDN Library Each page lists the property keys that the control supports. The documentation describes how some of the properties are accessed through calls to the Ribbon control’s IUIFramework interface (the IUIFramework::GetUICommandProperty andIUIFramework::SetUICommandProperty methods). However, most of the properties are accessible through a process that the documentation calls invalidation.
Invalidation means that the application tells the Ribbon control that a control property is invalid and then the Ribbon control calls the application to request the new value. In Hilo Annotator invalidation occurs in the image editor code whenever the Image Editor window is redrawn or if a command is executed. Listing 11 shows this code where them_framework variable is the IUIFramework interface on the Ribbon control.
HRESULT ImageEditorHandler::UpdateUIFramework() { // After we're done drawing make sure to update framework buttons as necessary if (m_framework) { m_framework->InvalidateUICommand( ID_BUTTON_UNDO, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( ID_BUTTON_REDO, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( ID_FILE_SAVE, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( pencilButton, UI_INVALIDATIONS_VALUE, nullptr); m_framework->InvalidateUICommand( cropButton, UI_INVALIDATIONS_VALUE, nullptr); } return S_OK; }
The pencilButton and cropButton controls are Toggle Buttoncontrols and in Hilo Annotator only one of them can be pushed in. This pushed-in state is a Boolean value so rather than requesting the change in property state, the call to the InvalidateUICommand method for these two controls requests that the values of the control properties are invalidated, and the application will check for, and return a value for the UI_PKEY_BooleanValue of these controls.
Once you have invalidated a command, the Ribbon control cannot display the associated control’s state, so it must ask the application for the property values by calling theIUICommandHandler::UpdateProperty method. This method is called with four parameters, the first three indicate the command, the property, and the current value of the property.The fourth is an out parameter for the new value of the property. In Hilo Annotator this method is implemented in the UICommandHandler class and has two purposes. The first is to return the state and value properties invalidated by the UpdateUIFramework method (Listing 11)1 The second purpose is to populate the DropDownGallery control for the pencil widths with appropriate images.
Listing 5 shows that the markup for the Gallery control does not provide the values to be shown by the control. When the control is initialized, the Ribbon control calls theUpdateProperty method to update the UI_PKEY_ItemsSource property. The UICommandHandler class implementation of this method accesses the IUICollection interface on this property and adds the images to be shown by the Gallery control.
No comments:
Post a Comment
Thank you for Commenting Will reply soon ......