ActiveX控件的MFC设计之旅-第11步 .

OLE自动化集合类

在VB中有下面的这种语法
Set docs = Application.Documents
For Each doc in docs
 MsgBox doc.Title
Next
在这里docs就是一个集合类,VB中还专门提供了一个Collection对象,可以组建你自己的集合类
当然,集合类还有许多特征,一个集合类都会有下面的几个方法属性
Count只读属性
Item方法,可以带1个或多个参数,返回集合内的对象,一般设置为集合类的缺省属性方法
_NewEnum只读属性,这个属性用户不可见,主要就用于上面的for each语法
一般来说,可能还会有Add和Remove等方法

下面,我们就将一步步来实现这个一个集合类Tcoll

前面的步骤都不说了

1.新建一个派生自CCmdTarget,选上Createable by type ID选项的CTcItem类,添加BSTR Key和BSTR Text属性,作为我们的集合中的元素,其中属性Key为关键字,用于利用关键字来选择集合元素,属性Text即为元素的文本,用于在控件在显示,这是个 很简单的类
另外,我们为CTcItem添加一个控件的指针CTcollCtrl* m_pCtrl;作用是为了在Text属性改变时,能够刷新控件
class CTcollCtrl;
class CTcItem : public CCmdTarget
{
...
public:
CTcollCtrl* m_pCtrl;
CString m_key;
CString m_text;
...
}
m_key和m_text用向导生成时,是protected的,但是,这里将它们移到了public下,因为后面会用到。

CTcItem::CTcItem()
{
EnableAutomation();

// To keep the application running as long as an OLE automation
// object is active,the constructor calls AfxOleLockApp.
m_pCtrl = NULL;
AfxOleLockApp();
}

void CTcItem::OnTextChanged()
{
// Todo: Add notification handler code
if(m_pCtrl)
m_pCtrl->InvalidateControl();
}

void CTcItem::OnKeyChanged()
{
// Todo: Add notification handler code

}

2.新建一个派生自CCmdTarget,选上Automation选项的CColl类,作为我们的集合类,添加long Count只读属性,LPdisPATCH Item(VARIANT Index)方法,LPUNKNowN NewEnum属性,LPdisPATCH Add(VARIATN strKey,VARIANT strText)方法和void Remove(VARIANT Index)方法

对于Item方法,需要设置为缺省属性,可是似乎向导对于方法,并不提供Default Property这一选项,所以我们得自己加上了,下面的disP_DEFVALUE宏就是用于设置缺省属性的,另外得将Item的dispID改为0
NewEnum是很特殊的,因为,它的名字并不重要,你可以改成其它的阿狗阿猫,但是它的dispID必须为disPID_NEWENUM=-4
所以上面的这些更改都在两个地方完成,一是在集合类的.cpp中,另一个是在.odl中,如下:

Coll.cpp:
BEGIN_disPATCH_MAP(CColl,CCmdTarget)
//{{AFX_disPATCH_MAP(CColl)
disP_PROPERTY_EX(CColl,"Count",GetCount,SetNotSupported,VT_I4)
disP_FUNCTION(CColl,"Item",Item,VT_disPATCH,VTS_VARIANT)
disP_DEFVALUE(CColl,"Item")
disP_PROPERTY_EX_ID(CColl,"NewEnum",disPID_NEWENUM,_NewEnum,VT_UNKNowN)
disP_FUNCTION(CColl,"Remove",Remove,VT_EMPTY,VTS_VARIANT)
disP_FUNCTION(CColl,"Add",Add,VTS_VARIANT VTS_VARIANT)
//}}AFX_disPATCH_MAP
END_disPATCH_MAP()

Tcoll.odl:
// Primary dispatch interface for CColl
#define disPID_NEWENUM -4

[ uuid(0D58DCBE-EBDF-4746-80FD-8F389CF6BB0E) ]
dispinterface IColl
{
properties:
[id(disPID_NEWENUM)] IUnkNown* _NewEnum;
// NOTE - ClassWizard will maintain property information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_PROP(CColl)
[id(1)] long Count;
//}}AFX_ODL_PROP

methods:
[id(0)] ITcItem* Item(VARIANT Index);
// NOTE - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CColl)
[id(3)] void Remove(VARIANT Index);
[id(4)] ITcItem* Add([optional]VARIANT strKey,[optional]VARIANT strText);
//}}AFX_ODL_METHOD

};

注:
a.这里将_NewEnum和Item都提到了向导生成代码的外面,是为了避免新增加属性方法时向导乱改dispID,而且,很显然,向导也不认识disPID_NEWENUM之类的dispID。
b.这里的Item和Add的返回值都从Idispatch*改成了ITcItem*,这是为了能在VB设计代码显示出接口的属性方法
c.这里将Add方法的两个参数均加上了optional选项,是为了可以使用缺省参数,就象ListView控件的ListItems对象的Add方法 ListView1.ListItems.Add,"Hello"一样的使用,要使用缺省参数的话,就一定要用VARIANT,所以这里用了VARIANT,而不是BSTR
d.这里的Item和Remove中的索引都是用的VARIANT,因为我们有可能会用关键字来选择集合中的元素。
e.将元素添加到集合中的Add方法是由你自己定的,这里是由函数生成一个元素,你也可以直接添加一个Idispatch(ITcItem)元素到集合 中(在用CCmdTarget派生时,需要设置createable by type ID选项),下面的注释掉的代码中有这种方法的实现,仅供参考。

根据上面的描述,不难写出下面的实现代码

long CColl::GetCount()
{
// Todo: Add your property handler here
return m_olItems.GetCount();
return 0;
}

LPdisPATCH CColl::Item(const VARIANT FAR& Index)
{
// Todo: Add your dispatch handler code here
if(Index.vt == VT_I4){
POSITION pos = m_olItems.Findindex(Index.lVal);
if(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetAt(pos);
if(pitem != NULL){
return pitem->GetIdispatch(TRUE);
}
}
return NULL;
}
else if(Index.vt == VT_BSTR){
POSITION pos = m_olItems.GetHeadPosition();
while(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos);
if(pitem->m_key == Index.bstrVal){
return pitem->GetIdispatch(TRUE);
}
}
return NULL;
}
return NULL;
}

LPUNKNowN CColl::_NewEnum()
{
// Todo: Add your property handler here
m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition();
LPUNKNowN pUnk = GetInterface(&IID_IEnumVARIANT);
if(pUnk) pUnk->AddRef();
return pUnk;
return NULL;
}

LPdisPATCH CColl::Add(const VARIANT FAR& strKey,const VARIANT FAR& strText)
{
// Todo: Add your dispatch handler code here
CTcItem* pitem = new CTcItem;
pitem->m_pCtrl = m_pCtrl;

if(strKey.vt == VT_ERROR && strKey.scode == disP_E_ParaMNOTFOUND){
pitem->m_key = "";
}
else if(strKey.vt != VT_BSTR){
COleVariant v;
v.ChangeType(VT_BSTR,(LPVARIANT)&strKey);
pitem->m_key = v.bstrVal;
}
else{
pitem->m_key = strKey.bstrVal;
}

if(strText.vt == VT_ERROR && strText.scode == disP_E_ParaMNOTFOUND){
pitem->m_text = "";
}
else if(strText.vt != VT_BSTR){
COleVariant v;
v.ChangeType(VT_BSTR,(LPVARIANT)&strText);
pitem->m_text = v.bstrVal;
}
else{
pitem->m_text = strText.bstrVal;
}
m_olItems.AddTail(pitem);

if(m_pCtrl) m_pCtrl->InvalidateControl();

return pitem->GetIdispatch(TRUE);
}

void CColl::Remove(const VARIANT FAR& Index)
{
// Todo: Add your dispatch handler code here
if(Index.vt == VT_I4){
POSITION pos = m_olItems.Findindex(Index.lVal);
delete m_olItems.GetAt(pos);
m_olItems.RemoveAt(pos);
}
else if(Index.vt == VT_BSTR){
POSITION pos = m_olItems.GetHeadPosition();
POSITION poscur = pos;
while(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos);
if(pitem->m_key = Index.bstrVal){
delete pitem;
m_olItems.RemoveAt(poscur);
}
poscur = pos;
}
}

if(m_pCtrl) m_pCtrl->InvalidateControl();

}

/*
LPdisPATCH CColl::Add(LPdisPATCH tcItem)
{
// Todo: Add your dispatch handler code here
CTcItem* pitem = (CTcItem*)CCmdTarget::FromIdispatch(tcItem);
m_olItems.AddTail(pitem);

if(m_pCtrl) m_pCtrl->InvalidateControl();

return tcItem;
return NULL;
}
*/

注:
a.请看这一句 if(strKey.vt == VT_ERROR && strKey.scode == disP_E_ParaMNOTFOUND),这就是判断是否有设置缺省参数的,如果为TRUE,就表示未设置参数,你可以做相应的处理了,这里只是简单 的设置了缺省的参数。
b.这里在参数不是字符串时,都转换成字符串,也就是说,你可以直接写成.Add 1,1。
c..这里和元素类中一样,也定义了CTcollCtrl* m_pCtrl,用途也一样,也是用来调用InvalidateControl刷新控件的。
d.这里有m_olItems,它的定义是CObList m_olItems;用来保存所有元素指针,应该不陌生吧,在集合类的析构函数中,需要删除所有的元素
CColl::~CColl()
{
POSITION pos = m_olItems.GetHeadPosition();
while(pos){
delete m_olItems.GetNext(pos);
}
}
e.在
LPUNKNowN CColl::_NewEnum()
{
// Todo: Add your property handler here
m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition();
LPUNKNowN pUnk = GetInterface(&IID_IEnumVARIANT);
if(pUnk) pUnk->AddRef();
return pUnk;
return NULL;
}
中,用到了m_xEnumVARIANT和IID_IEnumVARIANT,这就是接下来要做的,添加IEnumVARIANT接口

3.要能在VB中的for each语法中使用,一个很重要的部分就是要实现一个IEnumVARIANT接口,在for each时,VB会自动调用集合的_NewEnum来请求这个IEnumVARIANT接口(参见上面的_NewEnum实现代码)。 IEnumVARIANT接口可以在任何地方实现,只要能从_NewEnum中获得这个接口指针就可以了。我们这里还是用MFC的聚合类的方法来实现, m_xEnumVARIANT就是集合类中的聚合类对象,具体的方法不多述了,本系列的上面几步中已讲了很多了。

Coll.h
...
DECLARE_disPATCH_MAP()
DECLARE_INTERFACE_MAP()
public:
CTcollCtrl* m_pCtrl;
void Draw(CDC* pdc);
CObList m_olItems;
BEGIN_INTERFACE_PART(EnumVARIANT,IEnumVARIANT)
STDMETHOD(Next)(THIS_ ULONG celt,VARIANT FAR* rgvar,
ULONG FAR* pceltFetched);
STDMETHOD(Skip)(THIS_ ULONG celt) ;
STDMETHOD(Reset)(THIS) ;
STDMETHOD(Clone)(THIS_ IEnumVARIANT FAR* FAR* ppenum) ;
XEnumVARIANT() ; // constructor to set m_posCurrent
POSITION m_posCurrent ; // Next() requires we keep track of our current item
END_INTERFACE_PART(EnumVARIANT)
...

Coll.cpp
...
BEGIN_INTERFACE_MAP(CColl,CCmdTarget)
INTERFACE_PART(CColl,IID_IColl,dispatch)
INTERFACE_PART(CColl,IID_IEnumVARIANT,EnumVARIANT) END_INTERFACE_MAP() CColl::XEnumVARIANT::XEnumVARIANT() { m_posCurrent = NULL ; } STDMETHODIMP_(ULONG) CColl::XEnumVARIANT::AddRef() { METHOD_PROLOGUE(CColl,EnumVARIANT) return pThis->ExternalAddRef() ; } STDMETHODIMP_(ULONG) CColl::XEnumVARIANT::Release() { METHOD_PROLOGUE(CColl,EnumVARIANT) return pThis->ExternalRelease() ; } STDMETHODIMP CColl::XEnumVARIANT::QueryInterface( REFIID iid,void FAR* FAR* ppvObj ) { METHOD_PROLOGUE(CColl,EnumVARIANT) return (HRESULT)pThis->ExternalQueryInterface( (void FAR*)&iid,ppvObj) ; } // IEnumVARIANT::Next // STDMETHODIMP CColl::XEnumVARIANT::Next( ULONG celt,ULONG FAR* pceltFetched) { // This sets up the "pThis" pointer so that it points to our // containing CDocuments instance // METHOD_PROLOGUE(CColl,EnumVARIANT) HRESULT hr; ULONG l ; CTcItem* pItem = NULL ; if(pceltFetched != NULL) *pceltFetched = 0; else if(celt > 1){ return ResultFromScode(E_INVALIDARG); } for(l=0; l<celt; L++){ Variantinit(&rgvar[l]); } hr = NOERROR; for(l=0; m_posCurrent != NULL && celt != 0; L++){ pItem = (CTcItem*)pThis->m_olItems.GetNext(m_posCurrent); celt--; if(pItem){ rgvar[l].vt = VT_disPATCH; rgvar[l].pdispVal = pItem->GetIdispatch(TRUE); if(pceltFetched != NULL) (*pceltFetched)++; } else{ return ResultFromScode( E_UNEXPECTED ); } } if (celt != 0) hr = ResultFromScode( S_FALSE ) ; return hr; } // IEnumVARIANT::Skip // STDMETHODIMP CColl::XEnumVARIANT::Skip(ULONG celt) { METHOD_PROLOGUE(CColl,EnumVARIANT) while(m_posCurrent != NULL && celt--) pThis->m_olItems.GetNext(m_posCurrent); return (celt == 0 ? NOERROR : ResultFromScode( S_FALSE )) ; } STDMETHODIMP CColl::XEnumVARIANT::Reset() { METHOD_PROLOGUE(CColl,EnumVARIANT) m_posCurrent = pThis->m_olItems.GetHeadPosition(); return NOERROR ; } STDMETHODIMP CColl::XEnumVARIANT::Clone(IEnumVARIANT FAR* FAR* ppenum) { METHOD_PROLOGUE(CColl,EnumVARIANT) CColl* p = new CColl ; if (p) { p->m_xEnumVARIANT.m_posCurrent = m_posCurrent ; return NOERROR ; } else return ResultFromScode( E_OUTOFMEMORY ) ; } 注: a.在前面的_NewEnum属性实现中 LPUNKNowN CColl::_NewEnum() { // Todo: Add your property handler here m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition(); LPUNKNowN pUnk = GetInterface(&IID_IEnumVARIANT); if(pUnk) pUnk->AddRef(); return pUnk; return NULL; } m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition(); 这一句就是用来设置枚举的起始位置的。 4.集合类基本完毕,现在是将集合类和控件类联起来的时候了,这个很简单,为控件类加一个LPdisPATCH Coll只读属性,另外由于集合类需要控件类的指针,所以在构造函数中将控件类的指针传递给了集合类的m_pCtrl成员,同样在集合的元素类中,也会需 要控件类的指针以及时刷新控件,这个指针的传递在集合类的Add方法中 LPdisPATCH CColl::Add(const VARIANT FAR& strKey,const VARIANT FAR& strText) { // Todo: Add your dispatch handler code here CTcItem* pitem = new CTcItem; pitem->m_pCtrl = m_pCtrl; ...... } TcollCtl.cpp ...... CTcollCtrl::CTcollCtrl() { InitializeIIDs(&IID_DTcoll,&IID_DTcollEvents); // Todo: Initialize your control's instance data here. m_pColl = new CColl; m_pColl->m_pCtrl = this; } ///////////////////////////////////////////////////////////////////////////// // CTcollCtrl::~CTcollCtrl - Destructor CTcollCtrl::~CTcollCtrl() { // Todo: Cleanup your control's instance data here. delete m_pColl; } ... LPdisPATCH CTcollCtrl::GetColl() { // Todo: Add your property handler here return m_pColl->GetIdispatch(TRUE); return NULL; } tcoll.odl ...... [ uuid(7852D333-7E2F-49DA-BA8F-EAC8748A23B2),helpstring("dispatch interface for Tcoll Control"),hidden ] dispinterface _DTcoll { properties: // NOTE - ClassWizard will maintain property information here. // Use extreme caution when editing this section. //{{AFX_ODL_PROP(CTcollCtrl) [id(1)] IColl* Coll; //}}AFX_ODL_PROP methods: // NOTE - ClassWizard will maintain method information here. // Use extreme caution when editing this section. //{{AFX_ODL_METHOD(CTcollCtrl) //}}AFX_ODL_METHOD [id(disPID_ABOUTBox)] void AboutBox(); }; ...... 5.整个框架好了,现在还需要一个外在的表现了,OnDraw函数上场了。 void CTcollCtrl::OnDraw( CDC* pdc,const CRect& rcBounds,const CRect& rcInvalid) { // Todo: Replace the following code with your own drawing code. pdc->FillRect(rcBounds,CBrush::FromHandle((HBrush)GetStockObject(WHITE_Brush))); m_pColl->Draw(pdc); } void CColl::Draw(CDC *pdc) { int x = 5; int y = 5; POSITION pos = m_olItems.GetHeadPosition(); while(pos){ CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos); pdc->textout(x,y,pitem->m_text); y += 20; } } 控件类先画底,然后调用集合类的Draw函数,在集合类的Draw函数中将所有集合元素中的Text属性列了出来。 6.编译,在VB下测试 Private Sub Form_Load() With Tcoll1 .Coll.Add "keyHello","Hello" .Coll.Add,"Good" .Coll.Add "keyThank" MsgBox .Coll("keyHello").Text Dim c As TcItem For Each c In .Coll MsgBox c.Text & c.Key Next End With End Sub 参考资料:刚刚发现,很多关于ActiveX和OLE的资料 MSDN98/98VS/2052/techart.chm 本文的相关参考也在其中 MSDN98/98VS/2052/techart.chm::/html/msdn_collect.htm

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Format[$] ( expr [ , fmt ] ) format 返回变体型 format$ 强制返回为文本 -------------------------------- 数字类型的格式化 --------------------------------     固定格式参数:     General Number 普通数字,如可以用来去掉千位分隔号     format$("100,1
VB6或者ASP 格式化时间为 MM/dd/yyyy 格式,竟然没有好的办法, Format 或者FormatDateTime 竟然结果和系统设置的区域语言的日期和时间格式相关。意思是尽管你用诸如 Format(Now, "MM/dd/yyyy"),如果系统的设置格式区域语言的日期和时间格式分隔符是"-",那他还会显示为 MM-dd-yyyy     只有拼凑: <%response.write
在项目中添加如下代码:新建窗口来显示异常信息。 Namespace My ‘全局错误处理,新的解决方案直接添加本ApplicationEvents.vb 到工程即可 ‘添加后还需要一个From用来显示错误。如果到这步还不会则需要先打好基础啦 ‘======================================================== ‘以下事件
转了这一篇文章,原来一直想用C#做k3的插件开发,vb没有C#用的爽呀,这篇文章写与2011年,看来我以前没有认真去找这个方法呀。 https://blog.csdn.net/chzjxgd/article/details/6176325 金蝶K3 BOS的插件官方是用VB6编写的,如果  能用.Net下的语言工具开发BOS插件是一件很愉快的事情,其中缘由不言而喻,而本文则是个人首创,实现在了用V
Sub 分列() ‘以空格为分隔符,连续空格只算1个。对所选中的单元格进行处理 Dim m As Range, tmpStr As String, s As String Dim x As Integer, y As Integer, subStr As String If MsgBox("确定要分列处理吗?请确定分列的数据会覆盖它后面的单元格!", _
  窗体代码 1 Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) 2 Dim path As String, hash As String 3 For Each fil
  Imports MySql.Data.MySqlClient Public Class Form1 ‘ GLOBAL DECLARATIONS Dim conString As String = "Server=localhost;Database=net2;Uid=root;Pwd=123456;" Dim con As New MySqlConnection
‘導入命名空間 Imports ADODB Imports Microsoft.Office.Interop   Private Sub A1() Dim Sql As String Dim Cnn As New ADODB.Connection Dim Rs As New ADODB.Recordset Dim S As String   S = "Provider=OraOLEDB.Oracl
Imports System.IO Imports System.Threading Imports System.Diagnostics Public Class Form1 Dim A(254) As String    Function ping(ByVal IP As Integer) As String Dim IPAddress As String IPAddress = "10.0.
VB运行EXE程序,并等待其运行结束 参考:https://blog.csdn.net/useway/article/details/5494084 Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long Pr
今天碰到一个问题,登陆的时候,如果不需要验证手机号为空,则不去验证手机号 因为登陆的时候所有的验证信息都存放在一个数组里 Dim CheckUserInfo() As String ={UserBirthday, SecEmail, UserMob, UserSex, RealNameFirst, RealName, CheckCardID, CheckCardType, Contactemail
在VB6.0中,数据访问接口有三种: 1、ActiveX数据对象(ADO) 2、远程数据对象(RDO) 3、数据访问对象(DAO) 1.使用ADO(ActiveX Data Objec,ActiveX数据对象)连接SQL Server 1)使用ADO控件连接 使用ADO控件的ConnectionString属性就可以连接SQL Server,该属性包含一个由分号分隔的argument=value语
注:大家如果没有VB6.0的安装文件,可自行百度一下下载,一般文件大小在200M左右的均为完整版的软件,可以使用。   特别提示:安装此软件的时候最好退出360杀毒软件(包括360安全卫士,电脑管家等,如果电脑上有这些软件的话),因为现如今的360杀毒软件直接会对VB6.0软件误报,这样的话就可能会在安装过程中被误报阻止而导致安装失败,或者是安装后缺乏很多必须的组件(其它的杀毒软件或安全卫士之类的
Private Sub Form_Load() Call conndb End Sub Private Function conndb() Dim cn As New ADODB.Connection Dim rs As New ADODB.Recordset Dim strCn, sql As String Dim db_host As String Dim db_user As String
  PPSM06S70:  Add  moddate  EDITSPRINTJOB:  MAX(TO_CHAR(ETRN.MODDATE, ‘yyyy/mm/dd/HH24:MI AM‘)) ACTUAL_SHIPDATE   4.Test Scenario (1) :Query SQL Test DN:8016578337 SELECT CTRN.TKCTID TRUCK_ID,        
  沒有出現CrystalReportViewer時,須安裝CRforVS_13_0. 新增1個數據集,新增1個數據表,添加二列,列名要和資料庫名一樣. 修改目標Framework 修改app.config, <startup >改成<startup useLegacyV2RuntimeActivationPolicy ="true">  CrystalReport1.rpt增加數據庫專家 在表單
Imports System.Threading Imports System Public Class Form1 Dim th1, th2 As Thread Public Sub Method1() Dim i As Integer For i = 1 To 100 If Me.Label1.BackColor =
Friend Const PROCESS_ALL_ACCESS = &H1F0FFF = 2035711 Friend Const PROCESS_VM_READ = &H10 Friend Const PROCESS_VM_WRITE = &H20 Friend Const PAGE_READONLY = &H2 Friend Const PAGE_READWRITE = &H4 Friend
以下代码随手写的 并没有大量测试 效率也有待提升 如果需要C#的请自行转换 Function SplitBytes(Data As Byte(), Delimiter As Byte()) As List(Of Byte()) Dim i = 0 Dim List As New List(Of Byte()) Dim bytes As New
Imports System.Data.SqlClient Public Class Form1 REM Public conn1 As SqlConnection = New SqlConnection("server=.; Integrated Security=False;Initial Catalog= mydatabase1; User ID= sa;password")