自定义树视图 VB.net

Custom treeview VB.net

我想在 VB.net 中创建一个自定义树视图控件,我需要做的是有一个标准的树视图控件,例如显示系统文件结构,但在右边有一个额外的图标folder/file 仅在悬停在节点上时出现的名称。因此,例如,如果我将鼠标悬停在文件夹 Sup2 上,如图所示,则会出现橙色图标

我做了一些研究,据我所知,我必须重写 onpaint 事件才能实现这一点,但我不确定具体如何操作。我还需要向那个新的橙色图标添加一个 onclick 事件。

我想下面的例子会给你一些提示和技巧。

Option Explicit On

Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Runtime.InteropServices

Public Class TreeViewEx
    Inherits TreeView

#Region "API"

    Private Const TVM_SETEXTENDEDSTYLE As Integer = &H1100 + 44
    Private Const TVS_EX_DOUBLEBUFFER As Integer = &H4

    <DllImport("user32.dll")>
    Private Shared Function SendMessage(ByVal hWnd As IntPtr,
                                        ByVal msg As Integer,
                                        ByVal wp As IntPtr,
                                        ByVal lp As IntPtr) As IntPtr
    End Function

#End Region

#Region "Private Fields"

    Private ReadOnly RightImage As Bitmap
    Private ReadOnly NSF As StringFormat

    Private HoverNode As TreeNode
    Private RightImageRect As Rectangle

#End Region

#Region "Constructors"

    Sub New()
        DrawMode = TreeViewDrawMode.OwnerDrawText
        RightImage = New Bitmap(My.Resources.Modify)
        NSF = New StringFormat With {
            .Alignment = StringAlignment.Near,
            .LineAlignment = StringAlignment.Center,
            .Trimming = StringTrimming.EllipsisCharacter,
            .FormatFlags = StringFormatFlags.NoWrap
        }
    End Sub

#End Region

#Region "Paint"

    Protected Overrides Sub OnDrawNode(e As DrawTreeNodeEventArgs)
        MyBase.OnDrawNode(e)

        If e.Node Is Nothing Then Return

        Dim rect As Rectangle = e.Bounds : rect.Inflate(0, 1)

        If Not ClientRectangle.IntersectsWith(rect) Then
            Return
        End If

        Dim G As Graphics = e.Graphics

        G.SmoothingMode = SmoothingMode.HighQuality
        G.TextRenderingHint = TextRenderingHint.ClearTypeGridFit

        'Option1: If you want to draw different background color for the selected node.
        'If (e.State And TreeNodeStates.Selected) = TreeNodeStates.Selected Then
        '    Using br As New SolidBrush(Color.LightSteelBlue) '<- suit yourself!
        '        G.FillRectangle(br, rect)
        '    End Using
        'Else
        '    Using br As New SolidBrush(If(e.Node.BackColor.Equals(Color.Empty), BackColor, e.Node.BackColor))
        '        G.FillRectangle(br, rect)
        '    End Using
        'End If

        'Option2: If you don't want Option1.
        Using br As New SolidBrush(If(e.Node.BackColor.Equals(Color.Empty), BackColor, e.Node.BackColor))
            G.FillRectangle(br, rect)
        End Using

        Using br As New SolidBrush(If(e.Node.ForeColor.Equals(Color.Empty), ForeColor, e.Node.ForeColor))
            G.DrawString(e.Node.Text, If(e.Node.NodeFont, Font), br, rect, NSF)
        End Using

        If ReferenceEquals(e.Node, HoverNode) Then
            RightImageRect = New Rectangle(rect.Right + 5,
                                           rect.Y + ((rect.Height - RightImage.Height) / 2),
                                           rect.Height - 4, rect.Height - 4)
            G.DrawImage(RightImage,
                        RightImageRect,
                        New Rectangle(0, 0, RightImage.Width, RightImage.Height),
                        GraphicsUnit.Pixel)
        End If
    End Sub

#End Region

#Region "Other Events"

    'You need this to reduce the flickering.
    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
        SendMessage(
            Handle,
            TVM_SETEXTENDEDSTYLE,
            IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER),
            IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER)
            )
        MyBase.OnHandleCreated(e)
    End Sub

    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)

        Dim node = GetNodeAt(e.Location)

        If node IsNot Nothing Then
            'Avoid unnecessary Invalidate() calls.
            If Not ReferenceEquals(node, HoverNode) Then
                HoverNode = node
                Invalidate()
            End If
        Else
            'Avoid unnecessary Invalidate() calls.
            If HoverNode IsNot Nothing Then
                HoverNode = Nothing
                Invalidate()
            End If
        End If
    End Sub

    Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
        MyBase.OnMouseDown(e)

        If e.Button = MouseButtons.Left AndAlso
            RightImageRect.Contains(e.Location) Then
            'Notify the container to do something.
            OnEditButtonClicked()
        End If
    End Sub

    Protected Overrides Sub OnMouseLeave(e As EventArgs)
        MyBase.OnMouseLeave(e)
        Invalidate()
    End Sub

    Protected Overrides Sub Dispose(disposing As Boolean)
        MyBase.Dispose(disposing)
        If disposing Then
            RightImage.Dispose()
            NSF.Dispose()
        End If
    End Sub

#End Region

#Region "Custom Events"

    Public Class EditButtonClickArgs
        Inherits EventArgs

        Public Property Node As TreeNode

        Sub New(node As TreeNode)
            Me.Node = node
        End Sub
    End Class

    ''' <summary>
    ''' Raised when the right image is clicked.
    ''' </summary>
    Public Event EditButtonClicked As EventHandler(Of EditButtonClickArgs)

    ''' <summary>
    ''' Raises the <see cref="EditButtonClicked"/> events.
    ''' </summary>
    Protected Overridable Sub OnEditButtonClicked()
        RaiseEvent EditButtonClicked(Me, New EditButtonClickArgs(HoverNode))
    End Sub

#End Region

End Class

在包含新的 TreeViewEx 控件的 Form 中,您可以处理 EditButtonClicked 做必要的事情:

Public Class Form1
    Inherits Form

    Private Sub TreeViewEx1_EditButtonClicked(sender As Object, e As TreeViewEx.EditButtonClickArgs) Handles TreeViewEx1.EditButtonClicked
        'Do something with the e.Node
    End Sub

End Class

这是一个快速演示:

祝你好运。

这不是专业的,但值得一试..

我创建了一个继承自 Treeview 控件的 class 并覆盖了构造函数 - 更改默认宽度和高度,将 DrawMode 设置为 TreeViewDrawMode.OwnerDrawText

接下来,我处理了 TreeView.DrawNode 事件,使用 PictureBox 显示图像并根据当前突出显示的项目更改其位置。

我还处理了PictureBoxClick事件。在那个事件下,你可以对高亮节点做任何你想做的事情。

我将 My.Resources 中的图像用于 ImageList.Images(0)PictureBox.Image

Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim trv As New MyTreeView
        trv.Nodes.Add("Suppliers")
        trv.Nodes(0).Nodes.Add("Sup 1")
        trv.Nodes(0).Nodes.Add("Sup 2")
        trv.Nodes(0).Nodes.Add("Sup 3")
        trv.Nodes(0).Nodes.Add("Sup 4")
        trv.Nodes(0).Nodes.Add("Sup 5")
        Controls.Add(trv)
    End Sub
End Class

'Class Starts Here
Public Class MyTreeView
    Inherits TreeView
    WithEvents myImage As PictureBox
    Dim activeItem As TreeNode    'Variable to store active TreeNode
    Public Sub New()
        MyBase.New()              'Call the base class constructor
        'And set some values
        Height = 300
        Width = 300
        Location = New Point(50, 50)
        DrawMode = TreeViewDrawMode.OwnerDrawText       'Very neccesary
        AddHandler DrawNode, AddressOf MyTreeViewDrawNode   
        'Add event handlers
        AddHandler AfterCollapse, AddressOf MyTreeViewCollapsed
        'Set HotTracking event to true to allow for MouseHover
        HotTracking = True
        ImageList = new ImageList
        ImageList.Images.Add(My.Resources.FolderImage)
        ImageIndex = 0

        Font = New Font(Font.FontFamily, 10)
        'Initialize picturebox
        myImage = New PictureBox() With
        {
            .Image = My.Resources.editPencilImage,
            .SizeMode = PictureBoxSizeMode.Zoom,
            .Size = New Size(10, 10),
            .Visible = False
        }
        Controls.Add(myImage)
    End Sub

    Private Sub MyTreeViewCollapsed(sender As Object, e As TreeViewEventArgs)
        myImage.Visible = False
    End Sub

    Sub ImageClicked(sender As Object, e As EventArgs) Handles myImage.Click
        If (Not activeItem Is Nothing) Then
            MessageBox.Show("Clicked Item - " & activeItem.Text)
        End If
    End Sub

    Private Sub MyTreeViewDrawNode(sender As Object, e As DrawTreeNodeEventArgs)
        e.DrawDefault = True
        If (e.State = TreeNodeStates.Hot) Then
            myImage.Visible = True
            activeItem = e.Node
            Dim tmpSize = TextRenderer.MeasureText(e.Node.Text, Font)
            myImage.Location = New Point(e.Node.Bounds.Location.X + tmpSize.Width, e.Node.Bounds.Location.Y)
        End If
    End Sub
End Class