在使用VB6編寫類或控件時,有一個不如意的地方,那就是無法在屬性或函數中返回結構體。一般情況下,我們可以使用一個新的類來模擬結構體,比如有一個表格類,類名爲clsGrid,它包含一個行屬性Rows,而Rows又包含Height等成員屬性,此時,我們需要定義一個類clsRows,代碼大致如下:
類clsGrid:
Dim m_objRows As clsRows
Private Sub Class_Initialize()
Set m_objRows = New clsRows
End Sub
Private Sub Class_Terminate()
Set m_objRows = Nothing
End Sub
Public Property Get Rows(Optional ByVal Index as Long) As clsRows
Set Rows = m_objRows
End Property
類clsRows:
Dim m_lngHeight As Long
Public Property Get Height() As Long
Height = m_lngHeight
End Property
Public Property Let Height(ByVal New_Value As Long)
m_lngHeight = New_Value
End Property
OK,一切看上去很不錯,但問題來了,當類clsRow的成員屬性Height改變時,需要通知類clsGrid,以便類clsGrid在Class_Paint事件中按新的Height屬性繪製,怎麼辦?
相信大家已經想到了答案,那就是讓clsRows拋出事件,或者定義一個全局變量,讓clsGrid和clsRows都可以讀寫。
如果只是一個clsRows實例,拋出事件也是一個不錯的選擇,然而,這是一個表格類,行對象(即clsRows可能會超過10萬個),很明顯,拋出事件是一種低效的選擇,而且更重要的是,由於事件無法使用Friend而進行內部封裝,會對應用程序員編寫應用代碼時造成不必要的誤解。
那麼,就用全局變量吧。
我們先增加一個標準模塊,然後定義一個全局變量。
標準模塊modDeclare:
Global g_lngHeight() As Long
類clsGrid:
Private Sub Class_Initialize()
Set m_objRows = New clsRows
Redim g_lngHeight(1000)
End Sub
Private Sub Class_Terminate()
Set m_objRows = Nothing
End Sub
Public Property Get Rows(Optional ByVal Index as Long) As clsRows
m_objRows.RowIndex = Index
Set Rows = m_objRows
End Property
Public Sub Class_Paint()
Dim i as Long
For i=0 To UBound(g_lngHeight)
'繪圖代碼略
Next
End Sub
類clsRows:
Public RowIndex As Long
Public Property Get Height() As Long
Height = g_lngHeight(RowIndex)
End Property
Public Property Let Height(ByVal New_Value As Long)
g_lngHeight(RowIndex) = New_Value
End Property
到這裏,代碼基本實現了設計任務,看來可以編譯打包後交差了,打包工程暫命名爲MYGrid。
接下來,我們將使用MYGrid.Grid編寫一個應用,先建一個測試工程,工程裏只包含一個標準模塊,測試代碼如下:
Sub Main()
Dim o As MYGrid.Grid
Set o = New MYGrid.Grid
o.Rows(1).Height = 100
Debug.Print o.Rows(1).Height
Set o = Nothing
End Sub
運行後,立即窗口如願輸出了100,結果正確。
我們再將代碼修改一下:
Sub Main()
Dim o1 As MYGrid.Grid
Dim o2 As MYGrid.Grid
Set o1 = New MYGrid.Grid
Set o2 = New MYGrid.Grid
o1.Rows(1).Height = 100
o2.Rows(1).Height = 200
Debug.Print o1.Rows(1).Height,o2.Rows(1).Height
Set o1 = Nothing
Set o2 = Nothing
End Sub
立即窗口輸出200,200,這個結果明顯是錯的,爲什麼會這樣呢?
原來問題出在全局變量裏,在ActiveX DLL工程裏,不管應用程序創建了多個接口實例,而工程實例只有一個,也就是說,全局變量g_lngRows將被實例o1和o2共同使用,這樣一來,o1和o2的屬性值就成了一個了。
如何解決這個問題呢?
答案當然是避免在ActiveX DLL 工程中使用全局變量,而是在類clsGrid中聲明數組lngHeight(),然而這樣一來,類clsRows卻無法訪問lngHeight()了,怎麼辦?
我們也許會想到在clsGrid中傳遞一個引用給clsRows,比如:
類clsGrid:
Dim m_lngHeight() As Long
Private Sub Class_Initialize()
Set m_objrows = New clsRows
ReDim m_lngHeight(1000)
m_objrows.BindArray m_lngHeight
End Sub
Private Sub Class_Terminate()
Set m_objrows = Nothing
End Sub
Public Property Get Rows(Optional ByVal Index As Long) As clsRows
m_objrows.RowIndex = Index
Set Rows = m_objrows
End Property
Public Sub Class_Paint()
Dim i As Long
For i = 0 To UBound(m_lngHeight)
'繪圖代碼略
Next
End Sub
類clsRows:
Public RowIndex As Long
Dim m_lngHeight() As Long
Public Property Get Height() As Long
Height = m_lngHeight(RowIndex)
End Property
Public Property Let Height(ByVal New_Value As Long)
m_lngHeight(RowIndex) = New_Value
End Property
Friend Sub BindArray(ByRef v() As Long)
m_lngHeight = v
End Sub
按我們的思路,當修改clsRows中的m_lngHeight()時,clsGrid中的m_lngHeight()應該一起改變,因爲clsRows中的m_lngHeight()是clsGrid中的m_lngHeight()一個引用。
其實不然,在BindArray(ByRef v() As Long)中,只有v()是正確的引用,當使用m_lngHeight = v語句時,結果是m_lngHeight按照v構建了一個新的數組。
那麼,怎麼實現clsRows中的m_lngHeight()是clsGrid中的引用呢?
正確的辦法是將clsRows中的m_lngHeight這個安全數組的pvData成員指針指向clsGrid中的m_lngHeight中的pvData成員(具體可參才SAFEARRAY結構),爲此,我們可以使用CopyMemory進行構造,然而,由於SAFEARRAY結構體是變長的,其長度隨維數而變化,從而導致CopyMemory不知源數據的長度。其實,我們可以巧用VARIANT,因爲VARIANT結構體是定長的,這樣就可以實現通用的引用來代替全局變量,需要注意的是,不要忘了在類銷燬前還原VARIANT,修正後的clsRows代碼如下:
類clsRows:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Public RowIndex As Long
Dim m_lngHeight As Variant
Public Property Get Height() As Long
Height = m_lngHeight(RowIndex)
End Property
Public Property Let Height(ByVal New_Value As Long)
m_lngHeight(RowIndex) = New_Value
End Property
Friend Sub BindArray(ByRef v As Variant)
CopyMemory m_lngHeight, v, 16 'VARIANT結構體長度爲16
End Sub
Private Sub Class_Terminate()
Dim v As Variant
CopyMemory m_lngHeight, v, 16 '還原m_lngHeight
End Sub