

![]() ![]() |
software-on-demand-ita.comGiuseppe Pischedda Software Engineer software-on-demand-ita.comGiuseppe Pischedda Software Engineer |
A suo tempo, si è voluto separare UI (User Interface) e codice sorgente, allo scopo di favorire la creatività dei progettisti di UI, senza che questi dovessero avere la minima conoscenza di alcun linguaggio di sviluppo. Tecnicamente si tratta di "declarative programming", un approccio già arcinoto a chi sviluppa Silverlight o WPF etc. In WPF(Windows Presentation Foundation) ad esempio viene utilizzato XAML per la UI e C# o VB.Net per il code-behind.
A partire da Win7 è stato concesso gli sviluppatori Windows un completo framework ( alle cui funzioni si accede tramite l’interfaccia COM IUIFRamework ) basato proprio sul concetto di separazione tra UI (User Interface) e il codice (code-behind). La UI viene disegnata da Windows in base alla "descrizione degli attributi" contenuti in un file xml based mentre si userà il linguaggio C++/COM per il code-behind.
Il ribbon, le tabs i gruppi e tutti i relativi controlli, anche molto sofisticati, vengono creati semplicemente dichiarandoli in un file xml che deve aderire al cosiddetto Ribbon Markup. Il markup è definito in un file di schema : uicc.xsd. In base a tale schema, lo strumento uicc.exe esegue la validazione del nostro file xml. Invito a leggere la documentazione su Windows Ribbon Framework per avere un quadro di quali sono i possibili attributi da inserire nel file dichiarativo del nostro ribbon (e come usarli).
Lo strumento uicc.exe (tra gli strumenti di Visual Studio e dell’ SDK) si occupa, oltre che di compilare il nostro file xml, di creare i file di risorse da includere poi nel nostro progetto.
Il file xml deve essere compilato in formato binary (con estensione bml). Quest’ultimo dovrà essere inserito come risorsa Win32 (nell’exe o in una DLL ), la quale risorsa sarà caricata in memoria a runtime e darà vita al Windows Ribbon grazie alla tecnologia COM (Component Object Model). Windows Ribbon infatti non è un controllo Win32 classico ma un oggetto COM (non un ActiveX) che le applicazioni possono istanziare, caricare in memoria e agganciare al top dell’area client di una finestra di windows per consentire all’utente di interagire. Inoltre non è possibile aggiungere al ribbon controlli Win32 che non siano espressamente consentiti dallo schema previsto da Microsoft (tanto per dire un nostro controllo derivato o owner draw).
Prerequisiti:
Visual Studio 2013 Professional o Community Edition per avere il supporto MFC.
Creazione del progetto:
Step 1: Creiamo un nuovo progetto Visual C++. Il progetto sarà di tipo MFC -> MFC Application. Diamo un nome al progetto per esempio MFCNativeRibbonDemo.<Application xmlns="http://schemas.microsoft.com/windows/2009/Ribbon"> <Application.Commands> <Command Name="TabHome" Symbol="cmdTabHome" Id="30000" /> </Application.Commands> <Application.Views> <Ribbon> <Ribbon.Tabs> <Tab CommandName="TabHome"> </Tab> </Ribbon.Tabs> </Ribbon> </Application.Views> </Application>
In solution explorer facciamo click destro -> properties sul file xml nativeribbonmarkup.xml. In General alla riga Item Type scegliamo Custom Build Tool e quindi facciamo click su Apply. Questo attiva la funzione Custom Build Tool in cui alla riga Command Line specifichiamo il comando :
uicc.exe nativeribbonmarkup.xml nativeribbonmarkup.bml /header:nativeribbonmarkup.h /res:nativeribbonmarkup.rc
nativeribbonmarkup.bml;nativeribbonmarkup.rc;nativeribbonmarkup.h
In pratica non stiamo facendo altro che dire allo strumento uicc.exe di creare dal file xml un file binario (.bml) un header file (.h) e un file di risorse compilato (.rc). Il linker poi lo inserirà per noi tra le risorse.
E' necessario aggiungere al file di risorse predefinito (nel nostro caso MFCNativeRibbonDemo.rc) le inclusioni dei file nativeribbonmarkup.h e nativeribbonmarkup.rc creati dallo strumento ucc.exe.
In solution expolorer facciamo click destro su MFCNativeRibbonDemo.rc e scegliamo View Code. Nel file che viene aperto da Visual Studio aggiungiamo le inclusioni.
#include "nativeribbonmarkup.h" #include "nativeribbonmarkup.rc"
Dopo aver creato le risorse per il ribbon dobbiamo implementare il codice per comunicare con esso. Creiamo una nuova classe C++. Diamo un nome. Io l’ho chiamata CNativeRibbon. Click su Finish. Ora, dato che in realtà non si tratterà di una classe classica ma invece una classe COM cancelleremo il contenuto predefinito dentro la dichiarazione di classe. Stessa cosa nel file di implementazione.
#include <atlbase.h> #include <atlcom.h> #include <initguid.h> #include <uiribbon.h>
//NativeRibbon.h #pragma once #include "stdafx.h" #include <atlbase.h> #include <atlcom.h> #include <initguid.h> #include <uiribbon.h> #include "resource.h" IUIFramework* g_pFramework = NULL; UINT m_wRibbonHeight = 0; BOOL OFFICE_2013_STYLE = false; class CNativeRibbon : public CComObjectRootEx, public IUIApplication, public IUICommandHandler { public: BEGIN_COM_MAP(CNativeRibbon) COM_INTERFACE_ENTRY(IUIApplication) COM_INTERFACE_ENTRY(IUICommandHandler) END_COM_MAP() STDMETHOD(OnViewChanged)(UINT32 nViewID, __in UI_VIEWTYPE typeID, __in IUnknown* pView, UI_VIEWVERB verb, INT32 uReasonCode) { HRESULT hr; // The Ribbon size has changed. if (verb == UI_VIEWVERB_SIZE) { CComQIPtr pRibbon = pView; if (!pRibbon) return E_FAIL; UINT ulRibbonHeight; // Get the Ribbon height. hr = pRibbon->GetHeight(&ulRibbonHeight); if (FAILED(hr)) return hr; m_wRibbonHeight = ulRibbonHeight; ::InvalidateRect(NULL, NULL, TRUE); } return E_NOTIMPL; } STDMETHOD(OnDestroyUICommand)(UINT32 commandId, __in UI_COMMANDTYPE typeID, __in_opt IUICommandHandler* pCommandHandler) { return E_NOTIMPL; } STDMETHOD(OnCreateUICommand)(UINT32 nCmdID, __in UI_COMMANDTYPE typeID, __deref_out IUICommandHandler** ppCommandHandler) { if (nCmdID == pencilButton) { return QueryInterface(IID_PPV_ARGS(ppCommandHandler)); } else if (nCmdID == ID_FILE_EXIT) { return QueryInterface(IID_PPV_ARGS(ppCommandHandler)); } return E_NOTIMPL; } STDMETHODIMP Execute(UINT nCmdID, UI_EXECUTIONVERB verb, __in_opt const PROPERTYKEY* key, __in_opt const PROPVARIANT* ppropvarValue, __in_opt IUISimplePropertySet* pCommandExecutionProperties) { HRESULT hr = S_OK; switch (verb) { case UI_EXECUTIONVERB_EXECUTE: if (nCmdID == pencilButton) { MessageBox(NULL, L"Pencil Button was clicked",L"Pencil Button Handler", MB_OK); } else if (nCmdID == ID_FILE_EXIT) { ::PostQuitMessage(0); } break; } return hr; } STDMETHODIMP UpdateProperty(UINT nCmdID, __in REFPROPERTYKEY key, __in_opt const PROPVARIANT* ppropvarCurrentValue, __out PROPVARIANT* ppropvarNewValue) { return E_NOTIMPL; } };
//NativeRibbon.cpp #include "stdafx.h" #include "NativeRibbon.h" #include <atlbase.h> #include <atlcom.h> #include <initguid.h> #include <uiribbon.h> #include <Propvarutil.h> HRESULT InitRibbon(HWND hWindowFrame) { HRESULT hr = ::CoCreateInstance(CLSID_UIRibbonFramework, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&g_pFramework)); if (FAILED(hr)) { return hr; } CComObjectApriamo ora il file MainFrm.cpp e dichiariamo come extern le funzioni contenute nei file NativeRibbon .h e NativeRibbon.cpp.*pApplication = NULL; hr = CComObject ::CreateInstance(&pApplication); if (FAILED(hr)) { return hr; } hr = g_pFramework->Initialize(hWindowFrame, pApplication); if (FAILED(hr)) { return hr; } g_pFramework->LoadUI(GetModuleHandle(NULL), L"APPLICATION_RIBBON"); if (FAILED(hr)) { return hr; } } BOOL DestroyRibbon() { if (g_pFramework) { g_pFramework->Destroy(); g_pFramework->Release(); g_pFramework = NULL; return S_OK; } return E_NOTIMPL; }
/* Extern Declarations */ /*************************************************/ CComModule _Module; //COM extern HRESULT InitRibbon(HWND hWindowFrame); extern BOOL DestroyRibbon(); extern BOOL OFFICE_2013_STYLE; /*************************************************/
Dobbiamo dichiarare una variabile globale (_Module) di tipo CComModule in quanto serve alle librerie COM. Sempre nello stesso file nel costruttore aggiungiamo la chiamata di inizializzazione alle librerie COM e nel distruttore la deallocazione di COM e del ribbon.
// CMainFrame construction/destruction CMainFrame::CMainFrame() { // TODO: add member initialization code here CoInitialize(NULL); } CMainFrame::~CMainFrame() { DestroyRibbon(); CoUninitialize(); }
Ancora nello stesso file aggiungiamo la funzione di inizializzazione del ribbon:
//Native Ribbon Loading HRESULT hr = InitRibbon(m_hWnd); if (FAILED(hr)) { return FALSE; }
Infine nella funzione PreCreateWindow aggiungiamo lo stile WS_CLIPCHILDREN alla finestra frame per evitare problemi di repaint.
Eseguiamo l’applicazione e vediamo in azione il Windows Ribbon Framework in MFC.
Dopo avere aggiunto qualche altra funzionalità alla nostra applicazione ecco il risultato finale:
Personalizzare lo stile del Ribbon
E' possibile settare lo stile del ribbon manipolando tre properietà:Modificandole è possibile avere un Windows Ribbon totalmente personalizzato. Io per esempio ho voluto renderlo somigliante a quello di Microsoft Office 2013.
//Office 2013 Ribbon Style //............................... //.............................. if (OFFICE_2013_STYLE) { CComPtrspPropertyStore; if (SUCCEEDED(g_pFramework->QueryInterface(&spPropertyStore))) { PROPVARIANT propvarBackground; PROPVARIANT propvarHighlight; PROPVARIANT propvarText; UI_HSBCOLOR BackgroundColor = UI_HSB(0x295, 0x20, 0xFF); UI_HSBCOLOR HighlightColor = UI_HSB(0x00, 0x55, 0xFF); UI_HSBCOLOR TextColor = UI_HSB(0x00, 0x00, 0x00); InitPropVariantFromUInt32(BackgroundColor, &propvarBackground); InitPropVariantFromUInt32(HighlightColor, &propvarHighlight); InitPropVariantFromUInt32(TextColor, &propvarText); spPropertyStore->SetValue(UI_PKEY_GlobalBackgroundColor, propvarBackground); spPropertyStore->SetValue(UI_PKEY_GlobalHighlightColor, propvarHighlight); spPropertyStore->SetValue(UI_PKEY_GlobalTextColor, propvarText); spPropertyStore->Commit(); } }