到目前為止,我們的例子中都沒有一點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, long * aiIndex, 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, ai, &iVal)) throw hres;
}
}
HRESULT SafeArrayPtrOfIndex(SAFEARRAY * psa, long *
aiIndex, 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的用法解釋的多了些,應該不會煩到哪去吧。那就到這裡了。