Die Community zu .NET und Classic VB.
Menü

Tipp-Upload: VB.NET 0361: ThreeStateTreeview

 von 

Über den Tipp  

Dieser Tippvorschlag wird übernommen.

Der Vorschlag ist in den folgenden Kategorien zu finden:

  • Algorithmen
  • Grafik
  • Steuerelemente

Dem Tippvorschlag wurden folgende Schlüsselwörter zugeordnet:
Checkstate,threestate,treeview

Der Vorschlag wurde erstellt am: 01.04.2009 05:16.
Die letzte Aktualisierung erfolgte am 22.03.2014 16:32.

Zurück zur Übersicht

Beschreibung  

Die (optionalen) Checkboxen der Treenodes unterstützen nur eine 2-wertige Logik. Dabei ist es das naheliegendste, einen Treenode auf Checkstate.Indeterminate zu setzen, wenn seine Children nicht einheitlich gecheckt sind.
Es gibt sogar eine StateImageList-Property der Treeview, mit der man die für die Checkboxen zu verwendenden Images angeben kann.
Allerdings gibt es auch den Bug, daß es keinerlei Auswirkung auf die Anzeige hat, wenn man den StateImageIndex eines Treenodes setzt.
Hier also eine Treeview, die ihre Checkboxen selber zeichnet.

Schwierigkeitsgrad

Schwierigkeitsgrad 2

Verwendete API-Aufrufe:

Download:

Download des Beispielprojektes [23,92 KB]

' Dieser Source stammt von http://www.activevb.de
' und kann frei verwendet werden. Für eventuelle Schäden
' wird nicht gehaftet.

' Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum.
' Ansonsten viel Spaß und Erfolg mit diesem Source!
'
' Beachten Sie, das vom Designer generierter Code hier ausgeblendet wird.
' In den Zip-Dateien ist er jedoch zu finden.

' ------- Anfang Projektgruppe ThreestateTreeview.sln  -------
' ------ Anfang Projektdatei ThreestateTreeview.vbproj  ------
' ---------- Anfang Datei frmThreestateTreeview.vb  ----------
Public Class frmThreestateTreeview

    Private Sub frmThreestateTreeview_Load(ByVal sender As Object, ByVal e As EventArgs) _
        Handles Me.Load

        TreeView1.ExpandAll()
        ThreeStateTreeview1.ExpandAll()

    End Sub

    Private Sub ThreeStateTreeview1_AfterCheckOrSelect(ByVal sender As Object, ByVal e As _
        TreeViewEventArgs) Handles ThreeStateTreeview1.AfterCheck, _
        ThreeStateTreeview1.AfterSelect

        Dim tv As ThreeStateTreeview = DirectCast(sender, ThreeStateTreeview)
        Dim nd As TreeNode = tv.SelectedNode

        If nd Is Nothing Then Return

        lbSelectedNode.Text = String.Concat(nd.Text, ": Checkstate.", tv.NodeCheckState(nd), _
            ", Checked=", nd.Checked)

    End Sub

    Private Sub MenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles _
        btCheckSelected.Click, btUnCheckSelected.Click

        Dim nd As TreeNode = ThreeStateTreeview1.SelectedNode

        If nd Is Nothing Then Return

        Select Case True

            Case sender Is btCheckSelected
                nd.Checked = True

            Case sender Is btUnCheckSelected
                nd.Checked = False

        End Select

    End Sub

End Class

' ----------- Ende Datei frmThreestateTreeview.vb  -----------
' ------------ Anfang Datei ThreeStateTreeview.vb ------------
Imports System.ComponentModel

''' <summary>
''' Treeview with 3-state Checkboxes.
''' Get the Treenodes 3-state Checkstate by calling ThreeStateTreeview.NodeCheckstate(Treenode)
''' </summary>
<DesignerCategory("Code")> Public Class ThreeStateTreeview

    Inherits TreeView

    Private _indeterminateds As New List(Of TreeNode)
    Private _graphics As Graphics
    Private _imgIndeterminate As Image
    Private _skipCheckEvents As Boolean = False

    Public Sub New()

        InitializeComponent()
        MyBase.DrawMode = TreeViewDrawMode.OwnerDrawAll
        MyBase.CheckBoxes = True
        _imgIndeterminate = ImageList1.Images(2)
        MyBase.StateImageList = ImageList1

    End Sub

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)

        If disposing AndAlso _graphics IsNot Nothing Then
            _graphics.Dispose()
            _graphics = Nothing

            If components IsNot Nothing Then components.Dispose()
        End If

        MyBase.Dispose(disposing)

    End Sub

    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)

        MyBase.OnHandleCreated(e)
        _graphics = Me.CreateGraphics

    End Sub

    Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)

        MyBase.OnSizeChanged(e)

        If _graphics IsNot Nothing Then
            _graphics.Dispose()
            _graphics = Me.CreateGraphics
        End If

    End Sub

    Protected Overrides Sub OnBeforeCheck(ByVal e As System.Windows.Forms.TreeViewCancelEventArgs)

        If _skipCheckEvents Then Return
        MyBase.OnBeforeCheck(e)

    End Sub

    Protected Overrides Sub OnAfterCheck(ByVal e As TreeViewEventArgs)

        ' Logic: All children of an (un)checked Node inherit its Checkstate
        ' Parents recompute their state: if all children of a parent have same state, that
        ' one will be taken over as parents state
        ' otherwise take Indeterminate
        ' changing any Treenodes .Checked-Property will raise another Before- and
        ' After-Check. Skip'em
        If _skipCheckEvents Then Return
        _skipCheckEvents = True

        Try

            Dim nd As TreeNode = e.Node

            ' newState is toggled from Checked to Unchecked, and from both Unchecked or
            ' Indeterminated to Checked
            Dim newState = If(NodeCheckState(nd) = CheckState.Checked, CheckState.Unchecked, _
                CheckState.Checked)

            If (newState = CheckState.Checked) <> nd.Checked Then Return ' suppress redundant event

            Dim newStateToChildNodes As Action(Of TreeNode) = Sub(nd0)

                AssignState(nd0, newState)

                For Each ndChild As TreeNode In nd0.Nodes
                    newStateToChildNodes(ndChild)
                Next

            End Sub

            newStateToChildNodes(nd) ' call recursive anonyme Sub

            ' Parents recompute their state
            nd = nd.Parent

            Do Until nd Is Nothing

                If newState <> CheckState.Indeterminate AndAlso nd.Nodes.Cast(Of _
                    TreeNode).Any(Function(nd2) NodeCheckState(nd2) <> newState) Then

                    newState = CheckState.Indeterminate
                End If

                AssignState(nd, newState)
                nd = nd.Parent
            Loop

            MyBase.OnAfterCheck(e)

        Finally

            _skipCheckEvents = False

        End Try

    End Sub

    Private Sub AssignState(ByVal nd As TreeNode, ByVal state As CheckState)

        Dim ck As Boolean = state = checkstate.Checked
        Dim stateInvalid As Boolean = NodeCheckState(nd) <> state

        If stateInvalid Then NodeCheckState(nd) = state
        If nd.Checked <> ck Then
            nd.Checked = ck ' changing .Checked-Property raises Invalidating internally

        ElseIf stateInvalid Then

            ' in general: the less and small the invalidated area, the less flickering
            ' so avoid calling Invalidate() if possible - just call, if really needed.
            Me.Invalidate(GetCheckRect(nd))
        End If

    End Sub

    Public Property NodeCheckState(ByVal nd As TreeNode) As System.Windows.Forms.CheckState
        Get
            Return DirectCast(Math.Max(0, nd.StateImageIndex), CheckState)

        End Get

        Private Set(value As System.Windows.Forms.CheckState)

            nd.StateImageIndex = value

        End Set

    End Property

    Protected Overrides Sub OnDrawNode(ByVal e As DrawTreeNodeEventArgs)

        ' here nothing is drawn. Only collect Indeterminated Nodes, to draw them later (in
        ' WndProc())
        ' because drawing Treenodes properly (Text, Icon(s) Focus, Selection...) is very complicated
        If e.Node.StateImageIndex = checkstate.Indeterminate Then _indeterminateds.Add(e.Node)
        e.DrawDefault = True
        MyBase.OnDrawNode(e)

    End Sub

    Protected Overrides Sub WndProc(ByRef m As Message)

        Const WM_LBUTTONDBLCLK As Integer = &H203

        If m.Msg = WM_LBUTTONDBLCLK Then

            ' fix the bug, when doubleclick on a StateImage
            Dim pt As Point = Me.PointToClient(Control.MousePosition)

            If Me.HitTest(pt).Location = TreeViewHitTestLocations.StateImage Then Return
        End If

        MyBase.WndProc(m)
        Const WM_Paint As Integer = 15

        If m.Msg = WM_Paint Then

            ' at that point the built-in drawing is completed - and I quickly paint over the
            ' indeterminated Checkboxes
            For Each nd As TreeNode In _indeterminateds
                _graphics.DrawImage(_imgIndeterminate, GetCheckRect(nd).Location)
            Next

            _indeterminateds.Clear()
        End If

    End Sub

    Private Function GetCheckRect(ByVal nd As TreeNode) As Rectangle

        With nd.Bounds

            If Me.ImageList Is Nothing Then
                Return New Rectangle(.X - 16, .Y, 16, 16)

            Else

                Return New Rectangle(.X - 35, .Y, 16, 16)
            End If

        End With

    End Function

    ' since ThreeStateTreeview comes along with its own StateImageList, prevent assigning
    ' the StateImageList- Property from outside. Shadow and re-attribute original property
    <EditorBrowsable(EditorBrowsableState.Never)> <DesignerSerializationVisibility( _
        DesignerSerializationVisibility.Hidden)> Public Shadows Property StateImageList() As _
        ImageList

        Get
            Return MyBase.StateImageList

        End Get

        Set(ByVal value As ImageList)

        End Set

    End Property

#Region "Designergenerated"

    Friend WithEvents ImageList1 As ImageList

    Private components As IContainer

    Private Sub InitializeComponent()

        Me.components = New Container

        Dim resources As ComponentResourceManager = New ComponentResourceManager(GetType( _
            ThreeStateTreeview))

        Me.ImageList1 = New ImageList(Me.components)
        Me.SuspendLayout()

        '
        ' ImageList1
        '
        Me.ImageList1.ImageStream = CType(resources.GetObject("ImageList1.ImageStream"), _
            ImageListStreamer)

        Me.ImageList1.TransparentColor = Color.Transparent
        Me.ImageList1.Images.SetKeyName(0, "UnChecked.ico")
        Me.ImageList1.Images.SetKeyName(1, "Checked.ico")
        Me.ImageList1.Images.SetKeyName(2, "Indeterminated.ico")
        Me.ResumeLayout(False)

    End Sub

#End Region ' Designergenerated

End Class

' ------------- Ende Datei ThreeStateTreeview.vb -------------
' ------- Ende Projektdatei ThreestateTreeview.vbproj  -------
' -------- Ende Projektgruppe ThreestateTreeview.sln  --------

	

Diskussion  

Diese Funktion ermöglicht es, Fragen, die die Veröffentlichung des Tipps betreffen, zu klären, oder Anregungen und Verbesserungsvorschläge einzubringen. Nach der Veröffentlichung des Tipps werden diese Beiträge nicht weiter verlinkt. Allgemeine Fragen zum Inhalt sollten daher hier nicht geklärt werden.

Um eine Diskussion eröffnen zu können, müssen sie angemeldet sein.