Windows Forms threestate treeview

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 IntegerByVal 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 IntegerByVal 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 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 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 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 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 ObjectByVal 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 ObjectAs 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

Print | posted on martedì 25 gennaio 2005 17:02

Comments have been closed on this topic.