如何创建Filter的属性页
本篇文档我们将要讲述如何给一个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, { 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, { 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. 派生,
图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 = { }; 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的属性。 采集参数的设置
采集前需要对要采集的视频格式、图像质量进行设置,如视频的分辨率、帧率和数据格式,图像的亮度、色度和饱和度参数设置等。下面分别对这两种设置进行讲述。
1.视频格式设置
对采集设备的参数进行配置,首先确定设备的类型:VFW、WDM驱动模型。目前流行的采集设备大部分采用WDM模式,采集设备在 DirectShow中被当做一个滤波器组件,所以配置采集设备就是对该滤波器的引脚进行设置。在确保滤波器链表完全建立前,可以使用如下的过程配置视频 格式。
void CCaptureClass::ConfigCameraPin(HWND hwndParent) { HRESULT hr; //返回值 IAMStreamConfig *pSC; //流配置接口 ISpecifyPropertyPages *pSpec; //属性页接口 //只有停止后,才能进行引脚属性的设置 m_pMC->Stop(); //首先查询捕获CAPTURE、视频Video接口 hr = m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pBF, IID_IAMStreamConfig, (void **)&pSC); CAUUID cauuid; //所有属性页结构体 hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); //获取所有属性页 //显示属性页 hr = OleCreatePropertyFrame(hwndParent, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); //释放内存、资源 CoTaskMemFree(cauuid.pElems); pSpec->Release(); pSC->Release(); } //回复运行 m_pMC->Run(); } |
上述代码首先以视频捕获方式获取流配置接口,在该接口下查询属性页接口,然后获取所有属性页,接着显示获取的属性页,最后释放资源。在显示了属性页后用户就可以对其属性页的对话框进行设置了,运行后的结果如图9-1所示。
![]() |
(点击查看大图)图9-1 视频格式属性设置 |
2.图像参数设置
图像参数设置类似于视频格式配置,根据设备的驱动类型VFW、WDM使用不同的技术配置设备的参数,这里我们同样假定采集设备的类型为WDM,配置过程如下。
void CCaptureClass::ConfigCameraFilter(HWND hwndParent) { HRESULT hr=0; ISpecifyPropertyPages *pProp; hr = m_pBF->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pProp); if (SUCCEEDED(hr)) { //获取滤波器名称和IUnknown接口指针 FILTER_INFO FilterInfo; hr = m_pBF->QueryFilterInfo(&FilterInfo); IUnknown *pFilterUnk; m_pBF->QueryInterface(IID_IUnknown, (void **)&pFilterUnk); //显示该页 CAUUID caGUID; pProp->GetPages(&caGUID); OleCreatePropertyFrame( hwndParent, //父窗口 0, 0, //Reserved FilterInfo.achName, //对话框标题 1, //该滤波器的目标数目 &pFilterUnk, //目标指针数组 caGUID.cElems, //属性页数目 caGUID.pElems, //属性页的CLSID数组 0, //本地标识 0, NULL //Reserved ); //释放内存、资源 CoTaskMemFree(caGUID.pElems); pFilterUnk->Release(); FilterInfo.pGraph->Release(); pProp->Release(); } m_pMC->Run(); } |
捕获滤波器属性是对图像参数进行配置和修改,包括图像参数、白平衡和模式控制。这些参数一旦更改,预览的视频图像马上起作用。当调整到合适的情况下,单击"确定"按钮,关闭该对话框,运行后的结果如图9-2所示。
![]() |
(点击查看大图)图9-2 运行后的结果 |
如何创建Filter的属性页的更多相关文章
- MFC属性页对话框
属性页对话框 分类 分页和引导 类 CPropertyPage-父亲CDialog类别,所谓的属性页或网页对话框. CPropertySheet-父类是CWnd,称为属性表单. 一个完整的属性页对话框 ...
- directshow filter中添加属性页
directShow 属性页的制作,为CBall filter加了一个属性页 具体为分以下步骤: 1.在要显示属性的类中继承现ISpecifyPropertyPages类,并实现此类的GetPages ...
- MFC编程入门之十五(对话框:一般属性页对话框的创建及显示)
属性页对话框包括向导对话框和一般属性页对话框两类,上一节讲了如何创建并显示向导对话框,本节将继续介绍一般属性页对话框的创建和显示. 实际上,一般属性页对话框的创建和显示过程和向导对话框是很类似的.将上 ...
- VS2010/MFC对话框:一般属性页对话框的创建及显示
一般属性页对话框的创建及显示 本节将介绍一般属性页对话框的创建和显示. 实际上,一般属性页对话框的创建和显示过程和向导对话框是很类似的.鸡啄米将上一节中的向导对话框进行少量修改,使其成为一般属性页对话 ...
- VS2010/MFC编程入门之十五(对话框:一般属性页对话框的创建及显示)
属性页对话框包括向导对话框和一般属性页对话框两类,上一节鸡啄米讲了如何创建并显示向导对话框,本节将继续介绍一般属性页对话框的创建和显示. 实际上,一般属性页对话框的创建和显示过程和向导对话框是很类似的 ...
- VS2010-MFC(对话框:一般属性页对话框的创建及显示)
转自:http://www.jizhuomi.com/software/169.html 属性页对话框包括向导对话框和一般属性页对话框两类,上一节演示了如何创建并显示向导对话框,本节将继续介绍一般属性 ...
- MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)
前面讲了模态对话框和非模态对话框,本节来将一种特殊的对话框--属性页对话框. 属性页对话框的分类 属性页对话框想必大家并不陌生,XP系统中桌面右键点属性,弹出的就是属性页对话框,它通过标签切换各个页面 ...
- 坑爹的vector iterators incompatible错误(VS中属性页-->C/C++-->代码生成-->>运行库)
之前一直被这个错误折磨着,就是不知道问题在那,后来找了很多资料,大概都是说这是因为多个线程同时操作vector的问题(参考这里).可是我这里的代码并没有问题,因为同样的代码在别的解决方案中已经成功运行 ...
- VC++在对话框中加入属性页
当一个基于对话框的程序中有相当多的控件时,你一定会想到使用属性页来将这些控件分类放置.本文针对这种方法来讨论几种可能实现的方案. 方案一本方案的例子请见源代码打包文件中的Property1部分 在对话 ...
随机推荐
- xBIM 应用与学习 (一)
目录 xBIM 应用与学习 (一) xBIM 应用与学习 (二) xBIM 基本的模型操作 xBIM 日志操作 XBIM 3D 墙壁案例 xBIM 格式之间转换 xBIM 使用Linq 来优化查询 x ...
- DevExtreme 学习应用[3]
DevExtreme dxSelectBox 联动查询案例 //数据获取 lookupDataSource = new DevExpress.data.DataSource({ store: stor ...
- 为Ghost博客扩展代码高亮、数学公式、页面统计、评论
前几天捣鼓了一下博客首页,接下来再丰富一下博客页面的功能与内容.由于我所使用的Ghost博客专注于轻量简洁,因此标题中提到的功能在Ghost中默认均不支持.下面将逐个介绍一下如何为Ghost扩展这些功 ...
- 探索从 MVC 到 MVVM + Flux 架构模式的转变
本文首发于 my blog 在业务中一般 MVVM 框架一般都会配合上数据状态库(redux, mobx 等)一起使用,本文会通过一个小 demo 来讲述为什么会引人数据状态库. 从 MVC 到 MV ...
- WebService 的工作原理
Web Service基本概念 Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的 ...
- MySQL5学习笔记(三)
系统变量 mysqld服务器维护两种变量.全局变量(GLOBAL)影响服务器整体操作.会话变量(SESSION)影响具体客户端连接的操作. 当服务器启动时,它将所有全局变量初始化为默认值.这些 ...
- 利用Lua读写本地文件
缘由 今天在使用Lua编写脚本时,需要用到读写文件的操作,很久没有使用Lua了,特写下此文来备忘一下. 简介 Lua对文件的操作与C对文件的操作基本一致,不管是参数还是方法.Lua中可以直接通过全局方 ...
- pygame写贪吃蛇
python小白尝试写游戏.. 学了点pygame不知道那什么练手好,先拿贪吃蛇开刀吧. 一个游戏可以粗略的分为两个部分: 数据(变量) 处理数据(函数,方法) 设计变量 首先预想下,画面的那些部分需 ...
- 从此不再担心键盘遮住输入框OC(
从此不再担心键盘遮住输入框OC(二) 字数544 阅读1492 评论15 喜欢25 在我发布这篇文章没多久之前,我发布了一篇叫 从此不再担心键盘遮住输入框OC(一)的文章.我在那篇文章中介绍了我的键盘 ...
- cocos2d-x中处理touch事件
在cocos2d-x中, touch事件分为两种:一种是单点事件, 另一种是多点事件. 单点事件对应的代理方法是: virtual bool ccTouchBegan(CCTouch *pTouch, ...