为了方便的移植及重用自己编写的控件,这时候我们就要用到ActiveX控件技术来封装自己的控件类。
封装一个ActiveX控件需要考虑:
1、提供用户设置的属性。
2、提供用户使用的接口函数。
3、控件事件的通知。
4、控件响应用户的操作。
5、控件的绘制。
6、异常处理。
添加用户设置属性方法:
打开类视图展开XXlib选择控件接口右键菜单添加-〉添加属性打开属性添加向导,设置属性后完成。
在idl文件自动生成代码:
- library ScribbleLib
- {
- importlib(STDOLE_TLB);
- // Primary dispatch interface for CScribbleCtrl
- [ uuid(9B217CBB-3D90-444A-898B-271B9EF1B36A),
- helpstring("Dispatch interface for Scribble Control")]
- dispinterface _DScribble
- {
- properties:
- [id(1) , helpstring("画笔颜色")] OLE_COLOR PenColor;
- methods:
- };
- #include <olectl.h>
- #include <idispids.h>
- typedef enum { Blank, SmallGrid, MediumGrid, LargeGrid } GridType;
- [
- uuid(0A33EFF3-46C9-4B38-B2DC-05C42C99D093), version(1.0),
- helpfile("Scribble.hlp"),
- helpstring("Scribble ActiveX Control module"),
- control
- ]
- ............
属性这样声明:
- properties:
- [id(1) , helpstring("画笔颜色")] OLE_COLOR PenColor;
- [id(2) , helpstring("显示网格")] GridType ShowGird;
- ...........
在控件头文件中生成代码
- class CScribbleCtrl : public COleControl
- {
- DECLARE_DYNCREATE(CScribbleCtrl)
- // Constructor
- public:
- CScribbleCtrl();
- // Implementation
- protected:
- ~CScribbleCtrl();
- .......................
- // Dispatch and event IDs
- public:
- enum {
- dispidShowGird = 2,
- dispidPenColor = 1
- };
- protected:
- // 根据向导的选择如果选择get/put则生成get/put函数,选择变量形式则为当前代码
- OLE_COLOR m_PenColor;
- void OnPenColorChanged(void);
- USHORT m_ShowGird;
- void OnShowGirdChanged(void);
- .................................
- };
在控件cpp文件中生成代码
- // Dispatch map
- BEGIN_DISPATCH_MAP(CScribbleCtrl, COleControl)
- DISP_PROPERTY_NOTIFY_ID(CScribbleCtrl, "ShowGird", dispidShowGird, m_ShowGird, OnShowGirdChanged, VT_UI2)
- DISP_PROPERTY_NOTIFY_ID(CScribbleCtrl, "PenColor", dispidPenColor, m_PenColor, OnPenColorChanged, VT_COLOR)
- DISP_PROPERTY_NOTIFY_ID(CScribbleCtrl, "DrawPen", dispiddrawPen, m_DrawPen, OndrawPenChanged, VT_UI2)
- END_DISPATCH_MAP()
- // CScribbleCtrl::DoPropExchange - Persistence support
- void CScribbleCtrl::DoPropExchange(CPropExchange* pPX)
- {
- ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
- COleControl::DoPropExchange(pPX);
- // 绑定控件属性默认值,显示在控件属性页中
- PX_UShort(pPX, _T("ShowGird"), m_ShowGird, Blank);
- PX_Color(pPX, _T("PenColor"), m_PenColor, RGB(0, 0, 0));
- if (pPX->IsLoading())
- {
- // 加载用户在控件属性页中设置的控件属性值
- }
- }
- // CScribbleCtrl message handlers
- void CScribbleCtrl::OnShowGirdChanged(void)
- {
- Refresh(); // 刷新控件
- }
- void CScribbleCtrl::OnPenColorChanged(void)
- {
- }
添加用户使用的接口函数:
与添加属性的方法一样,在添加菜单选择添加方法。
- // idl 代码段
- dispinterface _DScribble
- {
- properties:
- ......................
- methods:
- [id(3), helpstring("method GetScribbleBmp")] ULONG GetScribbleHBITMAP(void);
- .......................
- //头文件代码段
- .......................
- // Dispatch and event IDs
- public:
- enum {
- dispidGetScribbleBmp = 4L,
- dispidPenColor = 2,
- dispidShowGird = 1
- };
- ........................
- protected:
- ULONG GetScribbleHBITMAP(void);
- .......................
- //cpp文件代码段
- ........................
- // Dispatch map
- BEGIN_DISPATCH_MAP(CScribbleCtrl, COleControl)
- .....................
- DISP_FUNCTION_ID(CScribbleCtrl, "GetScribbleHBITMAP", dispidGetScribbleBmp, GetScribbleHBITMAP, VT_UI4, VTS_NONE)
- END_DISPATCH_MAP()
- ULONG CScribbleCtrl::GetScribbleHBITMAP(void)
- {
- .............
- }
- .............
添加控件事件的通知
打开类视图展开XXlib选择控件事件接口(一般为_DXXXXEvents)右键菜单添加-〉添加方法打开方法添加向导,设置后完成。
COleControl 已经封装了一些内置的事件
事件映射宏如下:
- EVENT_STOCK_CLICK()
- EVENT_STOCK_DBLCLICK()
- EVENT_STOCK_KEYDOWN()
- EVENT_STOCK_KEYPRESS()
- EVENT_STOCK_KEYUP()
- EVENT_STOCK_MOUSEDOWN()
- EVENT_STOCK_MOUSEMOVE()
- EVENT_STOCK_MOUSEUP()
- EVENT_STOCK_ERROREVENT()
- EVENT_STOCK_READYSTATECHANGE()
函数原型
- // Firing functions for stock events
- void FireKeyDown(USHORT* pnChar, short nShiftState);
- void FireKeyUp(USHORT* pnChar, short nShiftState);
- void FireKeyPress(USHORT* pnChar);
- void FireMouseDown(short nButton, short nShiftState,
- OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
- void FireMouseUp(short nButton, short nShiftState,
- OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
- void FireMouseMove(short nButton, short nShiftState,
- OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
- void FireClick();
- void FireDblClick();
- void FireError(SCODE scode, LPCTSTR lpszDescription, UINT nHelpID = 0);
- void FireReadyStateChange();
添加自定义的事件通知使用向导后需要编写一些代码,看代码段
- //idl代码段
- // Event dispatch interface for CScribbleCtrl
- [ uuid(DA9841E5-98B9-4674-939E-8EC6BEE35273),
- helpstring("Event interface for Scribble Control") ]
- dispinterface _DScribbleEvents
- {
- properties:
- // Event interface has no properties
- methods:
- [id(1), helpstring("method ScribbleChange")] void ScribbleChange(VARIANT_BOOL bIsEmpty);
- };
- //头文件
- //同样会自动生成一个id
- // Dispatch and event IDs
- public:
- enum {
- dispidScribbleChange = 1L,
- ..............
- }
- ........
- void ScribbleChange(VARIANT_BOOL bIsEmpty);
- ........
- //cpp
- // 会自动在函数影射中增加这一条,注释掉没有用
- // Dispatch map
- BEGIN_DISPATCH_MAP(CScribbleCtrl, COleControl)
- //DISP_FUNCTION_ID(CScribbleCtrl, "ScribbleChange", dispidScribbleChange, ScribbleChange, VT_EMPTY, VTS_BOOL)
- END_DISPATCH_MAP()
- //需要手工在事件映射内添加代码,要不然使用控件的地方得不到事件通知
- // Event map
- BEGIN_EVENT_MAP(CScribbleCtrl, COleControl)
- EVENT_CUSTOM_ID("ScribbleChange", dispidScribbleChange, ScribbleChange, VTS_BOOL)
- END_EVENT_MAP()
- // 时间函数实现
- void CScribbleCtrl::ScribbleChange(VARIANT_BOOL bIsEmpty)
- {
- FireEvent(dispidScribbleChange, EVENT_PARAM(VTS_BOOL), bIsEmpty);
- }
- //如果控件触发了这个事件需要对控件的容器进行事件通知可以直接调用
- {
- .........
- ScribbleChange(FALSE);
- }
控件响应用户的操作:
一般来说需要响应的是
WM_MOUSEMOVE;WM_LBUTTONDOWN;WM_LBUTTONUP;WM_MOUSELEAVE;WM_RBUTTONDOWN
这里只补充一下关于WM_MOUSELEAVE响应的方法;
首先在MOUSEMOVE函数中使用如下方法让系统通知WM_MOUSELEAVE
- if (!m_bSetTrack)
- {
- TRACKMOUSEEVENT trackm;
- ZeroMemory(&trackm, sizeof TRACKMOUSEEVENT);
- trackm.cbSize = sizeof TRACKMOUSEEVENT;
- trackm.dwFlags = TME_HOVER | TME_LEAVE;
- trackm.hwndTrack = m_hWnd;
- m_bSetTrack = TrackMouseEvent(&trackm);
- }
然后在响应WM_MOUSELEAVE消息后把m_bSetTrack变量重置一下即可
控件的绘制:
void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
rcBounds为控件绘制大小其实就是控件客户区域,rcInvalid为更新绘制区域
一般可以使用包装的双缓冲类进行绘制
包装类如下
- #ifndef _MEMDC_H_
- #define _MEMDC_H_
- // WTL CMemeryDC for MFC use
- namespace MFC
- {
- class CMemDC : public CDC
- {
- public:
- // Data members
- CDC * m_pDCOriginal;
- RECT m_rcPaint;
- CBitmap m_bmp;
- CBitmap* m_pBmpOld;
- // Constructor/destructor
- CMemDC(CDC *pDC, const RECT& rcPaint) : m_pDCOriginal(pDC), m_pBmpOld(NULL)
- {
- m_rcPaint = rcPaint;
- CreateCompatibleDC(m_pDCOriginal);
- ASSERT(m_hDC != NULL);
- m_bmp .CreateCompatibleBitmap(m_pDCOriginal, m_rcPaint.right - m_rcPaint.left, m_rcPaint.bottom - m_rcPaint.top);
- ASSERT(m_bmp.m_hObject != NULL);
- m_pBmpOld = SelectObject(&m_bmp);
- SetViewportOrg(-m_rcPaint.left, -m_rcPaint.top);
- }
- ~CMemDC()
- {
- m_pDCOriginal->BitBlt(m_rcPaint.left, m_rcPaint.top, m_rcPaint.right - m_rcPaint.left, m_rcPaint.bottom - m_rcPaint.top, this, m_rcPaint.left, m_rcPaint.top, SRCCOPY);
- SelectObject(m_pBmpOld);
- }
- };
- }
- #endif
调用例子
- void CScribbleCtrl::OnDraw(
- CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
- {
- if (!pdc)
- return;
- // TODO: Replace the following code with your own drawing code.
- MFC::CMemDC dcMem(pdc, rcInvalid);
- dcMem.FillSolidRect(rcBounds, RGB(255,255,255));
- // 绘制控件
- .......
- }
异常处理:
控件异常处理一般来说我基本上没有考虑,但是这个肯定是需要的