Mi é
recentemente capitata l'esigenza di avere una treeview (vale anche per la
listview) che permetta di avere le checkbox a tre (o più) stati.
Basandomi
sull'ottimo lavoro di Brad Martinez (in VB6)
http://www.mvps.org/ccrp/
ho esteso il
controllo treeview per suportare nodi a tre stati.
Dal principio, un
bell'Enum e una classe TreeNode estesa:
Public Enum TreeViewCheckState
NoCheck = 0
UnChecked = 1
Checked = 2
UnCheckedGrayed = 3
CheckedGrayed = 4
End Enum
Public Class TreeNode
Inherits System.Windows.Forms.TreeNode
Private m_CheckState As TreeViewCheckState
Public Overridable Property CheckState() As TreeViewCheckState
Get
Return m_CheckState
End Get
Set(ByVal Value As TreeViewCheckState)
m_CheckState = Value
'Imposto il valore di checked:
If Not Me.TreeView Is Nothing AndAlso TypeOf Me.TreeView Is Treeview Then
DirectCast(Me.TreeView, Treeview).SetCheckIcon(Me)
End If
End Set
End Property
End Class
E a questo punto
viene il momento della treeview.
Alcune note sul comportamento della
treeview:
1) Integra una ImageList ChecksImageList
che continene le immagini che può assumere la "checkbox" dei singoli treenode.
L'immagine di indice zero non ha importanza perché all'indice zero corrisponde
l'assenza di checkbox.
2) C'è una sottoclasse EventHashtable (il nome deriva
dal fatto che nella mia treeview ha anche altri compiti) che permette di capire
quando un treenode viene aggiunto. Tale classe é già presente nella treeview
standard, ma é "friend".
Non ho (ancora) implementato un meccanismo di
transizione fra uno shato di cheskedstate e l'altro... Lascio al
lettore questo divertimento!
Public Class Treeview
Inherits System.Windows.Forms.TreeView
Private components As System.ComponentModel.IContainer
Private Sub InitializeComponent()
Me.components = New System.ComponentModel.Container
Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(Treeview))
Me.ChecksImageList = New System.Windows.Forms.ImageList(Me.components)
'
'ChecksImageList
'
Me.ChecksImageList.ImageSize = New System.Drawing.Size(16, 16)
Me.ChecksImageList.ImageStream = CType(resources.GetObject("ChecksImageList.ImageStream"), System.Windows.Forms.ImageListStreamer)
Me.ChecksImageList.TransparentColor = System.Drawing.Color.Transparent
End Sub
#Region "Multistate treeview"
#Region "Costanti e strutture"
Protected Structure TVITEM
Public mask As TVItemMasks
Public hItem As IntPtr
Public state As TVItemStates
Public stateMask As Int32
Public pszText As String ' Long ' pointer
Public cchTextMax As Int32
Public iImage As Int32
Public iSelectedImage As Int32
Public cChildren As Int32
Public lParam As Int32
End Structure
<System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet:=System.Runtime.InteropServices.CharSet.Auto, Pack:=1)> _
Public Structure TV_INSERTSTRUCT
' Fields
Public hInsertAfter As IntPtr
Public hParent As IntPtr
Public item_cChildren As Integer
Public item_cchTextMax As Integer
Public item_hItem As Integer
Public item_iImage As Integer
Public item_iSelectedImage As Integer
Public item_lParam As IntPtr
Public item_mask As Integer
Public item_pszText As IntPtr
Public item_state As Integer
Public item_stateMask As Integer
End Structure
Protected Enum TVItemMasks As Integer
TVIF_TEXT = &H1
TVIF_IMAGE = &H2
TVIF_PARAM = &H4
TVIF_STATE = &H8
TVIF_HANDLE = &H10
TVIF_SELECTEDIMAGE = &H20
TVIF_CHILDREN = &H40
'#If (WIN32_IE >= &H400) Then ' WIN32_IE = 1024 (>= Comctl32.dll v4.71)
TVIF_INTEGRAL = &H80
'#End If
TVIF_DI_SETITEM = &H1000 ' Notification
End Enum
<Flags()> _
Protected Enum TVItemStates As Integer
TVIS_SELECTED = &H2
TVIS_CUT = &H4
TVIS_DROPHILITED = &H8
TVIS_BOLD = &H10
TVIS_EXPANDED = &H20
TVIS_EXPANDEDONCE = &H40
'#If (WIN32_IE >= &H300) Then
TVIS_EXPANDPARTIAL = &H80
'#End If
TVIS_OVERLAYMASK = &HF00
TVIS_STATEIMAGEMASK = &HF000
TVIS_USERMASK = &HF000
End Enum
Protected Const TV_FIRST As Int32 = &H1100
Protected Const TVM_SETIMAGELIST As Int32 = (TV_FIRST + 9)
Protected Const TVM_SETITEM As Int32 = (TV_FIRST + 13)
Protected Enum TVM_GET_SETIMAGELIST_wParam As Integer
TVSIL_NORMAL = 0
TVSIL_STATE = 2
End Enum
Protected Enum TVHT_flags As Integer
TVHT_NOWHERE = &H1
TVHT_ONITEMICON = &H2
TVHT_ONITEMLABEL = &H4
TVHT_ONITEMINDENT = &H8
TVHT_ONITEMBUTTON = &H10
TVHT_ONITEMRIGHT = &H20
TVHT_ONITEMSTATEICON = &H40
TVHT_ONITEM = (TVHT_ONITEMICON Or TVHT_ONITEMLABEL Or TVHT_ONITEMSTATEICON)
TVHT_ABOVE = &H100
TVHT_BELOW = &H200
TVHT_TORIGHT = &H400
TVHT_TOLEFT = &H800
End Enum
#End Region
Protected Friend WithEvents ChecksImageList As System.Windows.Forms.ImageList
<System.Runtime.InteropServices.DllImport("User32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto, Entrypoint:="SendMessage")> _
Private Overloads Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wparam As Int32, ByVal lparam As IntPtr) As IntPtr
End Function
<System.Runtime.InteropServices.DllImport("User32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto, Entrypoint:="SendMessage")> _
Private Overloads Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wparam As Int32, ByRef lparam As TVITEM) As IntPtr
End Function
Protected Function SetItemStateImage(ByVal hItem As IntPtr, ByVal iState As Int32) As Boolean
Dim tvi As TVITEM
tvi.mask = TVItemMasks.TVIF_HANDLE Or TVItemMasks.TVIF_STATE
tvi.hItem = hItem
tvi.stateMask = TVItemStates.TVIS_STATEIMAGEMASK
tvi.state = CType(INDEXTOSTATEIMAGEMASK(iState), TVItemStates)
Return SetItem(tvi)
End Function
Private Function INDEXTOSTATEIMAGEMASK(ByVal iIndex As Int32) As Int32
Return iIndex << 12
End Function
Private Function SetItem(ByVal pitem As TVITEM) As Boolean
Return Not SendMessage(Me.Handle, TVM_SETITEM, 0, pitem).Equals(IntPtr.Zero)
End Function
Public Overridable Sub SetCheckIcon(ByVal Node As treeNode)
'Imposto la corrispondente immagine
If Not Node Is Nothing AndAlso Not Node.Handle.Equals(IntPtr.Zero) Then
SetItemStateImage(Node.Handle, CType(Node.CheckState, Int32))
End If
End Sub
Public Sub New()
MyBase.new()
'Sostituisco la hashtable standard con la versione modificata
Me.nodeTable = New EventHashTable(Me)
Me.InitializeComponent()
End Sub
Protected Overrides Sub OnInvalidated(ByVal e As System.Windows.Forms.InvalidateEventArgs)
MyBase.OnInvalidated(e)
'Qui é necessario impostare la treeview di sistema a quella personalizzata:
SendMessage(Me.Handle, TVM_SETIMAGELIST, TVM_GET_SETIMAGELIST_wParam.TVSIL_STATE, ChecksImageList.Handle)
End Sub
Protected Overrides Sub OnStyleChanged(ByVal e As System.EventArgs)
MyBase.OnStyleChanged(e)
'Questa Sub viene chiamata quando CheckBoxes diventa true,
'quindi devo impostarne i valori
RefreshChecks()
End Sub
'Rifletto esternamente la nodeTable friend della treeview
Private m_nodeTable As Hashtable
Public Property nodeTable() As Hashtable
Get
If m_nodeTable Is Nothing Then
Dim FI As System.Reflection.FieldInfo = GetType(System.Windows.Forms.TreeView).GetField("nodeTable", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
m_nodeTable = DirectCast(FI.GetValue(Me), Hashtable)
End If
Return m_nodeTable
End Get
Set(ByVal Value As Hashtable)
Dim FI As System.Reflection.FieldInfo = GetType(System.Windows.Forms.TreeView).GetField("nodeTable", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
FI.SetValue(Me, Value)
m_nodeTable = Value
End Set
End Property
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Select Case m.Msg
Case TVM_SETITEM
'Sta venendo impostato lo stato di un nodo, intercetto e controllo che sia quello giusto
If Not m.LParam.Equals(IntPtr.Zero) Then
Dim Info As TVITEM = DirectCast(System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, GetType(TVITEM)), TVITEM)
Dim Node As Object = nodeTable.Item(Info.hItem)
'Se ho trovato il nodo della hashtable ne imposto le proprietà,
'altrimenti verranno impostante quando verrà inserito
If Not Node Is Nothing AndAlso TypeOf Node Is treeNode Then
'Creo una copia della struttura di impostazione del nodo:
Dim tNode As treeNode = DirectCast(Node, treeNode)
Info.mask = TVItemMasks.TVIF_HANDLE Or TVItemMasks.TVIF_STATE
Info.stateMask = TVItemStates.TVIS_STATEIMAGEMASK
Info.state = CType(INDEXTOSTATEIMAGEMASK(CType(tNode.CheckState, Int32)), TVItemStates)
'sovrascrivo la precedente struttura:
System.Runtime.InteropServices.Marshal.StructureToPtr(Info, m.LParam, True)
End If
End If
End Select
MyBase.WndProc(m)
End Sub
Protected Overridable Sub RefreshChecks()
'Reimposta manualmente il valore delle checkbox per i nodi personalizzati
For Each D As DictionaryEntry In Me.nodeTable
If TypeOf D.Value Is treeNode Then
Me.SetCheckIcon(DirectCast(D.Value, treeNode))
End If
Next D
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
'La disposizione della ImageList la lascio a se stessa:
SendMessage(Me.Handle, TVM_SETIMAGELIST, TVM_GET_SETIMAGELIST_wParam.TVSIL_STATE, IntPtr.Zero)
End If
MyBase.Dispose(disposing)
End Sub
'Una hashtable personalizzata che reagisce all'inserimento di un nodo:
Protected Class EventHashTable
Inherits Hashtable
Private MyTree As Treeview
Public Sub New(ByVal Treeview As Treeview)
MyTree = Treeview
End Sub
Public Overrides Sub Add(ByVal key As Object, ByVal value As Object)
MyBase.Add(key, value)
Evaluate(value)
End Sub
Private Sub Evaluate(ByVal Value As Object)
If TypeOf Value Is treeNode Then
MyTree.SetCheckIcon(DirectCast(Value, treeNode))
End If
End Sub
Default Public Overrides Property Item(ByVal key As Object) As Object
Get
Return MyBase.Item(key)
End Get
Set(ByVal Value As Object)
MyBase.Item(key) = Value
Evaluate(Value)
End Set
End Property
End Class
#End Region
End Class