浅尝辄止——使用ActiveX装载WPF控件
1 引言
使用VC编写的容器类编辑器,很多都可以挂接ActiveX控件,因为基于COM的ActiveX控件不仅封装性不错,还可以显示一些不错的界面图元。
但是随着技术不断的进步,已被抛弃的ActiveX早已无法满足现代客户对审美的新需求,所以我们需要在这条道路上不断的独辟蹊径,今天提到的使用ActiveX装载WPF控件就是其中一条思路。
WPF(Windows Presentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面。与传统的VC界面库相比,WPF不需要额外的界面库,通过xml格式配置显示页面,通过C#实现内部业务逻辑,在图形化方面WPF基于DirectX引擎,支持GPU硬件加速,可以给用户完美的体验。关于WPF的优点我就不多说了,读者可以自行查资料补脑。
虽然我对WPF技术也是一知半解(不过可以肯定的是与COM技术相比,WPF还是相当简单的),本文的主要目的是让ActiveX与WPF结合起来,让我们原始的VC工程重新复活起来,下面切入正题吧。
2 托管编程
有人可能用到过在C#调用C++代码或者COM对象,非常的简单,C#已经为用户做好的完善的封装的转换,直接导进来用就行,但在C++中使用C#还是比较苦难的,需要使用托管编程的方式。
C++托管编程的语法与传统C++和C#都有不同,所以这里需要简单介绍一下如何创建并修改ActiveX控件的代码。
2.1 启动托管编程
启动托管编程就是告诉编译器,我的C++代码里有托管代码,你编译的时候注意点。
在ActiveX工程的属性页面里,General页的“Common Language Runtime Support”,设置成“Common Language Runtime Support (/clr)”。
除此之外,字符集请选择“Unicode”,我们发现从VS2013之后,多字符集已经默认不支持了,需要单独安装一个补丁才能支持,这可以理解为微软的一个信号,建议大家创建工程时最好使用unicode
其他的差别我就不多说了。
2.2 编程语法
托管类:ref class CMyClass{...。“ref”表示后边声明的类是一个托管类,托管类被创建之后,内存是放在托管堆上的,也就是会自动释放(也可以手动释放delete p; p=nullptr;),不需要我们考虑如何删除。代码中并不是所有类都需要设置成托管类,原始的继承自CWnd等MFC类的子类最好还是保留原始结构。我们在托管类中可以使用一些C#提供的东西,引入C#C#的各类资源。
托管对象创建:在非托管类中,通过 gcroot<CMyClass^> m_pMyClass 定义一个托管对象指针;在托管类中,不需要使用gcroot,可以直接只用 “Class^”。
初始化托管对象:创建对象代码为 m_pMyClass = gcnew CMyClass();,只要需要创建托管对象指针,都需要使用 “gcnew”关键字。
指针:指向托管对象的指针为 “^”,所有从C#一侧得到的对象都是指针,都需要使用 “^”指向它。
数组:与C#之间传递数组或链表时,C++中只能使用“array^”,例如定义一个int数组 “array<int>^ m_arrInt;”,初始化代码 “m_arrInt = gcnew array<int>(500)”。
2.3 引入类库
C#提供了丰富的类库,使用前需要引入,在工程属性页最上面的 “Common Properties”,点击子节点 “References”,添加你想要的库吧。
3 开始编程吧
如果没有ActiveX基础或COM基础或WPF基础的童鞋,可以先去学习,有兴趣再来看。
3.1 准备WPF工程
首先需要编写一个WPF的工程,输出dll格式,我们假设这个WPF的工程中,有一个WPF界面类,比如叫WPFSample,那么将会有WPFSample.xaml和WPFSample.xaml.cs等文件,其中xaml是界面,cs是后台处理代码,具体怎么实现的我就不多说了。
3.2 创建ActiveX工程
创建ActiveX工程,输出ocx格式,比如这个ActiveX叫WPFShow,那么创建好后,就会有WPFShowCtrl文件,这个里边是控件的类CWPFShowCtrl。
除CWPFShowCtrl外,我们还需要提供一个用于管理WPF对象的容器,我们称其为WPFContainer类。
3.3 DesignTime(DT)
为了在DesignTime下能够看到WPF控件,我们需要使用一个Wnd对象,我们称其为CMyWnd吧,先让WPF画在这个Wnd上,再从Wnd上获得图像画到CDC上。
这部分是最难的,在编辑模式下,ActiveX对象并没有被创建出来,但有些时候我们却想要显示出来这个控件的样子,我的做法是这样的。
(1)先装载WPF
最好使用一个类去加载WPF。
.h实现——示意代码
using namespace "System","System::Windows::Interop","System::Reflection","WPF文件里的命名空间" class CMyWnd:pubilc CWnd(){ WPFContainer m_WpfDT; // 在Design Time下管理WPFContainer }; ref class WPFContainer{ // 这个类需要在Ctrl类里通过gcnew创建出来 HwndSource^ m_pHwndSource; // WPF对象会显示在这上面 HwndSourceParameters^ m_sourceParams; // 用于初始化HwndSource Assembly^ m_pCtrlAssem; // WPF对象的Assembly WPFSample^ m_WpfObj; // WPF对象 ... // 还有一些属性,我们后续再说 }; |
.cpp实现——创建WPF对象,示意代码
m_pCtrlAssem = Assembly::LoadFrom(strWpfFilePathName); // 加载WPF的dll文件 Type^ wpfType = m_pCtrlAssem->GetType(_T("WPFSample")); if (wpfType == nullptr){...} // 错误处理 m_ObjWpf = (WFSample^)Activator::CreateInstance(wpfType); // 创建WPF对象 |
(2)OnDraw画处理
ActiveX的主类CWPFShowCtrl,在Design time状态下,只有OnDraw方法被调用,这个方法是“OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)”
我们现在就是在这里将WPF画在CDC上,从而上用户能够看得到
.cpp实现——CWPFShowCtrl::OnDraw,示意代码
CDC memDC; CClientDC dc(NULL); CRect rcBoundsDP(rcBounds); CRect rcBitmap(0, 0, rcBoundsDP.Width(), rcBoundsDP.Height()); if (!memDC.CreateCompatibleDC(&dc)){return;} // 错误处理 // 创建一个Bitmap bool bSizeChanged = (m_rectLast!=rcBitmap); // 判断是否改变大小了,是否需要resize if (!m_Bitmap.GetSafeHandle() || bSizeChanged){ if (m_Bitmap.GetSafeHandle()){m_Bitmap.DeleteObject();} m_Bitmap.CreateCompatibleBitmap(&dc, rcBitmap.Width(), rcBitmap.Height()); m_rectLast = rcBitmap; } CBitmap *pOldBitmap = memDC.SelectObject(&m_Bitmap); CBrush brush(RGB(255, 255, 255)); memDC.SetBkColor(0); SetTextColor(xx); FillRect(&rcBitmap, &brush); memDC.SaveDC(); // 从WPF里获得CDC CMyWnd对象->DrawWPF(&memDC, rcBounds); // 这个对象还会再调WPFContainer去画 memDC.RestoreDC(nSaveDC); pdc->BitBlt(rcBoundsDP.left, rcBoundsDP.top, rcBoundsDP.Width(), rcBoundsDP.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBitmap); |
.cpp实现——CWPFContainer::OnDraw,示意代码
int w = rcBounds.Width(), h = rcBounds.Height(); RenderTargetBimap^ bmpRen; // usercontrol to bitmapencoder BitmapEncoder^ encoder; // bitmapencoder MemoryStream^ stream; // bitmapencoder to stream Bitmap^ bitmap; // get bitmap(c#) from stream try{ // c# try catch m_WpfObj->Resize(w, h); // 这个函数是要你们自己实现的,放大缩小功能是必要的,除非大小已经定死了 // get render System::Windows::Controls::UserControl^ pUC = (System::Windows::Controls::UserControl^)m_WpfObj; pUC->Width = w; ->Height = h; pUC->UpdateLayout() // let it redraw bmpRen = gcnew RenderTargetBimap(w, h, 0, 0, PixelFormats::Pbgra32); bmpRen->Render(pUC); // get C# bitmap encoder = gcnew BmpBitmapEncoder(); stream = gcnew MemoryStream(); encoder->Frames->Add(BitmapFrame::Create(bmpRen)); encoder->Save(stream); bitmap = gcnew Bitmap(stream); // bitmap from C# to C++ if (pDC){ IntPtr bitmapPtr = bitmap->GetHbitmap(); HBITMAP hBitmap = static_cast<HBITMAP>(bitmapPtr.ToPointer()); // draw cdc pDC->DrawState(CPoint(0,0), CSize(w, h), hBitmap, DST_BITMAP|DSS_NORMAL); DleleteObject(hBitmap); } } catch (Exception^ ex){} // 异常处理 finally{ // 释放资源 if (bmpRen !=nullptr) delete bmpRen; if (encoder != nullptr) delete encoder; if (stream != nullptr) delete stream; if (bitmap != nullptr) delete bitmap; } |
ok了,看看design time状态下是否可以显示WPF了。
此时虽然能看到,但不能够编辑,ActiveX为编辑状态提供了PropertyPage(属性页)功能,可以设置一些信息,这一点我们也可以实现,请看后面的PropertySet一节。
3.4 RunTime(RT)
参考DesighTime的方法,运行模式下显示WPF对象已经非常简单了,此时我们不需要CMyWnd参与,而是在CWPFShowCtrl里直接控制一个WPFContainer。
CWPFShowCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)方法
示意代码
m_WPFCtrl = gcnew WPFContainer(this); // 传递this指针,因为自己就是wnd,让WPF画在自己身上 m_WPFCtrl->LoadFile(strWpfFilePathName); // 让WPF加载并创建出WPF对象,这个过程上文已经提过了 |
示意代码,在WPFContainer里创建WPF对象
System::Windows::Controls::UserControl^ uc = (System::Windows::Controls::UserControl^)m_WpfObj; m_sourceParams = gcnew HwndSourceParameters("WPFSample"); // 根据名称创建对象 m_sourceParams->SetPosition(x, y); // 设定位置和大小 m_sourceParams->SetSize(w, h); m_sourceParams->ParentWindow = IntPtr(m_pParentWnd->GetSafeHwnd()); // m_pParentWnd就是外层的Wnd对象,DT和RT不是一个 m_sourceParams->WindowsStyle = WS_VISIBLE|WS_CHILD; m_pHwndSource = gcnew HwndSource(*m_sourceParams); m_pHwndSource->SizeToContent = SizeToContent::WidthAndHeight; FrameworkElement^ fe = (FrameworkElement^)m_WpfObj; // 立即在wnd上显示出来 m_pHwndSournce->RootVisual = fe; m_WpfObj->Resize(w, h); |
3.5 PropertySet
上文我们分别在DT和RT下创建了WPF对象并显示出来,本节将提供如何显示出PropertyPage的方法。
如果你就打算在ActiveX里自己画出来PropertyPage,用户配置完参数后,再通过接口传递给WPF,那么不需要看本节了,本节的工作是WPF控件里有PropertyPage(与WPF control本身一样的界面代码),想在ActiveX的PropertyPage里显示。
这部分可能需要调整一下代码了,需要ActiveX和WPF相互配合。
ActiveX需要在编译器之前就提供PropertyPage的数量,通过MFC的宏设定出来:
BEGIN_PROPPAGEEIDS(CWPFShowCtrl, 2)
PROPPAGEID(guid1)
PROPPAGEID(guid2)
END_PROPPAGEIDS
但是,如果我们不想再修改了WPF后还要花时间改ActiveX,或者ActiveX根本就不知道加载的WPF有哪些属性框,怎么办。
我的想法可能比较傻,就是先创建十个八个的假propertypage,然后然后WPF有多少就显示多少个,如果我们创建了十个PropertyPage容器,那么所有加载的WPF最多只能有十个属性页,可少。
创建一个用于显示PropertyPage的子类
.h,示意代码
// 定义用于创建多个page对象的宏 #define NEW_PROPPAGE_CLASS(n, caption) \ calss CWPFPropPage##n : public CPropertyPageBase {\ enum {IDD = IDD_PROPPAGE_WPFHOLD}; DECLARE_DYNCREATE(CWPFPropPage##n) \ DECLARE_OLECREATE_EX(CWPFPropPage##n) \ public: CWPFPropPage##n():CPropertyPageBase(IDD, caption, n){;}} #define NEW_PROPPAGE_CLASS_CPP(n, name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, ids) \ IMPLEMENT_DYNCREATE(CWPFPropPage##n, CPropertyPageBase) \ IMPLEMENT_OLECREATE_EX(CWPFPr4opPage##n, name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ BOOL CWPFPropPage##n::CWPFPropPage#nn##Factory::UpdateRegistry(BOOL bRegister) {\ if (bRegister) return AfxOleRegisterPropertyPageClass(AfxGetInstanceHandle(), m_clsid, ids); \ else return AfxOleUnregisterClass(m_clsid, NULL); } // 创建propertypage子类代码 class CPropertyPageBase : public COlePropertyPage { UINT m_nPropID; ... }; // 定义多个page页 NEW_PROPPAGE_CLASS(0, IDS_WPFCTRL_PPG_CAPTION0); NEW_PROPPAGE_CLASS(1, IDS_WPFCTRL_PPG_CAPTION1); NEW_PROPPAGE_CLASS(2, IDS_WPFCTRL_PPG_CAPTION2); ... |
.cpp示意代码
// 创建多个page页 NEW_PROPPAGE_CLASS(0, "WPFCtrl.WPFCtrlPropPage0", 0xb3d4a12c, 0x0251, 0x4301, 0xa1, 0xb5, 0x33, 0x32, 0x12, 0x44, 0x4e, 0xc1, IDS_WPFCTRL_PPG0); ... // 太多了就不打了,guid是通过生成器生成好的 下面列几个关键函数 CPropertyPageBase::DoDataExchange(CDataExchange* pDX){ if (pDX->m_bSaveAndValidate) OnApplyNow(); // 当用户点击 "Apply"按钮时,这里要触发保存函数,告诉WPF控件,保存一下你的所有属性 DDP_PostProcessing(pDX); } CPropertyPageBase::OnPropertyModify(WPARAM, LPARAM){SetModifyFlag(TRUE); return S_OK;} // 数据改变后通知上层使能Apply按钮 CPropertyPageBase::OnSetPageSite(){ SetpageName(g_WpfObj->GetPageName(m_nPropID)); // 每个页面有一个名字(显示在标签页上),这个名字需要从WPF控件里得到 } CPropertyPageBase::OnInitDialog(){ // 参考之前RT和DT代码,将WPF控件中的Page页面显示在CPropertyPageBase上 } CPropertyPageBase::OnDestroy(){ COlePropertyPage::OnDestroy(); // 释放WPF相应的资源 } |
在WPF的代码里,除了需要创建主页外,还需要创建对应的PropertyPage页面,每个Page页面都需要知道自己的ID和Name.
4 总结
文本介绍如何通过ActiveX显示WPF控件的方法,其中最关键的技术主要是:DT下如果得到WPF的bitmap然后draw到ActiveX里;RT下将WPF控件创建出来并贴到ActiveX上。
结尾又介绍了关于在ActiveX控件里显示出来WPF的属性页,这个工作主要针对那些想做通用ActiveX的朋友,如果你想做一个通用的用于显示WPF的ActiveX控件,也就是在加载WPF之前根本就不知道具体的WPF控件都有说明时,可以试试这个方法,前提是需要WPF配合你提供相应的页面,能够通知ActiveX有多个page,每个page的name是什么等等。
了解了托管编程的语法后,剩下的在ActiveX里调用WPF的类,属性,方法等,则可以通过反射,或提供一个接口类,让WPF去继承,等等很多种方式,我就不再多说了。
完。
浅尝辄止——使用ActiveX装载WPF控件的更多相关文章
- XMAL语法系列之-(2)---WPF控件继承图
WPF控件继承图 1 FrameworkElement 1.1 Panel(面板类元素) 1.1.1 Canvas 1.1.2 DockPanel 1.1.3 Grid 1.1.4 TabPanel ...
- 通过WinForm控件创建的WPF控件无法输入的问题
今天把写的一个WPF程序发布到别的机器上执行,发现一个比较奇怪的问题:在那个机器上用英文输入法无法输入数字,非要切换到中文输入法才行:但在我的机器上却是好好的. 最开始以为是输入法的问题,弄了好一阵子 ...
- WPF控件--利用Winform库中的NotifyIcon实现托盘小程序
WPF控件--NotifyIcon 运行界面如下所示: 图1 图2 代码很少,如下所示 ...
- (转)WPF控件开源资源
(转)WPF控件开源资源 Textbox Drag/Drop in WPFhttp://www.codeproject.com/Articles/42696/Textbox-Drag-Drop-in- ...
- WPF控件模板
引言:在进行WPF项目开发过程中,由于项目的需要,经常要对某个控件进行特殊的设定,其中就牵涉到模板的相关方面的内容.本文也是在自己进行项目开发过程中遇到控件模板设定时集中搜集资料后整理出来的,以供在以 ...
- 关于WinForm引用WPF窗体---在Winform窗体中使用WPF控件
项目中有个界面展示用WPF实现起来比较简单,并且能提供更酷炫的效果,但是在WinForm中使用WPF窗体出现了问题,在网上找了一下有些人说Winform不能引用WPF的窗体,我就很纳闷,Win32都能 ...
- 我的WPF控件库——KAN.WPF.XCtrl(141105)
自己开发的WPF控件库,只是初版,有扩展的Button,TextBox,Window.详细参见前几篇博文. WPF自定义控件(一)——Button:http://www.cnblogs.com/Qin ...
- Dev的WPF控件与VS2012不兼容问题
在只有vs2010环境下Dev的wpf可以在视图模式下显示,但是安装vs2012后无法打开界面的视图模式,报错:无法创建控件实例! 发现是Dev的wpf控件与.net framework 4.5不兼容 ...
- 解决 CefSharp WPF控件不能使用输入法输入中文的问题(代码已提交到 github)
首先,本文所有 代码已经提交到github,需要的可以直接从github获取:https://github.com/starts2000/CefSharp,希望可以帮助到有需要的朋友们. CEF 简介 ...
随机推荐
- 2016HUAS_ACM暑假集训4K - 基础DP
我不知道怎么用DP,不过DFS挺好用.DFS思路很明显,搜索.记录,如果刚好找到总价值的一半就说明搜索成功. 题目大意:每组6个数,分别表示价值1到6的物品个数.现在问你能不能根据价值均分. Samp ...
- 偶然翻出很久很久以前写的一款sqlmap UI,有点年头了
- mfc 调用Windows的API函数实现同步异步串口通信(源码)
在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信.串口通信方便易行,应用广泛. 一般情况下,工控机和各智能仪表通过RS485总线进行通信.RS485的通信方式是半 ...
- 关于python怎样编写登录接口
把今天的成果展示下,关于怎么用python编写登录接口, 要求是 1.输入用户名和密码 2.输错三次密码就锁定用户 3.认证成功后输出欢迎信息 账号文件内容如下: sanjiang sanjian ...
- 写自己的ASP.NET MVC框架(上)
http://www.cnblogs.com/fish-li/archive/2012/02/12/2348395.html 阅读目录 开始 ASP.NET程序的几种开发方式 介绍我的MVC框架 我的 ...
- Javascript定义类(class)的三种方法
将近20年前,Javascript诞生的时候,只是一种简单的网页脚本语言.如果你忘了填写用户名,它就跳出一个警告. 如今,它变得几乎无所不能,从前端到后端,有着各种匪夷所思的用途.程序员用它完成越来越 ...
- [转]require(),include(),require_once()和include_once()区别
require(),include(),require_once()和include_once()区别 面试中最容易提到的一个PHP的问题,我想和大家共勉一下: require()和include() ...
- 新增了个job
https://112.124.41.113/svn/wbhpro/wbh-adapter-job
- kafka单节点测试
======================命令====================== 启动zookeeper server bin/zookeeper-server-start.sh conf ...
- 使用图灵机器人API实现聊天机器人
使用图灵机器人的API需要先注册,获取key才行,这我就不说了,自己到http://www.tuling123.com/注册一个账号即可. 下面就是一个简单的python调用API实现聊天机器人的简易 ...