Throughout our history it seems as if the most revolutionary ideas just pop out of someone’s head at a given moment. Then again, there are some that are obtained through deep research. The appearance of the Ribbon in the Microsoft Office environment is the result of the latter. However, inventing something does not necessarily mean instantly making it accessible to the masses. For this, another group of developers from the Redmond giant created the Ribbon Framework.
Contributed by Gabor Bernat Rating: / 1 October 19, 2009
I have already disclosed some information about how the framework works in one of my previous articles, Markup Language for the Ribbon Framework. There I explained that the process of creating a ribbon is split into two parts, design and function. I tried to give a teaser about the first part in my previous articles. Naturally I just presented some general traits and coded a simple example.
I had no intention to cover the Markup language used in depth. For that you have the MSDN library, which is quite complete on this topic. Today I will focus on the second part, function. I will present how to get from the binary files to a working application. I will continue to use the HTML edit sample within Visual Studio 2008 to make my point in practice.
I hope that you've already linked the XML up with the UICC.exe from last time, and managed to generate the binary code from it, along with the resource file and the header full of definitions. For starters, we will need to add these to our solution. Open up the rc file (or just get to the resource includes from the Resource View) and add the include:
#include "RibbonRes/ribbonres.rc2"
You can also add it to the solution if you want better handling. However, this is not necessary. Now we can continue to create a ribbon. The content on the following pages should be easy to comprehend for anyone who has good knowledge of C++ and MFC.
The image above describes how all this works. The framework will be a single variable definition, and we will initialize it from our application. For this we need to call three functions. However, to avoid security and memory leaks, some more complicated code is required than just the three functions shown above. The communication from the framework to our application is assured via the OnCreateCommand. This is the case when, for instance, you push down a button.
Before we rush ahead, we need to define a pointer to the framework. Through this we can communicate later with the framework. A couple of includes are also required in a new file: Ribbon.cpp.
#include "StdAfx.h"
#include "MainFrm.h"
#include "RibbonRes/ribbonres.h"
#include "HTMLEdDoc.h"
#include "HTMLEdView.h"
#include "HTMLEdit.h"
#include "Ribbon.h"
#include <UIRibbonPropertyHelpers.h>
#include <UIRibbon.h>
IUIFramework* g_Framework = NULL;
First we need to declare two functions, one for the initialization process and one for the destroy process. The framework will only communicate with an IUIApplication type class. Therefore, we must create one for our application. The IUIApplication interface has three member functions that we will need to implement later. We will also use the following:
CMainFrame* m_pMainFrame; //Save pointer to Mainframe
};
Furthermore, the IUIApplication is derived from the IUnknown class. This interface has three functions we need to take care of: AddRef, Release and QueryInterface. While the first two are straightforward counting mechanisms of the pointers given out for the user, the third requires a little explanation.
This will use the GUID (globally unique identifiers) of the classes to determine what type of pointer you requested from the following: IUnknown, IUIApplication or IUICommandHandler. Think of it as a smart and safe base class caster paired with a smart pointer. These three functions will make sure that, once we no longer need the ribbon, it will destroy itself.
Now that we have an IUIApplication to which to link the framework, we can create it. For this we will use the InitRibbon function, and we will double it with a Destroy function for the Ribbon.
Here we've completed all three required steps. We created the platform with the CoCreateInstance. This will return a framework pointer for us. We created our interface that will let us treat callbacks and events that the framework will send in the form of the CApplication. Then we initialized the framework by giving it the two pointers. The first is the main windows within which the ribbon should be implemented and the second is the CApplication that lets us communicate with the framework.
Finally, we specify to the framework which resource file to load. If you used the default UICC.exe settings (and the ones I provided), the tool will generate it with the name of "APPLICATION_RIBBON." Finally, we release all the pointers that we used during the initialization, and we are done. The destroy function is just a framework call for destroying the ribbon and a simple pointer release:
Inside the static function used to create the instance of the CApplication, we just create the object dynamically, save the mainframe pointer, and if this is not possible, return an error message:
Before we have a functional ribbon, we need to add another class. We need to let the application know that the ribbon is there, and to tell the application the size of the ribbon. We will add the ribbon as a common toolbar to the main framework of our application. Nevertheless we need to redefine the height setting and calculation of this custom toolbar. For this, we derive it from and overwrite the CalcFixedLayout function.
Now we can add it to our application. Call it from the mainframe. First create the custom toolbar (add it as a member) and afterward call the InitRibbon function. The OnCreate method seems like a good place for this. While you are there, you can comment out the initialization of the old style ribbon.
if (!m_RibbonBar.Create(this, WS_CHILD|WS_DISABLED|WS_VISIBLE |CBRS_TOP|CBRS_HIDE_INPLACE,0))
{
TRACE0("Failed to create toolbarn");
return -1; // fail to create
}
HRESULT hr;
hr = InitRibbon(this, &m_pUIFramework);
if (FAILED(hr)) { return -1; }
Nevertheless, we need to extend things a bit to get a fully functional ribbon. First we need to ask the ribbon what size it needs and communicate this to our application. The OnViewChanged is going to be called whenever some state change occurs that should concern the ribbon. Insert into it the following code snippet:
We have to add another global function, one that will tell the Ribbon to update itself if a change occurs inside our source code. This is the ribbon invalidation:
Now it remains to bind the commands to a command handler. It is possible to use multiple command handlers. This way you may use one for all the buttons and another for all the check boxes. We need to return a pointer to the command handler. I will use just one to keep it as simple as possible:
STDMETHODIMP OnCreateUICommand(UINT32 /*nCmdID*/,
__in UI_COMMANDTYPE /*typeID*/, __deref_out
IUICommandHandler** ppCommandHandler)
{
//We use a single command handler for all ribbon commands.
This brings us to the commands. The IUICommandHandler interface that we implemented into our application will help. It defines the methods for gathering Command information and handling Command events from the Windows Ribbon framework. It contains two functions: Execute and UpdateProperty. The first one is called whenever a command is given. This assures the communication from the ribbon to the application. The second is for the opposite; it will be called whenever the ribbon wants to know some input data.
This can be from a single bool value that describes whether a check box is on or off up to more complex items, like the content of a combo box or gallery. The Ribbon will store no data. The application should take care of delivering any dynamic data upon appeal. Because our commands are already implemented into C++ code for the initial application, we will use the SendMessage function to just pass by the incoming commands:
In the update property, the most important part is the parameter of command for which the traits are asked. If the property asks for the Boolean value, we use a global function to set the new value. The GetStatus is a new function inside the CApplication function, inside which we call the view to tell us the correct value.
With the help of this, we can turn the view source button on and off and reuse the already-implemented functionality. Here is the function used inside the view:
BOOL CHTMLEdView::GetCommandStatus( UINT nCmdID )
{
BOOL bHasFunc;
UINT uiElemType;
INT id = GetDHtmlCommandMapping(nCmdID, bHasFunc, uiElemType);
return id > 0 ? !!(QueryStatus(id) & OLECMDF_ENABLED) : TRUE;
}
Here I should mention how you can change the application modes. If you read my previous article, I said that you can configure multiple application modes and switch between them during the run time. For this we declare another global function into our source file:
There you have it. This is all you need to know. In the end we can conclude that it is nice to see that the framework managed to split the look from the implementation. C++ wise, it does not matters what image icon is used or if you use a toggle button or a checkbox. This is true for more complex controls also, like a color picker or a font setter.
C++ wise, it is only important that the output of the control can be a string, RGB color, font or any other. This allows you to replace the XML code if a new, fancier control appears and you feel it fits your program better. That it is. No code modification required. Just edit and recompile the source and it will work. Here you have my end source code:
For more detailed information you can dig up the MSDN help files here. My hat's off to all the people who managed to keep up with me this time until the end. It may seem a little complex and hard to understand at first. Nevertheless, once you get the idea behind it, it will seem quite intuitive and easy.
The same framework will be available for the WFP too, besides MFC. You can ask any question you might have here in the comments after the article, and it would be good if you could rate this tutorial as well. If you want to discuss some topic you can join the DevHardware or DevArticles forums and ask our experts. Live With Passion!