#1 如何使用控件不能改变大小?
答:有时我们需要创建不可改变大小的控件,像那种在运行时没有界面的控件(例:时间控件,SysInfo 等),想做到这种功能的话,请把以下代码加入到控件类的构造函数:
m_bAutoSize = TRUE;
SIZEL size = {24, 24};
AtlPixelToHiMetric(&size, &m_sizeExtent);
m_sizeNatural = m_sizeExtent;
#2.如何在运行时显示属性页?
答:在CComControlBase::DoVerbProperties() 中会自动调用ISpecifyPropertyPages::GetPages(),::OleCreatePropertyFrame() 且创建与显示OLE属性页,只要从你的控件中简单调用DoVerbProperties()显示,如何下代码:
HRESULT STDMETHODCALLTYPE PopMeUp(void)
{
return DoVerbProperties(NULL, ::GetActiveWindow() );
}
改变它们等到。以下代码演示在已存在的属性表中加入新的属性页:
HRESULT STDMETHODCALLTYPE GetPages(CAUUID *pPages)
{
if(SUCCEEDED(ISpecifyPropertyPages_GetPages(pPages,NULL))
{
pPages->cElems += 1;
pPages->pElems =
(GUID *)::CoTaskMemAlloc(pPages->cElems * sizeof(CLSID));
pPages->pElems[pPages->cElems - 1] = CLSID_General;
}
else
return E_FAIL;
}
#3 如何在ATL控件中使用Dialog资源
答:这儿是Microsoft的Mark Davis的回答:
1.使用ATL对象向导新增加对话框资源(例如:CMyDialog)。
2.编辑Dialog。
3.在你的控件类中加入内部成员变量(例如:CMyDialog m_dlg)。
4.在你的控件中映射消息WM_CREATE,在消息处理函数里创建Dialog(例如:m_dlg.Create(m_hWnd))
有时你的处理一些标准的Windows窗口的问题,像WM_SIZE等,根据你的情况来作相应的处理。
答:1.在接口中加入新的方法,并在接口文件(.idl)中改变dispid为DISPID_ABOUTBOX。
2.产生Dialog资源,并设置ID为IDD_ABOUTBOX。
3.在你的控件中加入以下代码:
class CAboutDlg : public CDialogImpl
{
public:
enum {IDD = IDD_ABOUTBOX};
BEGIN_MSG_MAP(CAboutDlg)
COMMAND_ID_HANDLER(IDOK, OnOK)
END_MSG_MAP()
HRESULT OnOK(WORD, WORD, HWND, BOOL&)
{
EndDialog(0);
return 0;
}
};
4.在你当才加的新方法中加入实现代码,例如:
CAboutDlg dlg;
dlg.DoModal();
#4 如何处理控件的滚动条
在你的Active X控件中加入滚动条需要在你的控件类的构造函数中把窗口m_bWindowOnly标志设置为TRUE,你也需要映射与处理消息WM_CREATE,并在处理函数中在窗口类型中加入WS_HSCROLL与WS_VSCROLL类型,如以下代码:
LRESULT OnCreate(UINT nMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
DWORD dwStyle = GetWindowLong(GWL_STYLE);
dwStyle |= WS_VSCROLL | WS_HSCROLL;
SetWindowLong(GWL_STYLE, dwStyle);
return
0L
;
}
映射与处理消息WM_HSCROLL与WM_VSCROLL,并并覆盖TranslateAccelerator()来加入键盘支持:
STDMETHOD(TranslateAccelerator)(MSG *pMsg)
{
switch(pMsg->wParam)
{
case VK_UP:
{
::SendMessage(m_hWnd, WM_VSCROLL,
SB_LINEUP, MAKELONG(0,m_hWnd));
break;
}
case VK_DOWN:
{
::SendMessage(m_hWnd, WM_VSCROLL,
SB_LINEDOWN, MAKELONG(0,m_hWnd));
break;
}
//以上面相似:
// case VK_LEFT:
// case VK_RIGHT:
// case VK_PRIOR:
// case VK_NEXT:
}
return S_FALSE;
#5 如何使我的控件对IE来说是安全的?
要使控件对IE来说是安全的话,则必需实现IObjectSafety接口,ATL提供了IObjectSafetyImpl包装类,以下代码是演示这个功能,加精是新增加的:
class ATL_NO_VTABLE CNoteCtl :
public CComObjectRootEx
,
...
// Derive from IObjectSafety
public IObjectSafety
{
...
BEGIN_COM_MAP(CNoteCtl)
COM_INTERFACE_ENTRY(INoteCtl)
COM_INTERFACE_ENTRY(IDispatch)
...
// Add it to our interface map
COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()
...
// IObjectSafety implementation
STDMETHODIMP GetInterfaceSafetyOptions( REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions )
{
ATLTRACE(_T("CNoteCtl::GetInterfaceSafetyOptions() "));
*pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER |
INTERFACESAFE_FOR_UNTRUSTED_DATA;
*pdwEnabledOptions = *pdwSupportedOptions;
return S_OK;
}
STDMETHODIMP SetInterfaceSafetyOptions(REFIID riid, DWORD dwOptionSetMask, DWORD dwEnabledOptions)
{
ATLTRACE(_T("CNoteCtl::SetInterfaceSafetyOptions "));
return S_OK;
}
...
};
#6 如何在控件中使用字体?
在ATL 2.x开始支持内置字体属性,首先,处理这个属性不像MFC那么简单;第二,你需要在你的控件的IDL文件中加入字体属性的声明(其实在VC6的ATL向导中支持这些属性了,你在向导中选上的话,向导自动会在idl文件中加入相关声明)
ATL并没有完全实现内置字体属性,它提供了内部成员变量指向IFontDisp接口,可是你仍然需要进行OLE字体的初始化,以下代码是演示:
在你的控件类的构造函数中加入以下代码:
CMyCtl(){ static FONTDESC _fontDesc = {sizeof(FONTDESC), OLESTR("MS Sans Serif"), FONTSIZE( 12 ), FW_BOLD, ANSI_CHARSET, FALSE, FALSE, FALSE}; OleCreateFontIndirect( &_fontDesc,IID_IFontDisp,(void **)&m_pFont );}
在你需要使用的地方使用以下代码,一般是在控件的OnDraw方法中,如下:
//取得字体CComQIPtr pFont( m_pFont );if ( pFont ){ HFONT hOldFont = 0; HFONT hFont; pFont->get_hFont( &hFont ); hOldFont = (HFONT) SelectObject( hdc, hFont ); // 使用它... if ( hOldFont ) SelectObject( hdc, hOldFont );}
一般在VC6的ATL向导中选择了Font字体属性的话,向导会在IDL文件中自动产生以下代码,没有的话手工加入以下声明(加粗部分):
#include
import "oaidl.idl";[
uuid(E
63A
22F
1-9BD3-11D0-A6D7-0000837E3100),
version(1.0),
helpstring("NoteIt 1.0 Type Library")]library NOTEITLib{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
// Interface is now inside the library block
[
object,
uuid(E
63A
2306-9BD3-11D0-A6D7-0000837E3100),
dual,
helpstring("INoteCtl Interface"),
pointer_default(unique)
]
interface INoteCtl : IDispatch
{
...
[propputref, id(DISPID_FONT)] HRESULT Font([in]IFontDisp* pFont); [propput, id(DISPID_FONT)] HRESULT Font([in]IFontDisp* pFont); [propget, id(DISPID_FONT)] HRESULT Font([out, retval]IFontDisp** ppFont); ...
};...}
#7 在COM/ATL中如何处理错误?
基于Windows的组件都有支持ISupportErrorInof接口,它允许将组件的错误信息返回给客户端,在VC5以后提供了本地的支持,如下:
_com_error( HRESULT hr, IErrorInfo* perrinfo = NULL ) throw( );
_com_error( const _com_error& that ) throw( );
这个函数检查IErrorInfo接口指针年是否存在,如果存在将抛出_com_error异常对象,你只要捕获这个_com_error异常对象就要以了,以下是示例代码:
STDMETHODIMP CMessageHandler::NewMessage(BSTR inMessage, BSTR inTo,
BSTR inFrom, BSTR inReply)
{
HRESULT hr = S_OK;
try
{
......
if(FAILED(hr))
_com_error(hr);
}
catch (_com_error& e) {
hr = Error((BSTR)e.Description(), e.HelpContext(), e.HelpFile(),e.GUID(), e.Error());
ATLTRACE("com error: %d - %s ", e.Error(), (const char*)e.Description());
}
return hr;
}
至于返回错误信息到客户端,请参阅我的《COM的错误处理》(也在文档中心)。
#8 如何自定义控件的Verbs
Microsoft标准文档定义了OLE对象从容器中响应消息,在一个对象容器或客户端链接到对象,通常是调动IOleObject::DoVerb()来响应用户或容器的消息,你可以通过双击对象或点击鼠标右键的上下文菜单来提供的选择来操作,容器对象装入上下文菜单是通过调用IOleObject::EnumVerbs().
典型的服务对象或控件是在IOleObject::EnumVerbs()的实现中调用OleRegEnumVerbs() ,ATL默认实现了这些功能,但你必须按照以下步骤:
1.首先添加菜单项到.RGS文件中,verb关键字存储在注册,如下:
HKEY_LOCAL_MACHINESOFTWAREClassesCLSIDVerb
1 =
2 =
3 =
以下是verb的格式:
Verb_Number =
Verb_Number是个枚举类型,Verb_String是有效的字符串,像"属性",Menu_Flag描述如何调用::AppendMenu,Verb_Flag是OLEVERBATTRIB枚举类型的值之一,如下:
OLEVERBATTRIB_NEVERDIRTIES = 1,
OLEVERBATTRIB_ONCONTAINERMENU = 2
所以请修改你的.RGS文件,如下:
NoRemove CLSID
{
ForceRemove {E
14A
8DEA
-8C
72-11D1
-891C
-00C
04FA3FB11} = s 'X Class'
{
ProgID = s 'X.X.1'
VersionIndependentProgID = s 'X.X'
ForceRemove 'Programmable'
...
'verb'
{
'1' = s '&Play,0,2'
'2' = s '&Transpose,0,2'
'3' = s '&Detune,0,2'
'4' = s '&Properties,0,2'
}
...
}
}
当容器检测到作过在对象上的verb操作将调用IOleObject::DoVerb(),在ATL,你需要覆盖IOleObjectImpl::DoVerb(),如下:
STDMETHOD(DoVerb)(LONG iVerb,
LPMSG lpmsg,
IOleClientSite *pActiveSite,
LONG lindex,
HWND hwndParent,
LPCRECT lprcPosRect)
{
if (iVerb == 1)//The verb number mentioned in the .rgs file
{
//Do whatever you want
}
else if(iVerb == 2)
{
}
return IOleObjectImpl
::DoVerb(iVerb, lpmsg,
pActiveSite, lindex, hwndParent, lprcPosRect);
}
#9 ATL里设置默认属性、默认方法?
对于属性只要在.IDL文件中将其ID设为0就行了。如:
[propget, id(0), helpstring("property test")] HRESULT test([out, retval] short *pVal);
同理对于方法也生效。
#10 如何使某个参数可选择?
HRESULT MyFunc([in]BSTR szName,[in, optional] VARIANT Param1, [out, optional] VARIANT Param2)
你在MyFunc程序中得检查Param1.vt是否为VT_EMPTY,如果是,用户未使用该参数。
#11 如何使用枚举类型?
在你的IDL文件中加入如下相似的代码:
typedef enum tagFontAlign
{
[helpstring("Left")]Left=0,
[helpstring("Center")]Center=1,
[helpstring("Right")]Right=2,
}FontAlign;
[propget, id(2), helpstring("对齐方式")] HRESULT Align([out, retval] FontAlign *pVal);
[propput, id(2), helpstring("对齐方式")] HRESULT Align([in] FontAlign newVal);
在接下来的接口定义中添加属性Align时,属性的数据类型就填FontAlign,其它操作照常。编译完以后,你就应该在VB Project中的Object Browser中看到有这么一个枚举类型。在控件属性中选中Align时,就会有个Combo Box让你选择FontAlign中的一个值。
#12 OLE_COLOR与COLORREF的有区别吗
OLE_COLOR与COLORREF之间是有一定区别的:OLE_COLOR和COLORREF都是DWORD类型,但对于COLORREF来说,它的最高一个字节永远是0x00。即如果是红色,对于COLORREF来说是0x000000FF。而OLE_COLOR的最高一个字节有两种情况:0x80(也就是10000000,最高位是1)或0x00(也就是00000000,最高位是0)。当OLE_COLOR的最高位是0时,它与COLORREF是相同的,最后三个字节代表RGB,可以相互赋值。例如红色用OLE_COLOR来表示同样是0x000000FF。但当OLE_COLOR的最高位是1时,它的中间两个字节一定都是0x00,最后一个字节表示的是系统颜色索引值。例如系统定义菜单的颜色索引值是4,所以用OLE_COLOR来表示就是0x80000004。在VB中,如果你选中一个FORM,在它的属性页中你可以看到它的BackColor属性,你点击下拉框,就可以选择是使用调色板色还是系统色,调色板色就是对应了OLE_COLOR的高位为0的情况,系统色对应的是OLE_COLOR高位为1的情况。你试一下就知道是怎么回事了,详细请参看:MSDN/Platform SDK/Component Services/COM/Controls and Property Pages/Functions/OleTranslateColor的Remarks。
OLE_COLOR与COLORREF的转达换处理:在MFC中可有OLEControl::TranslateColor()来转达换,在ATL或其它地方可调用API:OleTranslateColor()来进行从OLE_COLOR到COLORREF的转换。返过来可用如下方法:OLE_COLOR ocConverted = (OLE_COLOR)clrBack;
同样,VARIANT_BOOL和BOOL之间也有区别:BOOL为long,在BOOL中,TURE为1,FALSE为0。VAIRNAT_BOOL为short,在VARIANT_BOOL中,VARIANT_TRUE为-1(0xFFFF),VARIANT_FALSE为0(0x0000)。并且VARIANT_BOOL是和VB中的Boolean相同的,就像BSTR和String的关系一样。所以,在自动化组件及控件中应该使用VARIANT_BOOL。
#13 如何让我的控件输出数组?
参阅如下代码:
void CMyControl::GetArray( VARIANT FAR* pVariant )
{
//商业代码
int nCount = GetCount();
//定义维数
SAFEARRAYBOUND saBound[1];
//定义数组指针对性
SAFEARRAY* pSA;
saBound[0].cElements = nCount;
saBound[0].lLbound = 0;
//创建数组
pSA = SafeArrayCreate( VT_BSTR, 1, saBound );
for( long i = 0; i < nCount; i++ )
{
BSTR bstr;
//商业代码
bstr = GetItem( i ).AllocSysString();
//给数组赋值
SafeArrayPutElement( pSA, &i, bstr );
::SysFreeString( bstr );
}
// 初始化传递的参数
VariantInit( pVariant );
//设置返值的类型为数组
pVariant->vt = VT_ARRAY | VT_BSTR;
pVariant->parray = pSA;
}
Visual Basic 代码:
Dim t As Variant
Dim i as Integer
MyControl1.GetArray t
For i = 0 To MyControl1.Count - 1
ListBox.AddItem t( i )
Next i