ActiveX的MFC设计之旅-第2步 .

我晕,就因为我没写摘要,也不至于把我写的这一大段给全删没了吧,这Blog也做的太菜了吧
哎,只好重来了

上一步咱走到哪了,哦,对了,就是要能在VB编辑时列出LiteGrid的Cell对象的属性。
首先,为什么列不出来,那是因为我们的Cell属性的返回类型是LPDISPATCH,也就是IDispatch*,IDispatch是一通用接口,好象是没有Text之类的属性的了
所以,很简单的,把LPDISPATCH改成ICell*就可以了

1.打开LiteGrid.odl文件,将Cell属性的返回类型LPDISPATCH改成ICell*
是不是好了,编译看看,好象不行啊,不认识ICell*,呵呵,我想大多数的朋友都知道原因的,把ICell接口的定义提前就可以了。
2.把从注释语句
// Primary dispatch interface for CCell
开始的ICell接口相关的定义拉到
// Primary dispatch interface for CLiteGridCtrl
前面。
编译看看,成功了
3.在VB中试试,当写到LiteGrid1.Cell(0,0).时,是不是列出来Cell对象的属性了,虽然只有一个(本来就只有一个啊),写完后,运行看看,OK了

好简单啊,呵呵,这回就算一小碎步吧,接下来我们会转个弯,把步子迈向属性页了,不过要到下一文了

前面说要转向属性页,突然想起好象还有一个比较有意思的小编没看,所以就先不转了,再往前走那么一小步吧
这次要看的是枚举
在VB中,我们会看到,好多控件在编辑代码的时候,会列出某一属性,或方法参数的许多可选值来,如
ListView1.BorderStyle =写到这时就会列出来ccFixedSingle和ccNone两个可选项来
它们是怎么来的呢,是的,就是枚举

在C++中,枚举的使用是很简单的,抄msdn上一段
enum Days // Declare enum type Days
{
saturday,// saturday = 0 by default
sunday = 0,// sunday = 0 as well
monday,// monday = 1
tuesday,// tuesday = 2
wednesday,// etc.
thursday,
friday
} today;

那么这里我们要怎么弄呢,也象上面一样用吗?用到哪?

用关键字enum查查msdn就能查到用法了,下面是msdn中的一个例子

typedef [uuid(DEADF00D-C0DE-B1FF-F001-A100FF001ED),helpstring("Farm Animals are friendly"),helpcontext(234)]
enum {
[helpstring("Moo")] cows = 1,pigs = 2
} ANIMALS;
在例子中,我们现在要加上一个滚动条,滚动条的枚举共4个,none,horz,vert和both

1.使用VC的Guid工具,(不出意外的话,在Tools菜单下就能找到)生成一个GUID,拷贝下来
2.打开LiteGrid.odl,在ICell接口的前面(之所以要加到前面,是因为这些枚举,可能其它接口会用到)加上
typedef [ uuid(C9A2BD1A-AEA5-459a-BA59-5C8C3C01E403) ]
enum
{
lgnone = 0,
lghorz = 1,
lgvert = 2,
lgboth = 3
} lgScroll;
上面的uuid是我生成的,如果不幸有哪位朋友也跟着想试试,请用自己生成的UUID
3.接下来定义一个属性ScrollBars,定义的时候,你并不能找到lgScroll类型,所以可以用long类型
4.接着在LiteGrid.odl中找到ScrollBars属性,将它的long类型改为lgScroll
5.编译,在VB中试试,是不是列出来了
就这么简单了
6.不过好象设置这些值没有用,当然了,没有实现代码啊,在OnScrollBarsChanged加上
if(!::IsWindow(m_hWnd)) return;
long l = GetWindowLong(m_hWnd,GWL_STYLE);
if(m_scrollBars & 0x01){
l |= WS_HSCROLL;
}
else{
l &= ~WS_HSCROLL;
}
if(m_scrollBars & 0x02){
l |= WS_VSCROLL;
}
else{
l &= ~WS_VSCROLL;
}
SetWindowLong(m_hWnd,GWL_STYLE,l);
SetWindowPos(NULL,SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE | SWP_FRAMECHANGED);
SetModifiedFlag();
再编译,再试,出来了吧
呵呵,这一步就走到这了,要下班了,88了
那么这回就来看一下网上说的挺多的传递自定义结构的问题吧,这个问题在早期不支持DCOM的操作系统中是没办法解决的,不过就目前的流行操作系统来说,应该是不成问题的。
其实网上讨论很多的,不过还是推荐一些网址:
第一个是微软的msdn,应该蛮正宗的吧,http://windowssdk.msdn.microsoft.com/library/default.asp?url=/library/en-us/Automat/htm/chap12_3rcj.asp
第二个是我偶尔看到的,在csdn上,感觉蛮不错的,中文的嘛,呵呵
http://dev.csdn.net/article/67/67955.shtm
其它的稀奇古怪的就不说了,各位可以自己网上搜搜,一大把的

那咱就照着这些资料开始我们自己的自定义结构的传递了,我们要定义的结构是RECT,用来确定Cell的尺寸位置,在VC中已经有了结构RECT,但是VB中却没有,没关系,我们自己给它定义一个。

1.打开LiteGrid.odl文件,在ICell接口的定义前面(其它位置也没关系)加上
typedef [ uuid(6BF5EE0C-373A-4893-89EB-2C0208D3D4EB) ]
struct tagtRECT
{
long left;
long top;
long right;
long bottom;
}Rect;
UUID用GUID生成工具生成,就不多说了了,以后也不说了
这里本来用tagRECT的,结果居然提示说有重复定义,同样也没用RECT,而用Rect了。
2.为CCell添加成员变量CRect m_rect;在构造函数中初始化为{0,0}
3.为CCell添加Get/Set属性Rect,类型为VARIANT
4.在LiteGrid.odl中,将该属性的类型VARIANT改为Rect
5.Get/Set函数定义如下:
void CCell::SetRect(const VARIANT FAR& newValue)
{
// TODO: Add your property handler here
COleVariant ar(newValue);
if(ar.vt == VT_RECORD){
IRecordInfo* pri = ar.pRecInfo;
pri->RecordCopy(ar.pvRecord,(PVOID)&m_rect);
}
}

VARIANT CCell::GetRect()
{
VARIANT vaResult;
VariantInit(&vaResult);
// TODO: Add your property handler here
IRecordInfo* pRecInfo = NULL;
GetRecordInfoFromGuids(GUID_Lib,1,LOCALE_USER_DEFAULT,GUID_Rect,&pRecInfo);
vaResult.vt = VT_RECORD;
vaResult.pvRecord = (PVOID)&m_rect;
vaResult.pRecInfo = pRecInfo;
return vaResult;
}
6.编译通过
7.新建VB工程,添加LiteGrid控件,在Form_Load中添加代码如下:
Dim rc As Rect
Dim rc1 As Rect
Dim ce As Cell
Set ce = LiteGrid1.Cell(0,0)
If Not ce Is Nothing Then
rc = ce.Rect
ce.Rect.Left = 100
rc = ce.Rect
rc.Left = 10
rc.Top = 29
rc.Right = 12
rc.bottom = 439
ce.Rect = rc
rc1 = ce.Rect
End If
运行观察一下几次rc和rc1的值,可以发现结果完全正确
OK,搞定

但是,不知道什么原因,从控件调试环境运行时,却发现以上代码调试出错,只保留rc = ce.Rect或只保留ce.Rect = rc代码时却不会出错,不知何原因,郁闷。
另外,对于GetRect中的pRecordInfo,不知是否需要Release,有待确定。
嘿嘿,咱不管了,懒得弄了,继续旅程了。
到目前为止,我们的例子中都没有一点Grid的影子,这趟改造一下程序,稍微能象点样,顺便加上一个也蛮流行的话题,传递数组问题,或者说是SAFEARRAY吧


我们先来看看SAFEARRAY和它的操作函数,从msdn上翻译了那么一段下来
SAFEARRAY的定义如下

struct SAFEARRAY {
WORD cDims;
WORD fFeatures;
DWORD cbElements;
DWORD cLocks;
void * pvData;
SAFEARRAYBOUND rgsabound[1];
};

基本上它的操作函数也就是对这个结构的操作了

SAFEARRAY * SafeArrayCreate(VARTYPE vt,UINT cDims,SAFEARRAYBOUND * aDims);
HRESULT SafeArrayDestroy(SAFEARRAY * psa);

这组函数不用多说了,建立一个SAFEARRAY,不过这个函数有个缺陷,它只能处理类型是VARIANT类型子集中的元素。

HRESULT SafeArrayAllocDescriptor(UINT cDims,SAFEARRAY ** ppsaOut);
HRESULT SafeArrayAllocData(SAFEARRAY * psa);
HRESULT SafeArrayDestroyData(SAFEARRAY * psa);
HRESULT SafeArrayDestroyDescriptor(SAFEARRAY * psa);

这组函数就提供了更多,更灵活的元素类型,但是用起来就稍微复杂一些了,何况一般在COM中也用不了这种复杂的数据类型。

HRESULT SafeArrayLock(SAFEARRAY * psa);
HRESULT SafeArrayUnlock(SAFEARRAY * psa);

这组函数只是对结构中cLocks值+1和-1,为什么要用这组函数,目前为止好象也不是非常理解,可能是为了防止在操作数据时删除数组吧,另外在已经 SafeArrayLock后,还可以再在其它地方SafeArrayLock来对数组进行操作,只要记得用完后SafeArrayUnlcok一下就可 以了。

HRESULT SafeArrayGetElement(SAFEARRAY * psa,long * aiIndex,void * pvElem);
HRESULT SafeArrayPutElement(SAFEARRAY * psa,void * pvElem);

这组函数是得到数组中的某一个值,aiIndex就是下标索引了,这组函数在每次调用的时候都自动调用SafeArrayLock和 SafeArrayUnlock,所以在需要遍历数组中的元素时,最好先用SafeArrayLock,再直接操作数据,最后用 SafeArrayUnlock。该组函数的用法举例如下:
long ai[2];
int iVal;
xMin = aDims[0].lLbound;
xMax = xMin + (int)aDims[0].cElements - 1;
yMin = aDims[1].lLbound;
yMax = yMin + (int)aDims[1].cElements - 1;
for (x = xMin; x <= xMax; x++) {
ai[0] = x;
for (y = yMin; y <= yMax; y++) {
ai[1] = y;
if (hres = SafeArrayGetElement(psaiInOut,ai,&iVal)) throw hres;
// Equivalent to: aiInOut(x,y) = aiInOut(x,y) + 1.
iVal++;
if (hres = SafeArrayPutElement(psaiInOut,&iVal)) throw hres;
}
}

HRESULT SafeArrayPtrOfIndex(SAFEARRAY * psa,void ** ppv);

这个函数主要用在多维数组中,一维数组可以用,不过没什么意思吧,这个函数其实和SafeArrayGetElement类似,不过是一个有Lock,一个没Lock,所以用这个函数前,最好先SafeArrayLock一下,用完了,再SafeArrayUnlock。

HRESULT SafeArrayAccessData (SAFEARRAY * psa,void ** ppvData);
HRESULT SafeArrayUnaccessData(SAFEARRAY * psa);

这组函数只是简单的Lock和Unlock,加上返回SAFEARRAY结构的pvData成员而已,并不包含维数信息,所以,想不出什么理由来使用它,当然要使用那也是你的事。(这是文摘中的话,个人觉得在用一维数组时,这组函数非常有用)

UINT SafeArrayGetDim(SAFEARRAY * psa);
UINT SafeArrayGetElemsize(SAFEARRAY * psa);

这两个函数,第一个返回数组的维数,第二个返回数组中每个元素的大小,比如long就是4了。

其它的函数也不多说了,累,查查资料了

在MFC中,SAFEARRAY用COleSafeArray来封装了,稍微简化一些操作,不过基本用法应该一样的。

现在,我们开始了:
1.改造一下目前的代码,我们先用一个确定大小的Grid,就定为10*10吧

在CLiteGrid中加上成员变量
CCell m_cells[10][10];

在CLiteGrid的构造函数中初始化这100个CCell
CLiteGridCtrl::CLiteGridCtrl()
{
InitializeIIDs(&IID_DLiteGrid,&IID_DLiteGridEvents);

// TODO: Initialize your control's instance data here.
m_scrollBars = 0;

int x = 0;
int y = 0;
for(int i=0; i<10; i++,x+=50){
y = 0;
for(int j=0; j<10; j++,y+=20){
m_cells[i][j].m_rect = CRect(x,y,x+50,y+20);
}
}
}

修改LPDISPATCH CLiteGridCtrl::GetCell(short nCol,short nRow)
LPDISPATCH CLiteGridCtrl::GetCell(short nCol,short nRow)
{
// TODO: Add your property handler here
/* CCell* pcell = new CCell;
return pcell->GetIDispatch(FALSE);*/
if(nCol >=0 && nCol < 10 && nRow >= 0 && nRow < 10){
return m_cells[nCol][nRow].GetIDispatch(TRUE);
}
else{
return NULL;
}
}

添加OnDraw绘图代码
void CLiteGridCtrl::OnDraw(
CDC* pdc,const CRect& rcBounds,const CRect& rcInvalid)
{
// TODO: Replace the following code with your own drawing code.
pdc->FillRect(&rcBounds,&CBrush(RGB(10,200,100)));
for(int i=0; i<10; i++){
for(int j=0; j<10; j++){
m_cells[i][j].Draw(pdc);
}
}
}

显然还缺省CCell的Draw代码,所以再添加CCell::Draw(CDC* pdc)函数
void CCell::Draw(CDC *pdc)
{
pdc->Rectangle(&m_rect);
int noldmode = pdc->SetBkMode(TRANSPARENT);
pdc->DrawText(m_text,&m_rect,DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pdc->SetBkMode(noldmode);
}
2.应该差不多了吧,大致就是画10*10个框框,然后在框框中写字,现在编译一下,不出意外的话,是通过了的。
3.转到VB下,就可以看到这个框框了,不过好象没字,当然了,m_text是空的啊
4.当然可以通过ICell的Text属性给m_text赋值,但我们讲了那么多SafeArray,总要用上吧,所以为CLiteGridCtrl再加 上一个Texts属性,用这个属性给所有的Cell赋上Text,Texts属性用Get/Set methods,类型为VARIANT
5.实现Texts属性

VARIANT CLiteGridCtrl::GetTexts()
{
COleSafeArray sa;
DWORD dweles[2] = {10,10};
sa.Create(VT_BSTR,2,dweles);
long lindex[2] = {0};
BSTR* pele = NULL;
sa.Lock();
for(int i=0; i<10; i++){
lindex[0] = i;
for(int j=0; j<10; j++){
lindex[1] = j;
sa.PtrOfIndex(lindex,(void**)&pele);
*pele = T2BSTR(m_cells[i][j].m_text);
}
}
sa.Unlock();
return sa.Detach();
}

void CLiteGridCtrl::SetTexts(const VARIANT FAR& newValue)
{
// TODO: Add your property handler here
USES_CONVERSION;
COleSafeArray sa(newValue);
ASSERT(sa.GetDim() == 2);
long llb1 = 0;
long lub1 = 0;
long llb2 = 0;
long lub2 = 0;
long l1 = 0;
long l2 = 0;
sa.GetLBound(1,&llb1);
sa.GetUBound(1,&lub1);
l1 = lub1-llb1+1;
ASSERT(l1 == 10);
sa.GetLBound(2,&llb2);
sa.GetUBound(2,&lub2);
l2 = lub2-llb2+1;
ASSERT(l2 == 10);

long lindex[2] = {0};
BSTR* pele = NULL;
sa.Lock();
for(int i=llb1; i<=lub1; i++){
lindex[0] = i;
for(int j=llb2; j<=lub2; j++){
lindex[1] = j;
sa.PtrOfIndex(lindex,(void**)&pele);
m_cells[i-llb1][j-llb2].m_text = OLE2T(*pele);
}
}
sa.Unlock();
InvalidateControl();
SetModifiedFlag();
}
要注意的是因为用到了字符串转换宏OLE2T,T2BSTR,所以需要#include "afxpriv.h",个人还是挺喜欢这组宏的,用起来特省事,就是老要加USES_CONVERSION,别扭的紧。

6.在VB的Form_Load中添加如下代码
Private Sub Form_Load()
Dim str(0 To 9,0 To 9) As String
Dim stro() As String
Dim i As Integer
Dim j As Integer
For i = 0 To 9
For j = 0 To 9
str(i,j) = i & " : " & j
Next
Next
LiteGrid1.Texts = str
stro = LiteGrid1.Texts
End Sub

运行后可以发现10*10个网格,每个网格上都有纵横坐标字样 这篇好象稍微长了些,主要是SAFEARRAY的用法解释的多了些,应该不会烦到哪去吧。那就到这里了。

相关文章

Format[$] ( expr [ , fmt ] ) format 返回变体型 format$ 强...
VB6或者ASP 格式化时间为 MM/dd/yyyy 格式,竟然没有好的办...
在项目中添加如下代码:新建窗口来显示异常信息。 Namespace...
转了这一篇文章,原来一直想用C#做k3的插件开发,vb没有C#用...
Sub 分列() ‘以空格为分隔符,连续空格只算1个。对所选...
  窗体代码 1 Private Sub Text1_OLEDragDrop(Data As Dat...