Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Ribbon Framework in Win32 C Application

0.00/5 (No votes)
24 Oct 2010 3  
Adding native Windows Ribbon Framework in Win32 C Application (NO MFC OR ATL)

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:

  1. Single UI for all command
  2. Visible & self explanatory
  3. Labelled grouping
  4. Modal but not hierarchical
  5. Direct & immediate
  6. Spacious
  7. Has application button & quick access toolbar (QAT)
  8. Minimal customization
  9. 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 view has the following children:

    • 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>
            </Tab>
          </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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here