相关文章推荐
一身肌肉的蟠桃  ·  Windows Ribbon ...·  1 年前    · 
一身肌肉的蟠桃  ·  Ribbon with C++, Part ...·  1 年前    · 
一身肌肉的蟠桃  ·  Ribbon with C++, Part ...·  1 年前    · 
一身肌肉的蟠桃  ·  DevExpress ...·  1 年前    · 
一身肌肉的蟠桃  ·  如何评价6 月24 ...·  1 年前    · 

Introduction

There's a project started at Microsoft called Hilo with the aim of helping native programmers to build high performance, responsive rich client applications. This article will try to explore Hilo's chapter 10 (Using the Windows Ribbon ) in pure C.

Background

Ribbon is a nice control & highly visual. And as stated on MSDN, Ribbon has all these characteristics:

  • Single UI for all command
  • Visible & self explanatory
  • Labelled grouping
  • Modal but not hierarchical
  • Direct & immediate
  • Spacious
  • Has application button & quick access toolbar (QAT)
  • Minimal customization
  • Improved keyboard accessibility
  • What I really like from Ribbon is that it's adaptive. When we resize window, Ribbon control will be automatically resized, we don't need to write special function to handle window layout changes.

    The adaptiveness of Ribbon is because of the separation of presentation and visual attributes (UI) and command logic. (We'll talk about this later.)

    Another great feature in Ribbon that is worth mentioning is galleries. Galleries is basically a list box control with graphics inside it, enabling user to do live previewing.

    In order to use Ribbon , we need Windows SDK 7.0 (SDK 7.0 contains uuic.exe needed for compiling XAML file and creating binary (BML file) and resource file). And for the client using our application, they need to run Windows 7 or Windows Vista with SP2 & platform updates.

    Creating Ribbon and Adding It To Our Application

    The creation of Ribbon is divided into 2 major tasks:

  • Designing UI (markup code)
  • Making connection to our command handler / callback through COM
  • OK, let's start with the design of UI.
    Ribbon's markup code is created as an XAML file, where the markup consists of 2 elements:
    <Application.Views> and <Application.Commands> .

  • <Application.Views> There're 2 type of views: Ribbon view and ContextPopup view. To see the difference between these 2 views, let's us see the pictures below:
  • Ribbon.ApplicationMenu
  • Ribbon.HelpButton
  • Ribbon.Tabs
  • Ribbon.ContextualTabs
  • Ribbon.QuickAccessToolbar
  • Ribbon.SizeDefinitions
  • While ContextPopup view has these:

  • ContextPopup.ContextMaps
  • ContextPopup.ContextMenus
  • ContextPopup.MiniToolbars
  • In our sample, our Ribbon view has 3 children:

  • Ribbon.ApplicationMenu contains Exit Button and MRU List
  • Ribbon.Tabs contains 4 groups of buttons
  • Ribbon.QuickAccessToolbar
  • < Application.Views > < Ribbon > < Ribbon.ApplicationMenu > < ApplicationMenu CommandName =" cmdFileMenu" > < ApplicationMenu.RecentItems > < RecentItems CommandName =" cmdMRUList" MaxCount =" 1" / > < /ApplicationMenu.RecentItems > < MenuGroup Class =" MajorItems" > < Button CommandName =" cmdExit" / > < /MenuGroup > < /ApplicationMenu > < / Ribbon.ApplicationMenu > < Ribbon.Tabs > < Tab CommandName =" cmdTab1" > < Group CommandName =" cmdGroup1" SizeDefinition =" OneButton" > < Button CommandName =" cmdButton1" / > < /Group > < Group CommandName =" cmdGroup2" SizeDefinition =" TwoButtons" > < Button CommandName =" cmdButton2" / > < Button CommandName =" cmdButton3" / > < /Group > < Group CommandName =" cmdGroup3" SizeDefinition =" ThreeButtons" > < Button CommandName =" cmdButton1" / > < Button CommandName =" cmdButton2" / > < Button CommandName =" cmdButton3" / > < /Group > < Group CommandName =" cmdGroup4" SizeDefinition =" FiveOrSixButtons" > < Button CommandName =" cmdButton3" / > < Button CommandName =" cmdButton4" / > < ToggleButton CommandName =" cmdToggleButton1" / > < Button CommandName =" cmdButton5" / > < ToggleButton CommandName =" cmdToggleButton2" / > < /Group > < / Ribbon.Tabs > < Ribbon.QuickAccessToolbar > < QuickAccessToolbar CommandName =" cmdQat" / > < / Ribbon.QuickAccessToolbar > < /Ribbon > < /Application.Views >
  • <Application.Commands> We'll skip this part, as this article will not explain about connecting to our Ribbon command handler yet. Right now, it's adequate to know that Symbol attribute (e.g.: IDC_CMD_EXIT ) will be used to connect to our command handler: < Application.Commands > < Command Name =" cmdGroup1" > < Command.SmallImages > < Image Id =" 201" > res/Button_Image.bmp < /Image > < /Command.SmallImages > < /Command > < Command Name =" cmdGroup2" / > < Command Name =" cmdGroup3" / > < Command Name =" cmdGroup4" / > < Command Name =" cmdButton1" > < Command.LabelTitle > < String Id =" 210" > Button 1 < /String > < /Command.LabelTitle > < Command.LargeImages > < Image Id =" 211" > res/AddTableL.bmp < /Image > < /Command.LargeImages > < Command.SmallImages > < Image Id =" 212" > res/AddTableS.bmp < /Image > < /Command.SmallImages > < /Command > < Command Name =" cmdToggleButton2" > < Command.LabelTitle > < String Id =" 270" > ToggleButton 2 < /String > < /Command.LabelTitle > < Command.SmallImages > < Image Id =" 271" > res/Copy.bmp < /Image > < /Command.SmallImages > < /Command > < Command Name =" cmdQat" / > < Command Name =" cmdFileMenu" / > < Command Name =" cmdMRUList" > < Command.LabelTitle > < String Id =" 280" > MRU List < /String > < /Command.LabelTitle > < /Command > < Command Name =" cmdExit" Symbol =" IDC_CMD_EXIT" > < Command.LabelTitle > < String Id =" 290" > Exit Button < /String > < /Command.LabelTitle > < Command.LargeImages > < Image Id =" 291" > res/ExitL.bmp < /Image > < /Command.LargeImages > < /Command > < /Application.Commands >

    Remember about compiling XAML file and creating BML file we talked before? Now, it's the time for us to do this. But before we compile the XAML file we created earlier, please make sure uuic.exe can be found by Visual Studio.

    You can add Windows SDK's bin folder to Visual Studio Executable Directories like the picture below:

    Are we ready now? Oh, wait a minute. We need to tell Visual Studio how to compile our Ribbon markup and what to output.

    You can do right clicking on the XAML file we created before (that we're going to compile), and change Item Type in the General option to Custom Build Tool, after that we can change Command Line option (in the new General option under Custom Build Tool) to something like this:

    uicc.exe Ribbon.xml %(Filename).bml /header:%(Filename).h /res:%(Filename).rc

    And in the Outputs option, write this:

    %(Filename).bml;%(Filename).h;%(Filename).rc

    This will tell Visual Studio to compile our XAML file and create binary file ( Ribbon.bml ) and resource files ( Ribbon.h and Ribbon.rc ).

    We're done with designing UI part, now we can move on to the COM part. (That's initializing the Ribbon control and binding the markup resource to our application.)

    We need to create a basic window application that will act as the host of the Ribbon first.
    As usual, we start with WinMain :

    int APIENTRY WinMain(HINSTANCE hInstance,
    	HINSTANCE hPrevInstance,
    	LPSTR     lpCmdLine,
    	int       nCmdShow)
    	MSG msg;
    	if(CoInitialize(0))
    		return FALSE;
    	MyRegisterClass(hInstance);
    	// Perform application initialization.
    	if (!InitInstance (hInstance, nCmdShow))
    		return FALSE;
    	// Main message loop.
    	while (GetMessage(&msg, NULL, 0, 0))
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	CoUninitialize();
    	return (int) msg.wParam;
    

    And then, we register window class:

    ATOM MyRegisterClass(HINSTANCE hInstance)
    	WNDCLASSEX wcl;
    	wcl.cbSize = sizeof(WNDCLASSEX);
    	wcl.hInstance = hInstance;
    	wcl.lpszClassName = g_szClassName;
    	wcl.lpfnWndProc = WndProc;
    	wcl.style = 0;
    	wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    	wcl.hIconSm = NULL;
    	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
    	wcl.lpszMenuName = NULL;
    	wcl.cbClsExtra = 0;
    	wcl.cbWndExtra = 0;
    	wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    	if(!RegisterClassEx(&wcl)) {
    		return FALSE;
    	return TRUE;
    

    Continue with the creation of the window:

    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    	HWND hWnd;
    	hWnd = CreateWindow(g_szClassName, g_szAppTitle, 
    		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
    		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    	if (!hWnd)
    		return FALSE;
    	ShowWindow(hWnd, nCmdShow);
    	UpdateWindow(hWnd);
    	return TRUE;
    

    And finally: the window procedure:

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    	switch (message)
    	case WM_CREATE:
    		break;
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		break; 
    	default:
    		return DefWindowProc(hWnd, message, wParam, lParam);
    	return 0;
    

    That's it for our basic window application. Now can finally move to the COM part. Before we continue, this article won't go into details about COM programming. I would suggest Jeff Glatt's series of article about programming COM in plain C.

    We know that every COM object must have 3 functions: QueryInterface, AddRef, and Release. We'll start creating these 3 functions. First QueryInterface, this function compares our GUID with our already defined IID_IUIAPPLICATION. If it matches, point our IUIApplication interface to our Ribbon obj, and do reference counting. And if it doesn't match, simply return E_NOINTERFACE.

    For AddRef and Release, we'll only increment or decrement the reference count.

    Must have functions for COM object: 1. QueryInterface will compare GUID, and if it matches then fill the object and do reference counting 2. AddRef and Release increment and decrement the count HRESULT STDMETHODCALLTYPE QueryInterface(IUIApplication *This, REFIID vtblID, void **ppv) /* No match */ if(!IsEqualIID(vtblID, &IID_IUIAPPLICATION) && !IsEqualIID(vtblID, &IID_IUnknown)) { *ppv = 0; return(E_NOINTERFACE); /* Point our IUIApplication interface to our Ribbon obj */ *ppv = This; /* Ref count */ This->lpVtbl->AddRef(This); return(NOERROR); ULONG STDMETHODCALLTYPE AddRef(IUIApplication *This) return(InterlockedIncrement(&OutstandingObjects)); ULONG STDMETHODCALLTYPE Release(IUIApplication *This) return InterlockedDecrement(&OutstandingObjects);

    Because every application that's going to use Ribbon framework will be implementing IUIApplication interface (this is where callback entry-point methods defined), there're another 3 must have functions: OnViewChanged, OnCreateUICommand, OnDestroyUICommand.

  • OnViewChanged is called when there's a change in view (Ribbon or ContextPopup view)
  • OnCreateUICommand is called to bind Command (we specified before in our markup code) with IUICommandHandler (interface that defines method for gathering Command information and handling Command events)
  • And the last one, OnDestroyUICommand is (as we can see from the name of the function) called when the application is destroyed.
  • We're now simply returning E_NOTIMPL for these 3 functions, because we're not yet implementing it.

    3 must have functions for Ribbon application. For now we're leaving this as a stub HRESULT STDMETHODCALLTYPE OnViewChanged(IUIApplication *This, UINT32 viewId, UI_VIEWTYPE typeID, IUnknown *view, UI_VIEWVERB verb, INT32 uReasonCode) return E_NOTIMPL; HRESULT STDMETHODCALLTYPE OnCreateUICommand(IUIApplication *This, UINT32 commandId, UI_COMMANDTYPE typeID, IUICommandHandler **commandHandler) return E_NOTIMPL; HRESULT STDMETHODCALLTYPE OnDestroyUICommand (IUIApplication *This, UINT32 commandId, UI_COMMANDTYPE typeID, IUICommandHandler *commandHandler) return E_NOTIMPL;

    Remember that we're accessing Ribbon framework through COM? That's why now our VTable must have and must be started with QueryInterface, AddRef, Release and followed by our IUIApplication's functions. Please watch for the order!

    IUIApplicationVtbl is defined in UiRibbon.h and CINTERFACE symbol stops IntelliSense from complaining about undefined IUIApplicationVtbl identifier IUIApplicationVtbl myRibbon_Vtbl = {QueryInterface, AddRef, Release, OnViewChanged, OnCreateUICommand, OnDestroyUICommand};

    Next, we're ready to initialize the Ribbon. It's worth knowing how Ribbon framework interacts with our application, take a look at this diagram:

    First, we need to create an object with Ribbon framework CLSID:

    Because we're only creating a single object of CLSID_UIRibbonFramework, it's better to use CoCreateInstance hr = CoCreateInstance(&CLSID_UIRibbonFramework, NULL, CLSCTX_INPROC_SERVER, &IID_IUIFRAMEWORK, (VOID **)&g_pFramework);

    And then we call our COM's QueryInterface function where in turn we get IUIApplication object pointer. This object pointer will be passed in Ribbon framework's Initialize function as we try to connect our host application with Ribbon framework.

    IUIApplication    *pApplication = NULL;
    /* allocate pApplication */
    pApplication = (IUIApplication *)GlobalAlloc(GMEM_FIXED, sizeof(IUIApplication));
    if(!pApplication) {
        return(FALSE);
    Point our pApplication to myRibbon_Vtbl that contains
    standard IUnknown method (QueryInterface, AddRef, Release)
    and callback for our IUIApplication interface
    (OnViewChanged, OnCreateUICommand, OnDestroyUICommand).
    (IUIApplicationVtbl *)pApplication->lpVtbl = &myRibbon_Vtbl;
    
    hr = pApplication->lpVtbl->QueryInterface(pApplication, &IID_IUIAPPLICATION, &ppvObj); 
    hr = g_pFramework->lpVtbl->Initialize(g_pFramework, hWnd, (IUIApplication *)ppvObj); 

    If everything goes well, we can continue to bind the markup resource with our application, and now our application knows when to makes command-related callbacks at run time. LoadUI function will handle this.

    Take a look at LoadUI's params. The second param is our application instance (in which we're simply passing NULL to GetModuleHandle function to get a handle to the file used to create the calling process, that's our own application). And the third param is our compiled binary markup (default will be APPLICATION_RIBBON).

    hr = g_pFramework->lpVtbl->LoadUI(g_pFramework, GetModuleHandle(NULL), 
            L"APPLICATION_RIBBON");  

    Now that our we're done with our InitializeFramework function, all we need to do is call our newly created function in our window procedure, right in WM_CREATE section.

    case WM_CREATE:
        if(!InitializeFramework(hWnd))
            return(FALSE);
        break;
    

    And don't forget to do the clean up. Calling Destroy function will release and destroy any instance of Ribbon framework objects.

    hr = ((IUIFramework *)g_pFramework)->lpVtbl->Destroy(g_pFramework);
    g_pFramework = NULL;
    case WM_DESTROY:
        DestroyRibbon();
        PostQuitMessage(0);
        break;
    

    OK, we're done!
    You may test your Ribbon application now. I hope everything goes well, and you can see Windows Ribbon attached to your application.

    What's Next?

    Next, we'll be talking about Ribbon's command handlers or how our application will respond to any property update requests or respond to execute events.

    QuestionIUIApplicationVtbl not declared / unresilved symbol - MS Visual Studio C++ 2019 Pin
    Member 1362260526-Apr-21 11:00
    Member 1362260526-Apr-21 11:00  IUIApplicationVtbl
    .
    The header file uriribbon.h is included in the project source.
    how can I fix this problem ?
    AnswerRe: IUIApplicationVtbl not declared / unresilved symbol - MS Visual Studio C++ 2019 Pin
    Member 143402239-May-21 6:27
    Member 143402239-May-21 6:27  There is no such thing as a IUIApplicationVtbl in my Uiribbon.h header file.
    I understand the logic but there is no such thing and so it won't compile.
    I guess if I want to try it the C rather than C++ way I'll have to create this struct of function pointers myself.
    My apologies. The reason my intellisense (and compiler itself) didn't recognise it is because they #ifdef cplusplus'd it away when actually using a C++ compiler.
    Too bad it doesn't let me undo my vote of 2 and vote 5 now.
    I am a C# developer and I need this Ribbon Sample but in C#.
    If you could help me, I would appreciate it.
    Thanks in advance
    Célio

    QuestionC:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppCommon.targets(151,5): error MSB6006: "cmd.exe" exited with code 9009. Pin
    Member 1146014418-Feb-15 0:10
    Member 1146014418-Feb-15 0:10  C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppCommon.targets(151,5): error MSB6006: "cmd.exe" exited with code 9009.
    while trying :: Windows Ribbon Framework in Win32 C Application[^]
    Hi! I have read your article, but I have a problem with the implementation of the IUICommandHandler interface (I write in pure C).
    My code:
    DEFINE_GUID(IID_IUICommandHandler, 0xefc7bdf, 0x1fdc, 0x487a, 0xbc, 0x9b, 0x24, 0xcd, 0x19, 0x29, 0x1a, 0x17); IUICommandHandlerVtbl CommandHandlerVtbl = {CHQueryInterface, CHAddRef, CHRelease, OnExecute, OnUpdateProperty}; IUICommandHandler *pCommandHandler = NULL; HRESULT STDMETHODCALLTYPE OnCreateUICommand(IUIApplication *This, UINT32 commandId, UI_COMMANDTYPE typeID, IUICommandHandler **commandHandler) { //**************** WHAT’S WRONG? **************** pCommandHandler = (IUICommandHandler *)GlobalAlloc(GMEM_FIXED, sizeof(IUICommandHandler)); if (!pCommandHandler) return E_NOTIMPL; (IUICommandHandlerVtbl *)pCommandHandler->lpVtbl = &CommandHandlerVtbl; return pCommandHandler->lpVtbl->QueryInterface(pCommandHandler,&IID_IUICommandHandler,(void**)commandHandler); //commandHandler – last parameter OnCreateUICommand HRESULT STDMETHODCALLTYPE OnExecute(IUICommandHandler *CmdHandler, UINT32 nCmdID, UI_EXECUTIONVERB verb, const PROPERTYKEY *key, const PROPVARIANT *ppropvarValue, IUISimplePropertySet *pCommandExecutionProperties) switch (nCmdID) case btn: MessageBoxA(0, "TEST!", "Hello Ribbon!", MB_OK); //NO ACTIVITY :( break; return S_OK; Could you help me?
    Hi did you manage to do a command handler in c ? I am strugling and also I would like to get the ribbon height with GetHeight(), but didn't found any info.
    chears from France
    I created with Visual C++ 2010 a MFC application with ribbon. I have added buttons to this ribbon, but I do not know how to add images to these buttons. I tried editing writelarge.bmp file from the project, and setting the index of image on these buttons, but now the ribbon icons looks very ugly. Is there an easy way to add icons to ribbon images, other than editing writelarge.bmp file from the project?
    I am kinda a newby for Ribbon for Windows 7. Sigh | :sigh:
    I want to disable Quick Access Toolbar and to configure image (16x16) for Groups of the Tabs.
    How can I do that?
    How can I change the RibbonMarkup.xml?
    I don't know how to set the Dialog box button too.
    Can I add buttons / tabs / groups into Quick Access Toolbar but limit that funcionality for certain buttons? For example, a contextual Tab that is hidden at a certain time and it was added to QAT. How to remove it when I hide the contextual Tab?
    Thanks in advance Cool | :cool:
    CeliSoft

    Microsoft uses the Ribbon in Office 2010 - and it runs very well on XP.
    How does this work ?
    I think it is very bad if you leave the XP-user with an empty window ...
    Looking around one can find code working on XP with Ribbons alive
    A Professional Ribbon You Will Use (Now with orb!)[^]
    MFC Feature Pack has new controls including ribbon control for non-native OS support.
    http://msdn.microsoft.com/en-us/library/cc309024(v=vs.90).aspx[^]
    http://msdn.microsoft.com/en-us/library/bb982451(v=vs.90).aspx[^]
    http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6922[^]
    What is the point of using C++ instead of C ?
    Is anyone using pure C when programming in Windows nowadays ?
    Michael Chourdakis - Music & DSP Engineer
    http://www.turboirc.com - Software & Research

    I'm expecting to do a UAT this week, and hopefully everything goes OK.
    The next article will be relatively shorter than this one, and I hope it'll be ready by next week.
    I thank you all for every positive responses to this article!
    Web01 2.8:2024-01-30:1
  •