Visual Studio 2010響應Ribbon控件消息
Visual Studio 2010與Windows 7的完美配合,在本文中體現在Visual Studio 2010響應Ribbon控件消息上。UI美工們已經幫我們完成了設計工作,下面現在,該輪到程序員上場了!51CTO編輯推薦《Visual Studio 2010應用與開發詳解》
在Ribbon界面編輯完成的基礎上,我們來看看如何利用Windows Scenic Ribbon API處理各種控件消息,讓Ribbon界面真正地投入使用。
如何利用XML文件創建Ribbon控件,對控件進行排布和設置控件的縮放策略等等,可以說,這些工作都是UI設計師的任務。對程序員來說,更重要的是對控件的消息進行處理實現其業務邏輯。
添加消息處理功能
我們可以創建了一個派生自IUIApplication的Ribbon界面宿主對象類CApplication,并利用這個類完成了Ribbon界面的創建,初始化以及與Windows應用程序的集成。要對Ribbon控件的消息進行處理,我們還是要借助這個宿主對象類。為了使得CApplication類具有控件消息處理的能力,我們需要修改它的定義,讓它同時也從IUICommandHandler派生。而IUICommandHandler類,則是Scenic Ribbon API提供給我們的控件消息處理類,只要CApplication從這個類派生,就具有了相應的控件消息的處理能力。
為了對Visual Studio 2010響應Ribbon控件消息進行處理,我們修改CApplication類的定義如下:
- // 引入Scenic Ribbon API的頭文件
- #include <uiribbon.h>
- // 引入編譯生成的資源頭文件
- #include "ribbonres.h"
- // 調用Windows API獲得系統時間
- #include "windows.h"
- #include "stdio.h"
- IUIFramework* g_pFramework = NULL;
- // Ribbon界面宿主程序
- class CApplication
- : public CComObjectRootEx<CComMultiThreadModel>
- , public IUIApplication // 處理Ribbon界面的創建和初始化
- , public IUICommandHandler // 處理Ribbon控件的消息
然后,我們需要利用COM_INTERFACE_ENTRY宏定義COM_MAP,在CApplication類的定義中,添加:
- public:
- BEGIN_COM_MAP(CApplication)
- COM_INTERFACE_ENTRY(IUIApplication)
- COM_INTERFACE_ENTRY(IUICommandHandler)
- END_COM_MAP()
這樣,Visual Studio 2010響應Ribbon控件的消息就會發送到CApplication類,由CApplication類進行處理。在整個Scenic Ribbon API中,Ribbon控件消息的處理流程如下圖所示。除了之前我們介紹的跟Ribbon界面創建和初始化相關的過程之外,為了完成控件消息,我們首先需要為感興趣的控件進行注冊,這些工作都會在OnCreateCommand函數中完成。完成控件的注冊后,當我們需要進行消息處理的控件有消息發生時,IUIFramework就會將這些消息發送給CApplication,而我們就可以在CApplication類中對具體的消息進行處理。Ribbon控件的消息主要分成兩種:一種是來自控件的動作消息,比如按鈕控件被點擊,ComboBox的選擇發生了變化等。這類消息都在Execute函數中進行處理;另外一種是則對控件屬性進行更新的消息,這類消息在UpdateProperty函數中進行處理。

注冊感興趣的控件
按照Ribbon界面控件消息的處理流程,我們首先需要在OnCreateCommand函數中為我們感興趣的控件進行注冊,這樣當控件有動作發生的時候,CApplication類才會收到相應的控件消息,進而可以對其進行處理。在CApplication類的OnCreateCommand函數中,我們完成相應控件的注冊:
- STDMETHOD(OnCreateUICommand)(UINT32 nCmdID, __in UI_COMMANDTYPE typeID, __deref_out IUICommandHandler** ppCommandHandler)
- {
- // 對相應控件的消息進行注冊
- if (nCmdID == cmdMyButton
- || nCmdID == cmdDeleteTable
- || nCmdID == cmdAddTable
- || nCmdID == cmdPrintRelationships)
- {
- return QueryInterface(IID_PPV_ARGS(ppCommandHandler));
- }
- return E_NOTIMPL;
- }
在這段代碼中,我們根據控件的Symbol選取了幾個需要進行消息處理的控件,當這幾個控件有動作發生時,比如比鼠標點擊,或者是進行了選擇,IUIFramework會發送相應的消息給宿主對象CApplication,從而讓我們可以有機會對這些消息進行處理。
處理控件消息
對于普通的控件點擊消息或者是選擇消息等動作消息,我們需要重寫CApplication類的消息處理函數Execute函數,在其中對消息進行處理:
- // 消息處理函數
- 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 == cmdMyButton)
- {
- // 執行具體的業務邏輯
- // 這里我們獲得系統時間并進行輸出
- SYSTEMTIME sys;
- GetLocalTime( &sys );
- wchar_t strInfo[256] = L"";
- wsprintf( strInfo, L"當前系統時間:
- %4d/%02d/%02d %02d:%02d:%02d.%03d 星期%1d\n",
- sys.wYear,sys.wMonth,sys.wDay,
- sys.wHour,sys.wMinute,sys.wSecond,sys.wMilliseconds,
- sys.wDayOfWeek);
- // 顯示消息框
- MessageBox(NULL, strInfo,
- L"當前系統時間”,
- MB_OK);
- }
- break;
- }
- return hr;
- }
現在編譯運行這個解決方案,當我們點擊cmdMyButton 所對應的“MyButton”這個按鈕時,就可以得到一個消息框報告當前的系統時間。當然,我們這里只是對按鈕控件的動作進行處理,對于其他類型控件的消息處理,都是按照相同的流程進行。
圖2 按鈕控件的點擊事件
在運行時對控件屬性進行修改
在某些情況下,我們需要在運行時對控件的屬性進行修改。例如,在應用程序運行的某種狀態下,我們可能需要禁用某些控件,或者是修改控件的標簽文本,圖標等等。對控件屬性的修改,可以通過直接修改控件屬性達到,也可以通過調用InvalidateUICommand函數刷新控件的屬性,然后在UpdateProperty函數中進行控件屬性更改消息處理,實現具體的屬性修改。下面我們分別來看看這兩種方式是如何進行的。
在這個例子中,我們處理兩個按鈕控件的點擊消息,讓他們分別禁用另外的按鈕控件和修改按鈕的標簽文本。在Execute函數中,處理相應的按鈕消息,實現控件屬性的改變:
- 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 == cmdMyButton)
- {
- //
- PROPVARIANT varNew;
- BOOL _fEnabled = FALSE;
- // 初始化屬性值
- hr = UIInitPropertyFromBoolean(UI_PKEY_Enabled,
- _fEnabled, &varNew);
- if (FAILED(hr))
- {
- return hr;
- }
- // 為控件cmdDeleteTable設置新的屬性值
- hr = g_pFramework->SetUICommandProperty(cmdDeleteTable,
- UI_PKEY_Enabled, varNew);
- if (FAILED(hr))
- {
- return hr;
- }
- }
- else if (nCmdID == cmdAddTable)
- {
- // 發送屬性更新消息對控件屬性UI_PKEY_Label進行更新
- hr = g_pFramework->InvalidateUICommand(
- cmdPrintRelationships,
- UI_INVALIDATIONS_PROPERTY, &UI_PKEY_Label);
- if (FAILED(hr))
- {
- return hr;
- }
- }
- break;
- }
- return hr;
- }
在cmdMyButton按鈕的消息處理中,我們使用SetUICommandProperty就可以直接修改控件的屬性了。在這里,我們通過設置cmdDeleteTable按鈕控件的UI_PKEY_Enabled屬性為FALSE,達到了禁用這個控件的目的。而在cmdAddTable按鈕的消息處理中,我們只是調用了IUIFramework的InvalidateUICommand函數,這表示它會自動調用CApplication類的UpdateProperty來實現控件屬性的更新,所以對于第二種方式,我們還需要實現這個函數,在其中完成屬性的更新:
- STDMETHODIMP UpdateProperty(UINT nCmdID,
- __in REFPROPERTYKEY key,
- __in_opt const PROPVARIANT* ppropvarCurrentValue,
- __out PROPVARIANT* ppropvarNewValue)
- {
- UNREFERENCED_PARAMETER(ppropvarCurrentValue);
- HRESULT hr = E_FAIL;
- if (key == UI_PKEY_Label)
- {
- // 更新控件cmdPrintRelationships的標簽文本
- if (nCmdID == cmdPrintRelationships)
- {
- hr = UIInitPropertyFromString(UI_PKEY_Label,
- L"New Label", ppropvarNewValue);
- }
- }
- return hr;
- }
現在,我們就可以編譯運行整個解決方案,點擊相應的按鈕控件,就可以看到對控件屬性修改的效果了:
圖3 修改控件屬性
到這里,我們完成了整個Ribbon歷程:從創建XML文件到添加宿主對象,從創建到初始化,從控件消息處理到控件屬性更新。現在,Ribbon界面對我們來說,已經不再僅僅是微軟的一種界面技術,她切切實實地來到了我們身邊,可以為我們所用,提高應用程序的用戶體驗。
擁抱Ribbon,擁抱Windows 7,擁抱Visual Studio 2010!
【編輯推薦】