我们创建dll形式的ATL项目EllipseCase 如下图:
我打算创建一个Ellipse控件,该控件拥有背景属性,可以改变背景颜色。
该控件绘制一个笛卡尔坐标系,用户可以通过右键菜单修改X、Y、Z轴的颜色。
好啦,现就这么多了,让我上路吧。
重要提醒:如果你要创建一个能够处理WM_CREATE消息的ActiveX控件,请注意:
必须------
a) 选择标准控件
b) 选中仅适用于有窗口的
c) 在构造函数中加上m_bWindowOnly=TRUE。
其中的原因在于符合控件派生自CComCompositeControl<>模板类,而标准控件派生自CComControl<>类。派生自CComCompositeControl类意味着该类可以包容其它的控件,但是同时具有了对话框的特征,且拥有了自己的对话框资源。所以如果想要添加初始化代码,应该处理WM_INITDIALOG消息。如果我们想在自己的控件中包含另一个ActiveX控件,我们应该创建复合控件,并用对话框承载想要包含的ActiveX控件。
三 解释向导的行为
我们先停下来,了解一下ActiveX控件的基础知识,并理解向导生成的代码。
看看CEllispe的父类
class ATL_NO_VTABLE CEllispe :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CEllispe, &CLSID_Ellispe>,
public IPersistStreamInitImpl<CEllispe>,
public ISpecifyPropertyPagesImpl<CEllispe>,
public IPersistStorageImpl<CEllispe>,
public CStockPropImpl<CEllispe, IEllispe>,
public IOleControlImpl<CEllispe>,
public IOleObjectImpl<CEllispe>,
public IOleInPlaceActiveObjectImpl<CEllispe>,
public IOleInPlaceObjectWindowlessImpl<CEllispe>,
public IViewObjectExImpl<CEllispe>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CEllispe>,
public IProvideClassInfo2Impl<&CLSID_Ellispe, &__uuidof(_IEllispeEvents), &LIBID_EllipseCaseLib>,
public CProxy_IEllispeEvents<CEllispe>,
public IQuickActivateImpl<CEllispe>,
public IDataObjectImpl<CEllispe>,
public CComCompositeControl<CEllispe>
ActiveX控件拥有界面,所以它只能存在于单线程套间中,所以应该用CComObjectRootEx<CComSingleThreadModel>;
ActiveX控件可以被创建,所以通过CComCoClass<CEllispe, &CLSID_Ellispe>来实现自己的类厂;
ActiveX控件至少要支持流方式保存,所以使用IPersistStreamInitImpl<CEllispe>;
VB和IE要求优先以属性包的方式保存对象,所以有的ActiveX控件也要支持
ActiveX控件支持结构化存储,这样可以被嵌入到OLE文档中,IPersistStorageImpl<CEllispe>;
ActiveX控件支持标准属性,使用CStockPropImpl<CEllispe, IEllispe>可以帮助节省时间;
ActiveX控件支持标准OLE控制功能,使用IOleControlImpl<CEllispe>;
ActiveX控件支持实地激活,使用public IOleObjectImpl<CEllispe>,public IOleInPlaceActiveObjectImpl<CEllispe>;
IOleInPlaceObjectWindowlessImpl<CEllispe>帮助实现快速高效的激活;
ActiveX控件支持按需呈现对象的视图,使用IViewObjectExImpl<CEllispe>;
ActiveX控件支持传递错误信息给调用方,而不是仅通过HRESULT,使用ISupportErrorInfo;
ActiveX控件支持事件,因此使用IConnectionPointContainerImpl<CEllispe>,IProvideClassInfo2Impl,CProxy_IEllispeEvents<CEllispe>,
ActiveX控件是复合控件,所以使用了CComCompositeControl<CEllispe>
由于我们在向导中选择了两个标准属性:BackGroundColor和BackGroundStyle,所以我们来看看idl文件中有些什么:
interface IEllispe : IDispatch{
[propput, bindable, requestedit, id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget, bindable, requestedit, id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
[propput, bindable, requestedit, id(DISPID_BACKSTYLE)]
HRESULT BackStyle([in]long style);
[propget, bindable, requestedit, id(DISPID_BACKSTYLE)]
HRESULT BackStyle([out,retval]long* pstyle);
};
IEllispe从IDispatch接口派生,这是因为控件容器要使用控件的IDispatch方法访问控件的属性和方法。
注意,所有的标准属性都有bindable和requestedit两个属性,这是因为标准属性的put方法在改变属性之前之后都要向包容器激发变化通知
四 标准属性
背景颜色属于标准属性,CStockPropImpl类能够帮助我们。
template < class T, class InterfaceName, const IID* piid = &_ATL_IIDOF(InterfaceName), const GUID* plibid = &CAtlModule::m_libid, WORD wMajor = 1,
WORD wMinor = 0, class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE CStockPropImpl : public IDispatchImpl< InterfaceName, piid, plibid, wMajor, wMinor, tihclass >
{
public:
// Font
HRESULT STDMETHODCALLTYPE put_Font(IFontDisp* pFont)
{
__if_exists(T::m_pFont)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::put_Font/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_FONT) == S_FALSE)
return S_FALSE;
pT->m_pFont = 0;
if (pFont)
{
CComQIPtr<IFont, &__uuidof(IFont)> p(pFont);
if (p)
{
CComPtr<IFont> pFont;
p->Clone(&pFont);
if (pFont)
pFont->QueryInterface(__uuidof(IFontDisp), (void**) &pT->m_pFont);
}
}
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_FONT);
__if_exists(T::OnFontChanged)
{
pT->OnFontChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE putref_Font(IFontDisp* pFont)
{
__if_exists(T::m_pFont)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::putref_Font/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_FONT) == S_FALSE)
return S_FALSE;
pT->m_pFont = pFont;
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_FONT);
__if_exists(T::OnFontChanged)
{
pT->OnFontChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE get_Font(IFontDisp** ppFont)
{
__if_exists(T::m_pFont)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::get_Font/n"));
ATLASSERT(ppFont != NULL);
if (ppFont == NULL)
return E_POINTER;
T* pT = (T*) this;
*ppFont = pT->m_pFont;
if (*ppFont != NULL)
(*ppFont)->AddRef();
}
return S_OK;
}
// Picture
HRESULT STDMETHODCALLTYPE put_Picture(IPictureDisp* pPicture)
{
__if_exists(T::m_pPicture)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::put_Picture/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_PICTURE) == S_FALSE)
return S_FALSE;
pT->m_pPicture = 0;
if (pPicture)
{
CComQIPtr<IPersistStream, &__uuidof(IPersistStream)> p(pPicture);
if (p)
{
ULARGE_INTEGER l;
p->GetSizeMax(&l);
HGLOBAL hGlob = GlobalAlloc(GHND, l.LowPart);
if (hGlob)
{
CComPtr<IStream> spStream;
CreateStreamOnHGlobal(hGlob, TRUE, &spStream);
if (spStream)
{
if (SUCCEEDED(p->Save(spStream, FALSE)))
{
LARGE_INTEGER l;
l.QuadPart = 0;
spStream->Seek(l, STREAM_SEEK_SET, NULL);
OleLoadPicture(spStream, l.LowPart, FALSE, __uuidof(IPictureDisp), (void**)&pT->m_pPicture);
}
spStream.Release();
}
GlobalFree(hGlob);
}
}
}
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_PICTURE);
__if_exists(T::OnPictureChanged)
{
pT->OnPictureChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE putref_Picture(IPictureDisp* pPicture)
{
__if_exists(T::m_pPicture)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::putref_Picture/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_PICTURE) == S_FALSE)
return S_FALSE;
pT->m_pPicture = pPicture;
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_PICTURE);
__if_exists(T::OnPictureChanged)
{
pT->OnPictureChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE get_Picture(IPictureDisp** ppPicture)
{
__if_exists(T::m_pPicture)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::get_Picture/n"));
ATLASSERT(ppPicture != NULL);
if (ppPicture == NULL)
return E_POINTER;
T* pT = (T*) this;
*ppPicture = pT->m_pPicture;
if (*ppPicture != NULL)
(*ppPicture)->AddRef();
}
return S_OK;
}
// MouseIcon
HRESULT STDMETHODCALLTYPE put_MouseIcon(IPictureDisp* pPicture)
{
__if_exists(T::m_pMouseIcon)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::put_MouseIcon/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_MOUSEICON) == S_FALSE)
return S_FALSE;
pT->m_pMouseIcon = 0;
if (pPicture)
{
CComQIPtr<IPersistStream, &__uuidof(IPersistStream)> p(pPicture);
if (p)
{
ULARGE_INTEGER l;
p->GetSizeMax(&l);
HGLOBAL hGlob = GlobalAlloc(GHND, l.LowPart);
if (hGlob)
{
CComPtr<IStream> spStream;
CreateStreamOnHGlobal(hGlob, TRUE, &spStream);
if (spStream)
{
if (SUCCEEDED(p->Save(spStream, FALSE)))
{
LARGE_INTEGER l;
l.QuadPart = 0;
spStream->Seek(l, STREAM_SEEK_SET, NULL);
OleLoadPicture(spStream, l.LowPart, FALSE, __uuidof(IPictureDisp), (void**)&pT->m_pMouseIcon);
}
spStream.Release();
}
GlobalFree(hGlob);
}
}
}
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_MOUSEICON);
__if_exists(T::OnMouseIconChanged)
{
pT->OnMouseIconChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE putref_MouseIcon(IPictureDisp* pPicture)
{
__if_exists(T::m_pMouseIcon)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::putref_MouseIcon/n"));
T* pT = (T*) this;
if (pT->m_nFreezeEvents == 0 && pT->FireOnRequestEdit(DISPID_MOUSEICON) == S_FALSE)
return S_FALSE;
pT->m_pMouseIcon = pPicture;
pT->m_bRequiresSave = TRUE;
if (pT->m_nFreezeEvents == 0)
pT->FireOnChanged(DISPID_MOUSEICON);
__if_exists(T::OnMouseIconChanged)
{
pT->OnMouseIconChanged();
}
pT->FireViewChange();
pT->SendOnDataChange(NULL);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE get_MouseIcon(IPictureDisp** ppPicture)
{
__if_exists(T::m_pMouseIcon)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::get_MouseIcon/n"));
ATLASSERT(ppPicture != NULL);
if (ppPicture == NULL)
return E_POINTER;
T* pT = (T*) this;
*ppPicture = pT->m_pMouseIcon;
if (*ppPicture != NULL)
(*ppPicture)->AddRef();
}
return S_OK;
}
IMPLEMENT_STOCKPROP(OLE_COLOR, BackColor, clrBackColor, DISPID_BACKCOLOR)
IMPLEMENT_STOCKPROP(OLE_COLOR, BorderColor, clrBorderColor, DISPID_BORDERCOLOR)
IMPLEMENT_STOCKPROP(OLE_COLOR, FillColor, clrFillColor, DISPID_FILLCOLOR)
IMPLEMENT_STOCKPROP(OLE_COLOR, ForeColor, clrForeColor, DISPID_FORECOLOR)
IMPLEMENT_BOOL_STOCKPROP(AutoSize, bAutoSize, DISPID_AUTOSIZE)
IMPLEMENT_BOOL_STOCKPROP(Valid, bValid, DISPID_VALID)
IMPLEMENT_BOOL_STOCKPROP(Enabled, bEnabled, DISPID_ENABLED)
IMPLEMENT_BOOL_STOCKPROP(TabStop, bTabStop, DISPID_TABSTOP)
IMPLEMENT_BOOL_STOCKPROP(BorderVisible, bBorderVisible, DISPID_BORDERVISIBLE)
IMPLEMENT_BSTR_STOCKPROP(Text, bstrText, DISPID_TEXT)
IMPLEMENT_BSTR_STOCKPROP(Caption, bstrCaption, DISPID_CAPTION)
HRESULT STDMETHODCALLTYPE put_Window(LONG_PTR hWnd)
{
return put_HWND(hWnd);
}
HRESULT STDMETHODCALLTYPE get_Window(LONG_PTR* phWnd)
{
return get_HWND(phWnd);
}
HRESULT STDMETHODCALLTYPE put_HWND(LONG_PTR /*hWnd*/)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::put_HWND/n"));
return E_FAIL;
}
HRESULT STDMETHODCALLTYPE get_HWND(LONG_PTR* phWnd)
{
__if_exists(T::m_hWnd)
{
ATLTRACE(atlTraceControls,2,_T("CStockPropImpl::get_HWND/n"));
ATLASSERT(phWnd != NULL);
if (phWnd == NULL)
return E_POINTER;
T* pT = (T*) this;
*phWnd = reinterpret_cast<LONG_PTR>(pT->m_hWnd);
}
return S_OK;
}
IMPLEMENT_STOCKPROP(LONG, BackStyle, nBackStyle, DISPID_BACKSTYLE)
IMPLEMENT_STOCKPROP(LONG, BorderStyle, nBorderStyle, DISPID_BORDERSTYLE)
IMPLEMENT_STOCKPROP(LONG, BorderWidth, nBorderWidth, DISPID_BORDERWIDTH)
IMPLEMENT_STOCKPROP(LONG, DrawMode, nDrawMode, DISPID_DRAWMODE)
IMPLEMENT_STOCKPROP(LONG, DrawStyle, nDrawStyle, DISPID_DRAWSTYLE)
IMPLEMENT_STOCKPROP(LONG, DrawWidth, nDrawWidth, DISPID_DRAWWIDTH)
IMPLEMENT_STOCKPROP(LONG, FillStyle, nFillStyle, DISPID_FILLSTYLE)
IMPLEMENT_STOCKPROP(SHORT, Appearance, nAppearance, DISPID_APPEARANCE)
IMPLEMENT_STOCKPROP(LONG, MousePointer, nMousePointer, DISPID_MOUSEPOINTER)
IMPLEMENT_STOCKPROP(LONG, ReadyState, nReadyState, DISPID_READYSTATE)
};
我们先来看看模板参数
template < class T, class InterfaceName,
const IID* piid = &_ATL_IIDOF(InterfaceName), const GUID* plibid = &CAtlModule::m_libid, WORD wMajor = 1,
WORD wMinor = 0, class tihclass = CComTypeInfoHolder>
T是控件类的名字;
InterfaceName是接口的名字,该接口拥有标准属性和方法;
piid是接口的GUID的指针;
plibid是类型库GUID的指针;
wMajor是主版本号;
wMinor是次版本号;
实际运用中,只需要指定前面两个参数,其余皆可以采用默认值。
CStockPropImpl类为我们实现了所有标准属性的访问方法,但是我们只需要实现两个属性,因此我们只为我们需要的标准属性提供数据成员,成员变量的名称要与CStockPropImpl类使用的名字一致。如若不信,我们看看下面的宏
IMPLEMENT_STOCKPROP(OLE_COLOR, BackColor, clrBackColor, DISPID_BACKCOLOR)
该宏的定义如下:
#define IMPLEMENT_STOCKPROP(type, fname, pname, dispid) /
HRESULT STDMETHODCALLTYPE put_##fname(type pname) /
{ /
__if_exists(T::m_##pname) /
。。。。。。。。
所以CStockPropImpl类假定控件类T拥有m_clrBackColor成员变量
这很麻烦,但是不必烦恼,因为向导已经为我们定义了合适的成员变量。但是我们还是需要知道这些,万一我们不小心删除了代码,我们知道该如何恢复。
注意IMPLEMENT_STOCKPROP宏只能用于CStockPropImpl类
CEllispe类里边的属性声明为:
OLE_COLOR m_clrBackColor;
void OnBackColorChanged()
{
ATLTRACE(_T("OnBackColorChanged/n"));
}
OnBackColorChanged方法会被CStockPropImpl类在属性值修改的时候调用,如果我们需要对属性的修改进行即时反映,可以在该函数里面填写代码
其他属性也是如此。
现在我们的控件Ellipse已经拥有了背景颜色、背景状态两种标准属性,在Activex测试容器中右键点击控件的边缘,弹出菜单,选择属性,可以看到
我们可以设置背景颜色了。试着用用其他的功能,我们可以设置控件的环境属性,还可以把控件保存到流和结构化存储文件中。但是目前不支持属性包方式保存。
我撰写本文的时候,正是Windows2000已经普及的时候,出于效率的考虑和开发程序减少从ANSI和UNICODE转换的额外负担,我在本文示例程序中全部使用Unicode风格编码,工程中设置如下:
到这里忍不住提高点难度,用GDI绘制笛卡尔坐标系很简单,用OpenGL绘制三维笛卡尔坐标系才棒。如果读者不想涉及太多的OpenGL技巧,可以掠过这一节,用GDI绘图。
在stdafx.h文件中增加以下代码:
#include <gl/gl.h>
#include <gl/glu.h>
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glaux.lib")
#pragma warning(disable : 4100)
#include <gl/glaux.h>
#pragma comment(lib, "winmm.lib")
为我们的控件添加成员函数
//设置屏幕DC的像素格式,OpenGL绘图环境初始化
BOOL CEllispe::bSetupPixelFormat(HDC hdc)
{
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
1, // version number
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGL
PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type
24, // 24-bit color depth
0, 0, 0, 0, 0, 0, // color bits ignored
0, // no alpha buffer
0, // shift bit ignored
0, // no accumulation buffer
0, 0, 0, 0, // accum bits ignored
32, // 32-bit z-buffer
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
int pixelformat;
if ( (pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0 )
{