Sunday, March 22, 2009

ThreeStateTreeView / TriStateTreeView in vb.Net

Stumble del.icio.us Reddit MyWeb! Facebook Google bookmark

Here is a three state treeview control in vb.Net. Not sure if it will cater to everyone's needs. But should do enough.



' Copyright statement for Shweta :)
Namespace Example.Winforms
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.VisualStyles


Public Class ThreeStateTreeView
Inherits TreeView

Private Const STATE_UNCHECKED As Integer = 0

'unchecked state
Private Const STATE_CHECKED As Integer = 1

'checked state
Private Const STATE_MIXED As Integer = 2

'mixed state (indeterminate)
Private _InternalStateImageList As ImageList

'state image list
'create a new ThreeStateTreeView
Public Sub New()
MyBase.New

End Sub

'initialize all nodes state image
Public Sub InitializeNodesState(ByVal nodes As TreeNodeCollection)
For Each node As TreeNode In nodes
node.StateImageIndex = 0
If (node.Nodes.Count <> 0) Then
InitializeNodesState(node.Nodes)
End If
Next
End Sub

'update children state image with the parent value
Public Sub UpdateChildren(ByVal parent As TreeNode)
Dim state As Integer = parent.StateImageIndex
For Each node As TreeNode In parent.Nodes
node.StateImageIndex = state
If (node.Nodes.Count <> 0) Then
UpdateChildren(node)
End If
Next
End Sub

'update parent state image base on the children state
Public Sub UpdateParent(ByVal child As TreeNode)
Dim parent As TreeNode = child.Parent
If (parent Is Nothing) Then
Return
End If
If (child.StateImageIndex = STATE_MIXED) Then
parent.StateImageIndex = STATE_MIXED
ElseIf IsChildrenChecked(parent) Then
parent.StateImageIndex = STATE_CHECKED
ElseIf IsChildrenUnchecked(parent) Then
parent.StateImageIndex = STATE_UNCHECKED
Else
parent.StateImageIndex = STATE_MIXED
End If
UpdateParent(parent)
End Sub

'returns a value indicating if all children are checked
Public Shared Function IsChildrenChecked(ByVal parent As TreeNode) As Boolean
Return IsAllChildrenSame(parent, STATE_CHECKED)
End Function

'returns a value indicating if all children are unchecked
Public Shared Function IsChildrenUnchecked(ByVal parent As TreeNode) As Boolean
Return IsAllChildrenSame(parent, STATE_UNCHECKED)
End Function

'returns a value indicating if all children are in the same state
Public Shared Function IsAllChildrenSame(ByVal parent As TreeNode, ByVal state As Integer) As Boolean
For Each node As TreeNode In parent.Nodes
If (node.StateImageIndex <> state) Then
Return false
End If
If ((node.Nodes.Count <> 0) _
AndAlso Not IsAllChildrenSame(node, state)) Then
Return false
End If
Next
Return true
End Function

'build the checked, unchecked and indeterminate images
Private Shared Function GetStateImage(ByVal state As CheckBoxState, ByVal imageSize As Size) As Image
Dim bmp As Bitmap = New Bitmap(16, 16)
Dim g As Graphics = Graphics.FromImage(bmp)
Dim pt As Point = New Point(((16 - imageSize.Width) _
/ 2), ((16 - imageSize.Height) _
/ 2))
CheckBoxRenderer.DrawCheckBox(g, pt, state)
Return bmp
End Function

Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
MyBase.OnHandleCreated(e)
If (_InternalStateImageList Is Nothing) Then
_InternalStateImageList = New ImageList
Dim g As Graphics = MyBase.CreateGraphics
Dim glyphSize As Size = CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.UncheckedNormal)
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.UncheckedNormal, glyphSize))
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.CheckedNormal, glyphSize))
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.MixedNormal, glyphSize))
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.UncheckedDisabled, glyphSize))
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.CheckedDisabled, glyphSize))
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.MixedDisabled, glyphSize))
End If
MyBase.StateImageList = _InternalStateImageList
InitializeNodesState(MyBase.Nodes)
End Sub

'check if user click on the state image
Protected Overrides Sub OnMouseClick(ByVal e As MouseEventArgs)
MyBase.OnMouseClick(e)
If (e.Button = MouseButtons.Left) Then
Dim info As TreeViewHitTestInfo = MyBase.HitTest(e.Location)
If ((Not (info.Node) Is Nothing) _
AndAlso (info.Location = TreeViewHitTestLocations.StateImage)) Then
Dim node As TreeNode = info.Node
Select Case (node.StateImageIndex)
Case STATE_UNCHECKED, STATE_MIXED
node.StateImageIndex = STATE_CHECKED
Case STATE_CHECKED
node.StateImageIndex = STATE_UNCHECKED
End Select
UpdateChildren(node)
UpdateParent(node)
End If
End If
End Sub

'check if user press the space key
Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
MyBase.OnKeyDown(e)
If (e.KeyCode = Keys.Space) Then
If (Not (MyBase.SelectedNode) Is Nothing) Then
Dim node As TreeNode = MyBase.SelectedNode
Select Case (node.StateImageIndex)
Case STATE_UNCHECKED, STATE_MIXED
node.StateImageIndex = STATE_CHECKED
Case STATE_CHECKED
node.StateImageIndex = STATE_UNCHECKED
End Select
UpdateChildren(node)
UpdateParent(node)
End If
End If
End Sub

'swap between enabled and disabled images
Protected Overrides Sub OnEnabledChanged(ByVal e As EventArgs)
MyBase.OnEnabledChanged(e)
Dim i As Integer = 0
Do While (i < 3)
Dim img As Image = _InternalStateImageList.Images(0)
_InternalStateImageList.Images.RemoveAt(0)
_InternalStateImageList.Images.Add(img)
i = (i + 1)
Loop
End Sub
End Class
End Namespace

2 comments:

  1. Excelente, gracias por el aporte, estuve buscando buen tiempo este codigo

    very good

    ReplyDelete
  2. Just came across this. Looks great and helps me understand what needs to happen. Thanks!!!

    However, I am a new to .Net and while I can create the control on my form using this class I am clueless as to adding the images and making this work.

    Guess I'll keep looking.

    ReplyDelete

Please leave your opinion...