013 如何創建Filter的屬性頁

Post date: 2015/4/9 上午 01:27:19

摘要: 本篇文檔我們將要講述如何給一個filter創建一個屬性頁, 通過CBasePropertyPage基類。 這篇文檔的實例代碼演示了創建屬性頁的步驟, 這裏我們假設我們要創建屬性頁的視頻filter支持飽和度屬性頁, 這個屬性頁有一個滑動條, 用戶可以通過這個滑動條來控制飽和度。 第一步, 設置屬性的機理 Filter必須支持一種和屬性頁溝通的方式, 通過屬性頁可以設置或者獲取filter的屬性, 下面是可能的三種方式 1暴露一個接口 2通過IDispatch支持自動化屬性 3暴露IPropertyBag 接口, 並定義一系列的屬性 下面的例子利用了一個普通的COM接口, 叫做ISaturaton, 這並不是一個真正的com接口, 只是我們用來在這裏舉例的, 你也可以自己定義任何的com對象。 首先我們在一個頭文件中聲明接口的ID和定義。 // Always create new GUIDs! Never copy a GUID from an example. DEFINE_GUID(IID_ISaturation, 0x19412d6e, 0x6401, 0x475c, 0xb0, 0x48, 0x7a, 0xd2, 0x96, 0xe1, 0x6a, 0x19); interface ISaturation : public IUnknown { STDMETHOD(GetSaturation)(long *plSat) = 0; STDMETHOD(SetSaturation)(long lSat) = 0; }; 你也可以用IDL定義接口, 並用MIDL編譯器創建頭文件, 然後在Filter上實現這個接口, 這個例子採用「Get」, 「Set」方法來設置飽和度的值, 注意, 修改這個m_lSaturation的值的時候一定要進行保護 class CGrayFilter : public ISaturation, /* Other inherited classes. */ { private: CCritSec m_csShared; // Protects shared data. long m_lSaturation; // Saturation level. public: STDMETHODIMP GetSaturation(long *plSat) { if (!plSat) return E_POINTER; CAutoLock lock(&m_csShared); *plSat = m_lSaturation; return S_OK; } STDMETHODIMP SetSaturation(long lSat) { CAutoLock lock(&m_csShared); if (lSat < SATURATION_MIN || lSat > SATURATION_MAX) { return E_INVALIDARG; } m_lSaturation = lSat; return S_OK; } }; 當然你實現接口的一些細節可能和上面的代碼不一致。 反正你自己實現就是了 第二步, 實現ISpecifyPropertyPages接口 做完了上一步, 下面就要在你個filter中實現ISpecifyPropertyPages接口, 這個接口只有一個方法, GetPages, 這個方法返回filter所支持的所有的屬性頁的CLSID。 在這個例子裏, Filter只支持一個屬性頁, 首先產生一個CLSID, 並在頭文件聲明 // Always create new GUIDs! Never copy a GUID from an example. DEFINE_GUID(CLSID_SaturationProp, 0xa9bd4eb, 0xded5, 0x4df0, 0xba, 0xf6, 0x2c, 0xea, 0x23, 0xf5, 0x72, 0x61); 然後要實現ISpecifyPropertyPages接口的GetPages方法: class CGrayFilter : public ISaturation, public ISpecifyPropertyPages, /* Other inherited classes. */ { public: STDMETHODIMP GetPages(CAUUID *pPages) { if (pPages == NULL) return E_POINTER; pPages->cElems = 1; pPages->pElems = (GUID*)CoTaskMemAlloc(sizeof(GUID)); if (pPages->pElems == NULL) { return E_OUTOFMEMORY; } pPages->pElems[0] = CLSID_SaturationProp; return S_OK; } }; /* ... */ } 第三步, 支持QueryInterface 為了暴露Filter的接口, 照著下面的步驟作哦 1 在你的filter中包含DECLARE_IUNKNOWN宏的聲明: Public: DECLARE_IUNKNOWN; 2 重載CUnknown::NonDelegatingQueryInterface 方法來檢查兩個接口的IIDs。 STDMETHODIMP CGrayFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv) { if (riid == IID_ISpecifyPropertyPages) { return GetInterface(static_cast<ISpecifyPropertyPages*>(this), ppv); } if (riid == IID_ISaturation) { return GetInterface(static_cast<IYuvGray*>(this), ppv); } return CBaseFilter::NonDelegatingQueryInterface(riid, ppv); } 第四步, 創建屬性頁 到這一步, filter已經支持一個屬性頁的所需要的東西了, 下一步就是要實現屬性頁本身了。 首先創建一個對話框的資源, 然後以這個對話的資源聲明一個類, 要從CBasePropertyPage. 派生, 如何創建Filter的屬性頁 圖1 下面的代碼顯示了部分的聲明, 包含了我們在後面將要用到的部分變量。 class CGrayProp : public CBasePropertyPage { private: ISaturation *m_pGray; // Pointer to the filter's custom interface. long m_lVal // Store the old value, so we can revert. long m_lNewVal; // New value. public: /* ... */ }; 看看構造函數吧 CGrayProp::CGrayProp(IUnknown *pUnk) : CBasePropertyPage(NAME("GrayProp"), pUnk, IDD_PROPPAGE, IDS_PROPPAGE_TITLE), m_pGray(0) { } 下面, 你還要記得重載CBasePropertyPage 的幾個方法哦 OnConnect, 當屬性頁創建的時候, 會調用這個方法, 通過這個方法將IUnknown指針付給Filter。 OnActivate 當對話框創建的時候被調用 OnReceiveMessage 當對話框接收到窗口消息時被調用 OnApplyChanges當用戶單擊OK或者Apply 按鈕來確認對屬性進行更新時, 調用 OnDisconnect 當用戶取消Property sheet時調用 第五步, 保存filter的一個指針 通過重載CBasePropertyPage::OnConnect方法將一個指針保存到filter, 下面的例子演示了如何通過方法傳遞過來的參數查詢filter支持的接口 HRESULT CGrayProp::OnConnect(IUnknown *pUnk) { if (pUnk == NULL) { return E_POINTER; } ASSERT(m_pGray == NULL); return pUnk->QueryInterface(IID_ISaturation, reinterpret_cast<void**>(&m_pGray)); } 第六步, 初始化對話框 通過重載CBasePropertyPage::OnActivate方法來初始化一個對話框, 在這個例子裏, 屬性頁使用了滑動條, 所以, 在初始化的第一步就是要初始化控件動態庫, 然後再初始化slider。 HRESULT CGrayProp::OnActivate(void) { INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(INITCOMMONCONTROLSEX); icc.dwICC = ICC_BAR_CLASSES; if (InitCommonControlsEx(&icc) == FALSE) { return E_FAIL; } ASSERT(m_pGray != NULL); HRESULT hr = m_pGray->GetSaturation(&m_lVal); if (SUCCEEDED(hr)) { SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETRANGE, 0, MAKELONG(SATURATION_MIN, SATURATION_MAX)); SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETTICFREQ, (SATURATION_MAX - SATURATION_MIN) / 10, 0); SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1, m_lVal); } return hr; } 第七步, 處理窗口消息 重載CBasePropertyPage::OnReceiveMessage方法來處理用戶的輸入等消息。 如果你不想處理消息, 你只需簡單調用父類的OnReceiveMessage 即可。 無論何時用戶改變了屬性, 都會做下面的事情 1 將屬性頁的m_bDirty設置為TRUE; 2調用屬性框的IPropertyPageSite::OnStatusChange方法, 並傳遞一個PROPPAGESTATUS_DIRTY, 這個標誌用來通知property frame應該將Apply按鈕可用, CBasePropertyPage::m_pPageSite變量保存著一個IPropertyPageSite接口 為了簡化步驟, 你可以在你的屬性頁中添加下面的代碼 private: void SetDirty() { m_bDirty = TRUE; if (m_pPageSite) { m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY); } } 當用戶改變了屬性的時候, 在OnReceiveMessage方法中調用上面的函數。 BOOL CGrayProp::OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: if (LOWORD(wParam) == IDC_DEFAULT) { // User clicked the 'Revert to Default' button. m_lNewVal = SATURATION_DEFAULT; m_pGray->SetSaturation(m_lNewVal); // Update the slider control. SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1, m_lNewVal); SetDirty(); return (LRESULT) 1; } break; case WM_HSCROLL: { // User moved the slider. switch(LOWORD(wParam)) { case TB_PAGEDOWN: case SB_THUMBTRACK: case TB_PAGEUP: m_lNewVal = SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_GETPOS, 0, 0); m_pGray->SetSaturation(m_lNewVal); SetDirty(); } return (LRESULT) 1; } } // Switch. // Let the parent class handle the message. return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam); } 第八步, 處理屬性的改變 重載CBasePropertyPage::OnApplyChanges方法來提交屬性頁的改變, 如果用戶單擊了確定, 或者應用按鈕, OnApplyChanges方法都會調用到 HRESULT CGrayProp::OnApplyChanges(void) { m_lVal = m_lNewVal; return S_OK; } 第九步, 斷開屬性頁連接 重載CBasePropertyPage::OnDisconnect方法來釋放你在OnConnect方法中請求的所有的接口, 如果用戶沒有更新屬性, 而是單擊了取消按鈕, 你還要將屬性的原始值保存下來。 當用戶單擊取消按鈕, 但是沒有相應的響應這個消息的方法, 所以, 你要檢查用戶是否調用了OnApplyChanges方法, 看看例子也好: HRESULT CGrayProp::OnDisconnect(void) { if (m_pGray) { // If the user clicked OK, m_lVal holds the new value. // Otherwise, if the user clicked Cancel, m_lVal is the old value. m_pGray->SetSaturation(m_lVal); m_pGray->Release(); m_pGray = NULL; } return S_OK; } 第十步, 支持com的註冊 最後一步就是要支持com的註冊, 因此 屬性框才能夠創建你屬性頁的實例, 首先在全局數組g_Templates添加一個類廠模板的說明。 這個全局的數組是你的DLL中創建的所有的com對象都要用到的。 const AMOVIESETUP_FILTER FilterSetupData = { /* Not shown ... */ }; CFactoryTemplate g_Templates[] = { // This entry is for the filter. { wszName, &CLSID_GrayFilter, CGrayFilter::CreateInstance, NULL, &FilterSetupData }, // This entry is for the property page. { L"Saturation Props", &CLSID_SaturationProp, CGrayProp::CreateInstance, NULL, NULL } }; 如果你用下面的方式聲明全局數組, 數組的大小就會自動地得到修改 int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]); 同時, 還要在屬性頁類中添加一個CreateInstance方法 static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr) { CGrayProp *pNewObject = new CGrayProp(pUnk); if (pNewObject == NULL) { *pHr = E_OUTOFMEMORY; } return pNewObject; } 如果想測試屬性頁, 可以註冊DLL, 然後將filter加載到GraphEdit, 鼠標右擊來查看filter的屬性。