OK. This is my first attempt at a custom control. I still consider myself a beginner, but I have to say, not too shabby for a first attempt.
The Control uses version 3.5 of the .NET Framework, and the project file attachments are in vs2008. All Compiled elements have been removed.
I have tried to re-create the basic look and functionality of the vs2008 Toolbox control, with reasonable success. The control inherits from TreeView, and can be used in much the same way as you would use a standard treeview control:
1. The GroupedTreeView “Headers” are simply the Level 0 TreeNodes. When you populate this control, the first level of the node hierarchy is this.
2. There is only one lower node level (Level 1). I did this on purpose, so that you get “headers” and “Items”.
3. The GroupedTreeView.Imagelist is used to associate images/Icons with items at Level 1. Attempting to associate an image with a Level 0 node produces no change.
4. The nodes for the control (Level 0 AND level 1) are entirely owner-drawn.
5. There is a Class Called “GroupedTreeViewColorScheme” which defines pre-determined color schemes for the control. Due to the use of gradients, and the complexity of the TreeView Base Class, I decided it would be best this way. There are probably better ways to do it.
Events and User interaction
The Event handling for this was tricky, and I am hoping someone out there can find a better way to handle this. Apparently, the TreeNode Class responds differently depending on WHERE in the node area a mouseclick occurs. When Clicked over the area that hosts the +/– Symbol, the node expands/collapses, but does not select. When clicked in the area which hosts the Node Text, the node is selected, and expands/Collapses. When a click occurs in the area to the right of the node bounds (Where selection will occur if the FullRowSelect property is set to True), the node registers a double MousenodeClick Event, followed by double toggling of the control. In this case, the end result is no change in the expanded/collapsed status of the node (presumable to make room for the Fullrow select property, allowing users of TreeView to select a node from anywhere in this region without (visible) toggling).
Unfortunately, I am after a control which models the behavior of the vs Toolbox, which reponds to a click ANYWHERE in the header area by selecting the header (if not selected), and toggling the Header Node. I achieved this, but at a price. There is now a sporadic flicker when one clicks on a selected Header node (most notable during the collapse operation). Not enough to junk the control, but it is annoying.
I have not found a solution for this, and I have tried MANY different combinations of events and such. I suspect one probably needs to explore handling Windows messages at this point. If anyone has any ideas, or can improve on this, I would LOVE to see what you come up with!
Drawing the Headers
I did NOT get all of the detail found in the vs Toolbox Header into this version of the control. It looks pretty good, but this began as an effort to learn my way through control construction. Also, I wanted one of these for my App!!
I don’t know if I have explained any of this well enough, or if I have tried to explain too much. This is my first codebank posting. I am also going to post it in “Code it better” forum, because I am calling this a finished effort for now, but would LOVE to hear where I could improve, and what could be done better.
I have attached a vs Project file, and a folder with some images in it. The project file contains three classes and a simple WinForm to demo the control. The Project file has all necessary images set in My.Resources, but I included the stand-alone images for folks to work with.
I have also attached another vs project file which is the same, except this one is set up as a Class Library project. Since I removed all the compiled elements from the file (The bin and obj folders, per jmcilhinney’s suggestion), you can build the class library version, and play with the control by adding it to your toolbox.
Again, would love feedback, criticisms, comments, and most of all, pointers on how I SHOULD do it!
I will follow this post with the code, which may take several more posts in a row.
Thanks everyone!
Last edited by RunsWithScissors; Sep 18th, 2009 at 09:37 PM.
Here is part 1 of the Code for the GroupedTreeViewClass:
Code:
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms
Public Class GroupedTreeView
Inherits TreeView
#Region "Declarations"
'ColorScheme Members:
'The ColorScheme Class is a custom object created to handle the
'Semi-complex color properties of the control.
Private moColorScheme As GroupedTreeViewColorScheme
Private mlColorSchemeIndex As GroupedTreeViewColorSchemeIndex = 1
'Header Node Colors:
Private mColorHeaderGradientDark As Color = Color.FromKnownColor(KnownColor.ButtonShadow)
Private mColorHeaderGradientLight As Color = Color.FromKnownColor(KnownColor.ControlLight)
Private mColorHeaderSelectedBackGround As Color = Color.FromKnownColor(KnownColor.InactiveCaptionText)
Private mColorHeaderSelectedBorder As Color = Color.FromKnownColor(KnownColor.Highlight)
Private mColorHeaderStandardBorder As Color = Color.SlateGray
Private mColorNodeSelectedColor As Color = Color.FromKnownColor(KnownColor.InactiveCaptionText)
'Header Node Text Colors:
Private mColorHeaderStandardText As Color = Color.Black
Private mColorHeaderSelectedText As Color = Color.Black
'Sub Node Colors:
Private mColorSubNodeStandardBackGround As Color = Color.FromKnownColor(KnownColor.Control)
Private mColorSubNodeMouseOverBackGround As Color = Me.BackColor 'Not Implemented
Private mColorSubNodeSelectedBackGround As Color = Color.FromKnownColor(KnownColor.InactiveCaptionText)
Private mColorSubNodeSelectedBorder As Color = Color.FromKnownColor(KnownColor.Highlight)
'Sub-Node Text Brushes:
Private mColorSubNodeStandardText As Color = Color.Black
Private mColorSubNodeSelectedText As Color = Color.Black
'TreeView Colors:
Protected mTreeViewBackColor As Color = Color.FromKnownColor(KnownColor.Control)
Protected mTreeViewForeColor As Color = Color.FromKnownColor(KnownColor.Black)
Protected mTreeViewBorderColor As Color = Color.FromKnownColor(KnownColor.SlateGray)
Private fntHeaderFont As Font
Private fntSubNodeFont As Font
'I had to make my own +/- images, which are in My.Resources. For MY project,
'I have the images compiled into the resx. I have included the images
'externally as well, in the file "Image Resources". They are PNG Files . . .
Private mExpandMeImage As Image = My.Resources.ListExpandPNG9x9_DK
Private mCollapseMeImage As Image = My.Resources.ListCollapsePNG9x9_DK
#Region "About Variable mNode and Tree Events"
'mNode is used to keep track of the selected/Expanded
'status of a node; this is necessary because, depanding upon WHERE
'a mouseclick "lands", a Traditional TreeNode is designed
'to respond differently. In a normal treeview, a click which occurs
'over the area which normally contains the +/- icon simply expands/collapses
'the node, without selecting it. If the Node itself (the area containing the NodeText)
'is clicked, the node will be selected but not expanded/collapsed. And IF a
'traditional node is in FullRowSelect mode, a click in the area to the RIGHT of
'the Node Text causes the node to be selected but not expanded collapsed.
'In order to model the vs Toolbox, I wanted a click ANYWHERE within the Drawn boundary
'of a HEADER node to cause both selection, and toggling of the node. The "Drawn Boundary"
'DOES NOT CORRESPOND to the Node.Bounds defined by the TreeNode, so a bunch of
'Event handling B.S. became necessary to manage the interaction between these
'scenarios.
'Making this particular peice of the control work "properly" causes some
'minor, but irritating flicker.
'IF ANYONE CAN FIND A BETTER WAY (especially one that gets rid of the flicker),
'PLEASE PM ME and/or post the code!!!
#End Region 'About Variable mNode and Tree Events
Private mNode As TreeNode
'If this value is set to True via the Public Property
'AutoCollapse, HeaderNode Selection will automatically collapse
'All other expanded nodes:
Private mbooAutoCollapse As Boolean = False
#End Region 'Declarations
#Region "Constructor/Initialization"
Public Sub New()
With Me
.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
.SetStyle(ControlStyles.DoubleBuffer, True)
.SetStyle(ControlStyles.ResizeRedraw, True)
'Gotta be in OwnerDraw Mode to do this:
.DrawMode = TreeViewDrawMode.OwnerDrawAll
'Some default Property Settings conducive to the "ToolBox" Look:
.ItemHeight = 20
.FullRowSelect = True
.Scrollable = True
'Initialize the TreeView Imagelist from Jump,
'so we can specify 32 bit color depth at the outset:
.ImageList = New ImageList
.ImageList.ColorDepth = ColorDepth.Depth32Bit
End With
End Sub
Private Sub GroupedTreeView_DrawNode(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawTreeNodeEventArgs) Handles Me.DrawNode
Dim nd As TreeNode = e.Node
Me.SuspendLayout()
'Determine the level of the node. ROOT-level nodes
'are HEADER Nodes, and will be drawn differently
'than Sub-Level nodes:
If nd.Level = 0 Then
'This is a ROOT-LEVEL Node:
DrawHeader(e)
ElseIf nd.Level = 1 Then
'This is a SUB-NODE:
DrawSubNode(e)
Else
'For the Moment, I am only allowing the Top level
'"Header" nodes, and one level of Sub-node "Items":
Throw New ApplicationException("The GroupedTreeView Control can only host two levels of TreeNodes.")
End If
Me.ResumeLayout(False)
End Sub
#End Region 'Constructor/initialization
Part 2 of the Code (The "DrawHeader" Method from the GroupedTreeView Class):
Code:
#Region "Draw Header Nodes"
Protected Sub DrawHeader(ByVal e As DrawTreeNodeEventArgs)
'ALL of this can propbably be done better. Also, I have simplified
'the control's Headers, and the aren't as detailed as the vs Toolbox.
'After I refine the mechanics of the control (and LOSE THE FLICKER!)
'I may or may not revisit this . . .
Dim nd As TreeNode = e.Node
Dim gfx As Graphics = e.Graphics
'The Text Tto display in the Node:
Dim strText As String = nd.Text
'Establish the Node Location from the Object Bounds
'Received as a Parameter:
Dim pt As Point = nd.Bounds.Location
pt.X = 0
'Adjust the Width to bring the far left
'edge into the bounds of the TreeView Control:
Dim sz As Size = nd.Bounds.Size
'The Header Width needs to be adjusted if the TreeView
'ScrollBar is visible:
sz.Width = Me.ClientRectangle.Width - 1
'Create a rectangle from the location and Size
'of the node Bounds:
Dim NodeRect As New RectangleF(pt, sz)
'Derive a Graphics Path from the Rectangle:
Using NodePath As New Drawing2D.GraphicsPath
NodePath.AddRectangle(NodeRect)
'Is the HeaderNode Selected?
If nd.IsSelected Then
'Define a path to draw a border around the Selected Header:
Using BorderPath As New Drawing2D.GraphicsPath
'Shrink the border just a little:
NodeRect.Inflate(-2, -1)
BorderPath.AddRectangle(NodeRect)
Using br As New Drawing2D.LinearGradientBrush(NodeRect, mColorHeaderSelectedBackGround, mColorHeaderSelectedBackGround, Drawing2D.LinearGradientMode.Vertical)
e.Graphics.FillPath(br, BorderPath)
End Using
Dim pn As New Drawing.Pen(mColorHeaderSelectedBorder)
e.Graphics.DrawPath(pn, BorderPath)
End Using
'Use the local Function TextDrawLocation to locate the starting point
'for the text drawing operation:
Dim TextLoc As Point = TextDrawLocation(nd.Bounds.Location, Me.HeaderFont)
Using brText As New SolidBrush(mColorHeaderSelectedText)
e.Graphics.DrawString(strText, Me.HeaderFont, brText, TextLoc.X, TextLoc.Y)
End Using
Else
'FIll with a vertical gradient:
Using br As New Drawing2D.LinearGradientBrush(NodeRect, mColorHeaderGradientLight, mColorHeaderGradientDark, Drawing2D.LinearGradientMode.Vertical)
e.Graphics.FillPath(br, NodePath)
End Using
'Define a pen to draw a border around the Selected Header:
Dim pn As New Drawing.Pen(mColorHeaderGradientDark)
pn.Alignment = Drawing2D.PenAlignment.Inset
'Define a path to draw a border around the Selected HeaderNode:
Using BorderPath As New Drawing2D.GraphicsPath
'Shrink the border just a little:
NodeRect.Inflate(0, -1)
NodePath.AddRectangle(NodeRect)
e.Graphics.DrawPath(pn, BorderPath)
End Using
'Use the local Function TextDrawLocation to locate the starting point
'for the text drawing operation:
Dim TextLoc As Point = TextDrawLocation(nd.Bounds.Location, Me.HeaderFont)
Using brText As New SolidBrush(mColorHeaderStandardText)
e.Graphics.DrawString(strText, Me.HeaderFont, brText, TextLoc.X, TextLoc.Y)
End Using
End If
End Using
'DRAWING THE EXPANDED/COLLAPSED IMAGES (the +/- Symbols)
'Is The node Expanded?
Dim ExpandedImage As Image
If nd.IsExpanded Then
ExpandedImage = mCollapseMeImage
Else
ExpandedImage = mExpandMeImage
End If
'Draw the Expander Icon. Use local function Expander location to
'locate the upper left-hand corner of the image:
Dim imgLoc As Point = ExpanderLocation(nd.Bounds.Location, ExpandedImage.Size)
e.Graphics.DrawImage(ExpandedImage, imgLoc)
End Sub
#End Region 'Draw Header Nodes
Part 3 of Code for The GroupedTreeView Class(The "DrawSubNodes" Method)
Code:
#Region "Draw Sub-Nodes"
Protected Sub DrawSubNode(ByVal e As DrawTreeNodeEventArgs)
Dim nd As TreeNode = e.Node
Dim gfx As Graphics = e.Graphics
'The Text Tto display in the Node:
Dim strText As String = nd.Text
'Establish the Node Location from the Object Bounds
'Received as a Parameter:
Dim pt As Point = nd.Bounds.Location
pt.X = 0
'Adjust the Width to bring the far left
'edge into the bounds of the TreeView Control:
Dim sz As Size = nd.Bounds.Size
'The Selected Outline of the SubNode needs to be adjusted
'if the TreeView ScrollBar is visible, so use the Client
'Rectangle of the The TreeView Control as the Starting point:
sz.Width = Me.ClientRectangle.Width - 2
'Create a rectangle from the location and Size
'of the node Bounds:
Dim NodeRect As New RectangleF(pt, sz)
'Derive a Graphics Path from the Rectangle:
If Not NodeRect.IsEmpty Then
'Fine-Tune the alignments of the fill region with the Border:
Using NodePath As New Drawing2D.GraphicsPath
NodeRect.Y = NodeRect.Y + 1
NodeRect.Height = NodeRect.Height - 1
NodePath.AddRectangle(NodeRect)
'Is the SubNodeNode Selected?
If nd.IsSelected Then
'Fill with a Uniform gradient:
Using br As New Drawing2D.LinearGradientBrush(NodeRect, mColorSubNodeSelectedBackGround, mColorSubNodeSelectedBackGround, Drawing2D.LinearGradientMode.Vertical)
e.Graphics.FillPath(br, NodePath)
End Using
'Define a path to draw a border around the Selected SubNode:
Using BorderPath As New Drawing2D.GraphicsPath
'Shrink the border just a little:
NodeRect.Inflate(-0, -1)
'And move it slightly Down to Accomodate the Shrinkage:
NodeRect.Y = NodeRect.Y - 1
'Then adjust the height again for perfect positioning:
NodeRect.Height = NodeRect.Height + 1
BorderPath.AddRectangle(NodeRect)
Dim pn As New Drawing.Pen(mColorSubNodeSelectedBorder)
pn.Alignment = Drawing2D.PenAlignment.Inset
e.Graphics.DrawPath(pn, BorderPath)
End Using
'Use the local Function TextDrawLocation to locate the starting point
'for the text drawing operation:
Dim TextLoc As Point = TextDrawLocation(nd.Bounds.Location, Me.SubNodeFont)
Using brText As New SolidBrush(mColorHeaderSelectedText)
e.Graphics.DrawString(strText, Me.SubNodeFont, brText, TextLoc.X, TextLoc.Y)
End Using
Else
'FIll with a vertical gradient:
Using br As New Drawing2D.LinearGradientBrush(NodeRect, mColorSubNodeStandardBackGround, mColorSubNodeStandardBackGround, Drawing2D.LinearGradientMode.Vertical)
e.Graphics.FillPath(br, NodePath)
End Using
'Use the local Function TextDrawLocation to locate the starting point
'for the text drawing operation:
Dim TextLoc As Point = TextDrawLocation(nd.Bounds.Location, Me.SubNodeFont)
Using brText As New SolidBrush(mColorSubNodeStandardText)
e.Graphics.DrawString(strText, Me.SubNodeFont, brText, TextLoc.X, TextLoc.Y)
End Using
End If
End Using
'IMAGES FROM NATIVE IMAGE LIST:
'Does the node have an Image Associated with it in the native image list?
Dim str As String = nd.ImageKey
Dim iIndex As Integer = nd.ImageIndex
'Was a valid Image Key or index provided?
If Not [String].IsNullOrEmpty(str) Then
If Not Me.ImageList Is Nothing AndAlso Me.ImageList.Images.Count > 0 Then
Try
Dim imgListImage As Image = Me.ImageList.Images(nd.ImageKey)
'Draw the Node Icon. Use local function Expander location to
'locate the upper left-hand corner of the image:
Dim imgLoc As Point = ExpanderLocation(nd.Bounds.Location, imgListImage.Size)
e.Graphics.DrawImage(imgListImage, imgLoc)
Catch ex As Exception
Throw New ApplicationException("" & ex.Source & " - " & ex.Message & " - " & My.Resources.ImageList_IndexNotValid)
End Try
End If
Else
'If the key was invalid, test for the index:
If iIndex >= 0 Then
If Not Me.ImageList Is Nothing AndAlso Me.ImageList.Images.Count > 0 Then
Try
Dim imgListImage As Image = Me.ImageList.Images(nd.ImageIndex)
'Draw the Node Icon. Use local function Expander location to
'locate the upper left-hand corner of the image:
Dim imgLoc As Point = ExpanderLocation(nd.Bounds.Location, imgListImage.Size)
e.Graphics.DrawImage(imgListImage, imgLoc)
Catch ex As Exception
Throw New ApplicationException("" & ex.Source & " - " & ex.Message & " - " & My.Resources.ImageList_IndexNotValid)
End Try
End If
End If
End If
End If
End Sub
#End Region 'Draw Sub-Nodes
Part 4 of the code for GroupedTreeView (Image and Text positioning functions):
Code:
#Region "Image/Text Positioning Functions"
''' <summary>
''' Returns an optimal location for the Expander (+/-) Image
''' based upon the size of the image and the height of the
''' Header Node.
''' </summary>
''' <param name="NodeLocation"></param>
''' <param name="ImageSize"></param>
''' <returns></returns>
''' <remarks></remarks>
Protected Overridable Function ExpanderLocation(ByVal NodeLocation As Point, ByVal ImageSize As Size) As Point
Dim iTop As Integer
Dim iLeft As Integer
Dim dTop As Decimal
Dim dLeft As Decimal
'Err on the side of placemnent to the top:
dTop = 0.95 * (Me.ItemHeight - ImageSize.Height) / 2
iTop = NodeLocation.Y + Math.Truncate(dTop)
'Inset the Expansion Selector Image to the left 1.5 times
'the distance from the Top:
dLeft = 1.5 * (Me.ItemHeight - ImageSize.Height) / 2
iLeft = Math.Truncate(dLeft)
'Return a point structure:
Dim pt As Point
pt.X = iLeft
pt.Y = iTop
Return pt
End Function
''' <summary>
''' Returns an optimal location for the Header Node Text
''' based upon the size of the image and the height of the
''' Header Node.
''' </summary>
''' <param name="NodeLocation"></param>
''' <param name="Font"></param>
''' <returns></returns>
''' <remarks></remarks>
Protected Function TextDrawLocation(ByVal NodeLocation As Point, ByVal Font As Font) As Point
Dim iTop As Integer
Dim iLeft As Integer
Dim d As Decimal
'Find a decimal value that is 90% of the difference
'between the height of the header and the height of
'the display font:
d = (Me.ItemHeight - Font.Height) / 2
'Truncate the value to an integer:
iTop = NodeLocation.Y + Math.Truncate(d)
'Inset the text proportionately to the
'height of the Header:
iLeft = Me.ItemHeight + 5
'Return a Point Structure:
Dim pt As Point
pt.X = iLeft
pt.Y = iTop
Return pt
End Function
#End Region 'Image/Text Positioning Functions
Part 5 of the code for GroupedTreeView (NodeExpand/Collapse Events)
Code:
#Region "Node Expand, Contract Events"
Private Sub GroupedTreeView_AfterCollapse(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles Me.AfterCollapse
'If a node has been newly collapsed, it has also just been clicked.
'Set the mNode reference to nothing so that if this node is clicked
'again, the MouseClick or NodeMouseClick Events will Toggle the node:
mNode = Nothing
End Sub
Private Sub GroupedTreeView_AfterExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles Me.AfterExpand
'If a node has been newly expanded, it has also just been clicked.
'Set the mNode reference to nothing so that if this node is clicked
'again, the the MouseClick or NodeMouseClick Events will Toggle the node:
'If the AutoCollapse Property is True, Collapse all HeaderNodes except the
'currently selected one:
If mbooAutoCollapse Then
For Each nd As TreeNode In Me.Nodes
'Only worry about Level 0 Nodes:
If nd.Level = 0 Then
If Not ReferenceEquals(nd, e.Node) Then
nd.Collapse()
End If
End If
Next
End If
mNode = Nothing
End Sub
Private Sub GroupedTreeView_BeforeSelect(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) Handles Me.BeforeSelect
'Set mNode to nothing so there will be no
'equivelent reference to a newly selected node:
mNode = Nothing
End Sub
#End Region ' Node Expand, Contract Events
Part 6 of code for GroupedTreeView Control (Mouse Events)
This is where the thing got messy, and where the flicker is happening:
Code:
#Region "Mouse Events"
Private Sub GroupedTreeView_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick
Dim pt As Point = e.Location
Me.SuspendLayout()
For Each nd As TreeNode In Me.Nodes
'A. NODE BOUNDARIES FOR CLICK EVENTS:
'The Node bounds are NOT the same size as the area we are drawing the node into!
'Therefore, we need to treat certain areas of the drawn Node differently, since
'the Tree Control is designed to handle MouseCLick Events within the "Real" Node
'Bounds differently than the area we have Owner-Drawn. ADDITIONALLY, the TreeView
'Control we are inheriting from is set up to handle clicks in the area usually
'occupied by the expander (+/-) icon separately as well. We have to handle all
'of these cases and make it seem as if the entire Node is responding uniformly
'to a Mouse-Click event.
'NOTE: I used the MouseDown Event because the TreeView seems to only register
'Mouse CLICK events in areas NOT occupied by a node.
'B. CLICK EVENTS AND TRACKING NODE STATUS:
'For OwnerDrawn areas that are NOT contained withing the Control-defined
'Node Bounds, the click event fires TWICE (Presumably to handle Clicks
'in FullRowSelect mode). The node is expanded and collapsed. The Module
'variable mNode is used to keep track of a node's status through this process.
'Handle MouseClicks in the OwnerDrawn area that are NOT contained within the
'actual Node defined by the control:
Dim rect As New Rectangle(nd.Bounds.Left, nd.Bounds.Y, Me.Width, nd.Bounds.Height)
If rect.Contains(pt) Then
'EXCLUDE the area bounded by the real Node:
If Not nd.Bounds.Contains(pt) Then
'Compare the references held by mNode and nd: if they are
'equivelent, then this is the second time the MouseClick Event
'has fired in response to the initial user Click. Don't Toggle the control:
If Not ReferenceEquals(nd, mNode) Then
'Toggle the control, and then set mNode equal to
'the Clicked Node, so that the next pass will find the two
'equivelent and will not re-toggle the control:
nd.Toggle()
mNode = nd
Else
'Since the Node was clicked in order to fire the
'Click event and pass a valid node in, the node
'must be SELECTED. If it was NEWLY Selected, it
'was toggled during the first pass BEFORE it's selected status was
'changed to SELECTED, and mNode was set to reference it. It was skipped for
'toggling on the second pass, so now set mNode to Nothing. Now, if
'it is Clicked again in it's Selected state, it will succeed at causing the
'toggle again:
If nd.IsSelected Then
mNode = Nothing
End If
End If
End If
End If
Next
Me.ResumeLayout(False)
End Sub
Private Sub GroupedTreeView_NodeMouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeNodeMouseClickEventArgs) Handles Me.NodeMouseClick
Me.SelectedNode = e.Node
Dim pt As Point = e.Location
Dim nd As TreeNode = e.Node
'Handle MouseClicks to the area CONTAINED WITHIN the Control-defined Node Bounds.
'CLicks to this region do NOT double-fire the Click event, so no special handling is
'required. Simple toggle the control:
'The NodeMouseCLick still fires for Clicks Outside the Control-Defined area. It Double-Fires
'in these cases, and these are handled by the TreControl_MouseCLick Event. HERE, Exclude
'Any Click not contained within the control-defined node region:
'Only ROOT Nodes are Expandable:
If e.Node.Level = 0 Then
Dim rect As New Rectangle(nd.Bounds.Height, nd.Bounds.Top, nd.Bounds.Width + nd.Bounds.Height, nd.Bounds.Height)
If rect.Contains(pt) Then
nd.Toggle()
End If
End If
End Sub
#End Region 'Mouse Events
#Region "Property Settings"
''' <summary>
''' This Overload turned out not to be needed, but I left it anyway . . .
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overloads Property ItemHeight()
Get
Return MyBase.ItemHeight
End Get
Set(ByVal value)
MyBase.ItemHeight = value
End Set
End Property
''' <summary>
''' Gets or Sets the Font used to display the text
''' in Header (Level(0)) Nodes.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property HeaderFont() As Font
Get
'If a font has not been set by the client,
'initialize the default:
If fntHeaderFont Is Nothing Then
fntHeaderFont = New Font("Tahoma", 8, FontStyle.Bold, GraphicsUnit.Point)
End If
Return fntHeaderFont
End Get
Set(ByVal value As Font)
fntHeaderFont = value
End Set
End Property
''' <summary>
''' Gets or Sets the Font used to display the text
''' in Sub-Nodes (Level(1)).
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property SubNodeFont() As Font
Get
If fntSubNodeFont Is Nothing Then
fntSubNodeFont = New Font("Tahoma", 8, FontStyle.Regular, GraphicsUnit.Point)
End If
Return fntSubNodeFont
End Get
Set(ByVal value As Font)
fntSubNodeFont = value
End Set
End Property
''' <summary>
''' Gets or Sets the NodeAutoCollapse Property, which
''' determines if other nodes are collapsed after selection
''' of a new node.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property NodeAutoCollapse() As Boolean
Get
Return mbooAutoCollapse
End Get
Set(ByVal value As Boolean)
mbooAutoCollapse = value
End Set
End Property
#End Region 'Property Settings
Part 7 code for GroupedTreeView Control (Colorscheme)
Code:
#Region "ColorScheme"
''' <summary>
''' Colorscheme index parameter indicates which of several
''' pre-defined colorschemes to use in drawing the control.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ColorSchemeIndex() As GroupedTreeViewColorSchemeIndex
Get
Return mlColorSchemeIndex
End Get
Set(ByVal value As GroupedTreeViewColorSchemeIndex)
If value > 0 Then
mlColorSchemeIndex = value
moColorScheme = New GroupedTreeViewColorScheme(value)
UpdateColorScheme()
End If
End Set
End Property
''' <summary>
''' Gets or Sets color scheme settings for the
''' various control elements, based on the value of
''' the ColorSchemeIndex Property.
''' </summary>
''' <remarks></remarks>
Protected Sub UpdateColorScheme()
'Due to the complexity of the paint operations for this control,
'and the use of gradients in painting the headers, I decided to
'use pre-defined color combinations.
'Additional colorschemes can be defined in the Class GroupedTreeViewCOlorScheme.
'Header Colors:
mColorHeaderGradientDark = moColorScheme.ColorHeaderGradientDark
mColorHeaderGradientLight = moColorScheme.ColorHeaderGradientLight
mColorHeaderSelectedBackGround = moColorScheme.ColorSelectedBackGround
mColorHeaderSelectedBorder = moColorScheme.ColorHeaderSelectedBorder
mColorHeaderStandardBorder = moColorScheme.ColorHeaderStandardBorder
mColorHeaderStandardText = moColorScheme.ColorHeaderStandardText
mColorHeaderSelectedText = moColorScheme.ColorHeaderSelectedText
'SubNode Colors:
mColorSubNodeStandardBackGround = moColorScheme.SubNodeStandardBackGround
mColorSubNodeMouseOverBackGround = moColorScheme.SubNodeMouseOverBackGround
mColorSubNodeSelectedBackGround = moColorScheme.SubNodeSelectedBackGround
mColorSubNodeSelectedBorder = moColorScheme.ColorHeaderSelectedBorder
'Colors for Drawing the SubNode Text:
mColorSubNodeSelectedText = moColorScheme.SubNodeSelectedText
mColorSubNodeStandardText = moColorScheme.SubNodeStandardText
'TreeView Colors:
mTreeViewBackColor = moColorScheme.TreeViewBackColor
mTreeViewBorderColor = moColorScheme.TreeViewBorderColor
'Reset the value of the native control BackColor:
Me.BackColor = mTreeViewBackColor
'Refresh the entire object to reflect the new colorscheme:
Me.Refresh()
End Sub
#End Region 'Color Scheme
Part 8 code for Grouped TreeView Control (Dispose)
Code:
#Region "Dispose"
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso moColorScheme IsNot Nothing Then
moColorScheme.Dispose()
End If
Catch ex As System.Exception
MsgBox(ex.Message)
End Try
If disposing AndAlso fntHeaderFont IsNot Nothing Then
fntHeaderFont.Dispose()
End If
If disposing AndAlso fntSubNodeFont IsNot Nothing Then
fntSubNodeFont.Dispose()
End If
If disposing AndAlso mExpandMeImage IsNot Nothing Then
mExpandMeImage.Dispose()
End If
If disposing AndAlso mCollapseMeImage IsNot Nothing Then
mCollapseMeImage.Dispose()
End If
If disposing AndAlso Me.ImageList IsNot Nothing Then
Me.ImageList.Dispose()
End If
mlColorSchemeIndex = Nothing
mColorHeaderGradientDark = Nothing
mColorHeaderGradientLight = Nothing
mColorHeaderSelectedBackGround = Nothing
mColorHeaderSelectedBorder = Nothing
mColorHeaderStandardBorder = Nothing
mColorHeaderStandardText = Nothing
mColorHeaderSelectedText = Nothing
mColorNodeSelectedColor = Nothing
mColorSubNodeStandardBackGround = Nothing
mColorSubNodeMouseOverBackGround = Nothing
mColorSubNodeSelectedBackGround = Nothing
mColorSubNodeSelectedBorder = Nothing
mColorSubNodeSelectedText = Nothing
mColorSubNodeStandardText = Nothing
mTreeViewBackColor = Nothing
mTreeViewForeColor = Nothing
mTreeViewBorderColor = Nothing
MyBase.Dispose(disposing)
End Sub
#End Region 'Dispose
End Class
OK. Here is a NEW CLass; The GroupedTreeViewCOlorScheme Class. THe Grouped TreeView uses this to set it's color properties (if other than default):
Part 1 Declarations and Method GetColorScheme:
Code:
Imports System.Drawing
''' <summary>
''' Provides variable representation of combined color sets for the Gourped
''' Treeview Control. An enumerated ColorScheme Index informs this class which
''' color combinations to assign to the Member Variables.
''' </summary>
''' <remarks></remarks>
Public Class GroupedTreeViewColorScheme
Implements IDisposable
Private mlColorScheme As GroupedTreeViewColorSchemeIndex = 1
#Region "Declarations: Member Variables"
''' <summary>
''' Default Values for All members of the Standard Colorcheme are those that
''' most closely match the vs2008 ToolBox Header:
''' </summary>
''' <remarks></remarks>
Protected mColorHeaderGradientDark As Color = Color.FromKnownColor(KnownColor.ButtonShadow)
Protected mColorHeaderGradientLight As Color = Color.FromKnownColor(KnownColor.ControlLight)
Protected mColorHeaderSelectedBackGround As Color = Color.FromKnownColor(KnownColor.InactiveCaptionText)
Protected mColorHeaderSelectedBorder As Color = Color.FromKnownColor(KnownColor.Highlight)
Protected mColorHeaderStandardBorder As Color = Color.SlateGray
'Color for Drawing the Header Text:
Private mColorHeaderStandardText As Color = Color.Black
Private mColorHeaderSelectedText As Color = Color.Black
'Color for Treenodes other than RootNodes:
Private mColorSubNodeStandardBackGround As Color = Color.FromKnownColor(KnownColor.Control)
Private mColorSubNodeMouseOverBackGround As Color = Color.FromKnownColor(KnownColor.AliceBlue)
Private mColorSubNodeSelectedBackGround As Color = Color.FromKnownColor(KnownColor.InactiveCaptionText)
Private mColorSubNodeSelectedBorder As Color = Color.FromKnownColor(KnownColor.Highlight)
'Color for Drawing the SubNode Text:
Private mColorSubNodeStandardText As Color = Color.Black
Private mColorSubNodeSelectedText As Color = Color.Black
'Color for the TreeView Control itself:
Protected mTreeViewBackColor As Color = Color.FromKnownColor(KnownColor.Control)
Protected mTreeViewForeColor As Color = Color.FromKnownColor(KnownColor.Black)
Protected mTreeViewBorderColor As Color = Color.FromKnownColor(KnownColor.SlateGray)
#End Region 'Member Variables
Public Sub New()
End Sub
Public Sub New(ByVal ColorSchemeIndex As GroupedTreeViewColorSchemeIndex)
Me.New()
GetColorScheme(ColorSchemeIndex)
End Sub
Public Property ColorSchemeIndex() As GroupedTreeViewColorSchemeIndex
Get
Return mlColorScheme
End Get
Set(ByVal value As GroupedTreeViewColorSchemeIndex)
mlColorScheme = value
Me.GetColorScheme(mlColorScheme)
End Set
End Property
#Region "GetColorScheme"
''' <summary>
''' Sets the colorScheme properties based upon the value of
''' the ColorScheme Index passed in.
''' </summary>
''' <param name="ColorScheme"></param>
''' <remarks></remarks>
Private Sub GetColorScheme(ByVal ColorScheme As GroupedTreeViewColorSchemeIndex)
Select Case ColorScheme
Case GroupedTreeViewColorSchemeIndex.Standard
'Header Color:
mColorHeaderGradientDark = Color.FromKnownColor(KnownColor.ButtonShadow)
mColorHeaderGradientLight = Color.FromKnownColor(KnownColor.ControlLight)
mColorHeaderSelectedBackGround = Color.FromKnownColor(KnownColor.InactiveCaptionText)
mColorHeaderSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
mColorHeaderStandardBorder = Color.SlateGray
mColorHeaderStandardText = Color.Black
mColorHeaderSelectedText = Color.Black
'SubNode Color:
mColorSubNodeStandardBackGround = Color.FromKnownColor(KnownColor.Control)
mColorSubNodeMouseOverBackGround = Color.FromKnownColor(KnownColor.AliceBlue)
mColorSubNodeSelectedBackGround = Color.FromKnownColor(KnownColor.InactiveCaptionText)
mColorSubNodeSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
'Color for Drawing the SubNode Text:
mColorSubNodeSelectedText = Color.Black
mColorSubNodeStandardText = Color.Black
'TreeView Color:
mTreeViewBackColor = Color.FromKnownColor(KnownColor.Control)
mTreeViewBorderColor = Color.FromKnownColor(KnownColor.SlateGray)
mTreeViewForeColor = Color.Black
Case GroupedTreeViewColorSchemeIndex.Gray
mColorHeaderGradientDark = Color.FromKnownColor(KnownColor.DarkGray)
mColorHeaderGradientLight = Color.FromKnownColor(KnownColor.WhiteSmoke)
mColorHeaderSelectedBackGround = Color.FromKnownColor(KnownColor.LightSteelBlue)
mColorHeaderSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
mColorHeaderStandardBorder = Color.SlateGray
mColorHeaderStandardText = Color.Black
mColorHeaderSelectedText = Color.Black
'SubNode Color:
mColorSubNodeStandardBackGround = Color.FromKnownColor(KnownColor.WhiteSmoke)
mColorSubNodeMouseOverBackGround = Color.FromKnownColor(KnownColor.AliceBlue)
mColorSubNodeSelectedBackGround = Color.FromKnownColor(KnownColor.LightSteelBlue)
mColorSubNodeSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
'Color for Drawing the SubNode Text:
mColorSubNodeSelectedText = Color.Black
mColorSubNodeStandardText = Color.Black
'TreeView Color:
mTreeViewBackColor = Color.FromKnownColor(KnownColor.WhiteSmoke)
mTreeViewBorderColor = Color.FromKnownColor(KnownColor.SlateGray)
mTreeViewForeColor = Color.Black
Case GroupedTreeViewColorSchemeIndex.Blue
mColorHeaderGradientDark = Color.FromKnownColor(KnownColor.CornflowerBlue)
mColorHeaderGradientLight = Color.FromKnownColor(KnownColor.White)
mColorHeaderSelectedBackGround = Color.FromKnownColor(KnownColor.Cornsilk)
mColorHeaderSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
mColorHeaderStandardBorder = Color.MediumSlateBlue
mColorHeaderStandardText = Color.DarkBlue
mColorHeaderSelectedText = Color.DarkBlue
'SubNode Color:
mColorSubNodeStandardBackGround = Color.FromKnownColor(KnownColor.WhiteSmoke)
mColorSubNodeMouseOverBackGround = Color.FromKnownColor(KnownColor.AliceBlue)
mColorSubNodeSelectedBackGround = Color.FromKnownColor(KnownColor.Cornsilk)
mColorSubNodeSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
'Color for Drawing the SubNode Text:
mColorSubNodeSelectedText = Color.DarkBlue
mColorSubNodeStandardText = Color.DarkBlue
'TreeView Color:
mTreeViewBackColor = Color.FromKnownColor(KnownColor.WhiteSmoke)
mTreeViewBorderColor = Color.FromKnownColor(KnownColor.MediumSlateBlue)
mTreeViewForeColor = Color.DarkBlue
'Add Additional Color Schemes HERE:
Case Else
'Revert to Default:
'Header Color:
mColorHeaderGradientDark = Color.FromKnownColor(KnownColor.ButtonShadow)
mColorHeaderGradientLight = Color.FromKnownColor(KnownColor.ControlLight)
mColorHeaderSelectedBackGround = Color.FromKnownColor(KnownColor.InactiveCaptionText)
mColorHeaderSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
mColorHeaderStandardBorder = Color.SlateGray
mColorHeaderStandardText = Color.Black
mColorHeaderSelectedText = Color.Black
'SubNode Color:
mColorSubNodeStandardBackGround = Color.FromKnownColor(KnownColor.Control)
mColorSubNodeMouseOverBackGround = Color.FromKnownColor(KnownColor.AliceBlue)
mColorSubNodeSelectedBackGround = Color.FromKnownColor(KnownColor.InactiveCaptionText)
mColorSubNodeSelectedBorder = Color.FromKnownColor(KnownColor.Highlight)
'Color for Drawing the SubNode Text:
mColorSubNodeSelectedText = Color.Black
mColorSubNodeStandardText = Color.Black
'TreeView Color:
mTreeViewBackColor = Color.FromKnownColor(KnownColor.Control)
mTreeViewBorderColor = Color.FromKnownColor(KnownColor.SlateGray)
mTreeViewForeColor = Color.Black
End Select
End Sub
#End Region ' Get ColorScheme
Part 2 of GroupedTreeViewCOlorScheme Class (Property Settings and Dispose)):
Code:
#Region "Header BackGround Colors"
Public Property ColorHeaderGradientDark() As Color
Get
Return mColorHeaderGradientDark
End Get
Set(ByVal value As Color)
mColorHeaderGradientDark = value
End Set
End Property
Public Property ColorHeaderGradientLight() As Color
Get
Return mColorHeaderGradientLight
End Get
Set(ByVal value As Color)
mColorHeaderGradientLight = value
End Set
End Property
Public Property ColorSelectedBackGround() As Color
Get
Return mColorHeaderSelectedBackGround
End Get
Set(ByVal value As Color)
mColorHeaderSelectedBackGround = value
End Set
End Property
#End Region ' Header BackGround Color
#Region "Header Border Colors"
Public Property ColorHeaderSelectedBorder() As Color
Get
Return mColorHeaderSelectedBorder
End Get
Set(ByVal value As Color)
mColorHeaderSelectedBorder = value
End Set
End Property
Public Property ColorHeaderStandardBorder() As Color
Get
Return mColorHeaderStandardBorder
End Get
Set(ByVal value As Color)
mColorHeaderStandardBorder = value
End Set
End Property
#End Region ' Header Border Color
#Region "Header Text Colors"
Public Property ColorHeaderSelectedText() As System.Drawing.Color
Get
Return mColorHeaderSelectedText
End Get
Set(ByVal value As System.Drawing.Color)
mColorHeaderSelectedText = value
End Set
End Property
Public Property ColorHeaderStandardText() As System.Drawing.Color
Get
Return mColorHeaderStandardText
End Get
Set(ByVal value As System.Drawing.Color)
mColorHeaderStandardText = value
End Set
End Property
#End Region 'Header Text Color
#Region "TreeView COlors"
Public Property TreeViewBackColor() As System.Drawing.Color
Get
Return mTreeViewBackColor
End Get
Set(ByVal value As System.Drawing.Color)
mTreeViewBackColor = value
End Set
End Property
Public Property TreeViewForeColor() As System.Drawing.Color
Get
Return mTreeViewForeColor
End Get
Set(ByVal value As System.Drawing.Color)
mTreeViewForeColor = value
End Set
End Property
Public Property TreeViewBorderColor() As System.Drawing.Color
Get
Return mTreeViewBorderColor
End Get
Set(ByVal value As System.Drawing.Color)
mTreeViewBorderColor = value
End Set
End Property
#End Region ' TreeView Color
#Region "SubNode BackGround Colors"
Public Property SubNodeSelectedBackGround() As Color
Get
Return mColorSubNodeSelectedBackGround
End Get
Set(ByVal value As Color)
mColorSubNodeSelectedBackGround = value
End Set
End Property
Public Property SubNodeMouseOverBackGround() As Color
Get
Return mColorSubNodeMouseOverBackGround
End Get
Set(ByVal value As Color)
mColorSubNodeMouseOverBackGround = value
End Set
End Property
Public Property SubNodeStandardBackGround() As Color
Get
Return mColorSubNodeStandardBackGround
End Get
Set(ByVal value As Color)
mColorSubNodeStandardBackGround = value
End Set
End Property
#End Region 'SubNode BackGround Color
#Region "SubNode Selected Border"
Public Property SubNodeSelectedBorder() As Color
Get
Return mColorSubNodeSelectedBorder
End Get
Set(ByVal value As Color)
mColorSubNodeSelectedBorder = value
End Set
End Property
#End Region 'SubNode Selected Border
#Region "SubNode Text Colors"
Public Property SubNodeSelectedText() As Color
Get
Return mColorSubNodeSelectedText
End Get
Set(ByVal value As Color)
mColorSubNodeSelectedText = value
End Set
End Property
Public Property SubNodeStandardText() As Color
Get
Return mColorSubNodeStandardText
End Get
Set(ByVal value As Color)
mColorSubNodeStandardText = value
End Set
End Property
#End Region ' SubNode Text Color
Private disposedValue As Boolean = False ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
mColorHeaderGradientDark = Nothing
mColorHeaderGradientLight = Nothing
mColorHeaderSelectedBackGround = Nothing
mColorHeaderSelectedBorder = Nothing
mColorHeaderStandardBorder = Nothing
mColorHeaderStandardText = Nothing
mColorHeaderSelectedText = Nothing
mColorSubNodeSelectedText = Nothing
mColorSubNodeStandardText = Nothing
Me.Finalize()
End If
Me.disposedValue = True
End Sub
#Region " IDisposable Support "
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Last but not least, a FORM, with a GroupedTreeView Control added from the vs toolbox, and some VERY basic implementation code:
Code:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Set this to TRUE to auto-collapse unselected nodes(Default is FALSE)
GroupedTreeView1.NodeAutoCollapse = True
'Play with different COLORSchemes (Default is Brownish/vs standard):
GroupedTreeView1.ColorSchemeIndex = GroupedTreeViewColorSchemeIndex.Gray
'Populate the control with some demonstration nodes:
Dim nd As TreeNode
Dim sNd As TreeNode
'Add Simple Header/SubNodes with no SubNode Icons:
For i As Integer = 1 To 5
'Add "Header Nodes":
nd = Me.GroupedTreeView1.Nodes.Add("Header " & i)
For j As Integer = 1 To 5
'Add "SubNodes" to each header ("toolbox" items):
sNd = nd.Nodes.Add("Sub-Node " & j)
Next
Next
''NOTE: Uncomment THIS Code block, and Comment out the previous
''to see the control with simple Icons:
''The ImageList is initialized automatically; no need to call a constructor on it:
'GroupedTreeView1.ImageList.Images.Add("Folder", My.Resources.Folder1616_PNG)
'For i As Integer = 1 To 5
' nd = Me.GroupedTreeView1.Nodes.Add("Header " & i)
' For j As Integer = 1 To 5
' sNd = nd.Nodes.Add("Sub-Node " & j, "SUbNode " & j, "Folder")
' Next
'Next
End Sub
End Class
Uh, yeah. Believe it or not, I wanted to do that when I first posted it, but Couldn't figure out how . . .(Sheepishly).
Even worse, I was so damn excited that I was able to make the control draw itself and work, that I had not yet USED it in any context other than the Simple test iteration you see here (Basisally, loading it up with a "For i as Integer = 1 To 5 . . ." type structure to see it work. The one you see here is set to use a grey color Scheme. There are (so Far) THREE colorSchemes: Standard (Brownish), Gray, and Blue (Which is a little too bright for my taste . . . working on it).
Here are some REALLY basic screenshots of what it looks like.
Note the following:
A. I have NOT yet implemented the "Drag and Drop" Functionality as found in the vs ToolBox (Actually, much of THAT would need to be implemented on the client/host form side anyway-treeview provides most of what is needed . . .).
B. I TRIED to show the other side of the sample form, in which there is a Listview control, which populates based on node selection in the Grouped Tree Control. In other words, much as you might use a standard treeview. However, the Image attachment was then too large to upload . . .
C. Now that this is "done", I am trying to model the rest of my UI (or at least, the current attempt. UI design is HARD to get it RIGHT!). When I have a slightly more impressive view of this control in action, I will post it. I also fully expect to find new issues with the control as I go to use it in a real-world setting. I will post updates as I go.
D. As I said, the Header Nodes don't yet draw to the same level of detail as those found in the native vs control. Also, I have set the Item height property larger here.
This is my first attempt at anything like this. Thanks for bearing with me. Feedback is much appreciated!
The generic folder Icons could be anything else, and in any real-worold implementation would probably be specific to each Sub-node item.
Looks nice. I was making a similar control in C# but did not even think about using the Treeview. It works quite nice this way. Mine was completely from scratch which is a lot more work, but also gives me more flexibility.
You do have the disadvantage that the control flickers a little bit, which is actually the underlying treeview flickering. I don't think there's anything you can do about that. The treeview 'sucks' in that way...
Also, try to add something to limit the user to only one level of child nodes. I know you tried to do this by simply throwing an exception when a second level of child nodes is detected, but the control breaks after that and keeps throwing exceptions every few seconds.
Mine was completely from scratch which is a lot more work, but also gives me more flexibility.
Yeah, tell me. I did the "From scratch" thing FIRST, and it looked good (Slightly better then this, actually), and for the most part, worker pretty good too. However, My original "architecture" was flawed, and I ended up with some really messy event handling issues. It was AFTER I scrapped thatthat I realized I might be able to inherit Treeveiw. As you can see, that came with it's OWN set of problems.
Also, try to add something to limit the user to only one level of child nodes.
I agree. Sort of. I wasn't certain I wanted to limit the number of Node generations or not, so I threw the exception in for the moment. I had considered that it might be nice to have a nesting capability for the children. Of course, I ALSO considered that might become ugly and ungainly. SO I left it for now . . .
I also considered trying to add a separate mechanism for adding "Header" nodes vs "Sub-items". I might still. My "From sctatch" control had this, and there was also a lot more one could do with it in other ways. Since the "Header" was it's own object, which sourced discreet "Selected" "Unselected", "Collapsed" and "Expanded" events, I had originally planned versions in which the Child nodes would derive from ListViewItem OR DataGridViewRow.
I might still do that. I stilll have the mess of components that went into the Scratch version of this.
You do have the disadvantage that the control flickers a little bit, which is actually the underlying treeview flickering. I don't think there's anything you can do about that. The treeview 'sucks' in that way...
As far as I can tell, the flickering in THIS case occurs as a result of forcing the node to collapse when the click occurs in the area OUTSIDE the TReeView-Defined Event are for the Node (The "FullRowSelect" area, for lack of a better term.
I one is happy with a control which expands and collapsed only in response to a click on the Expander Icon, there is no flicker. If I remember correctly, the same is true of the area the TReeView THinks is the "Node" (The part you can select if FRS is false.
I traded the slight flickering for a control which mimics the expand/collapse behavior of the Vs Toolbox. I may end up killing that off though, because I hate the flickering . . .
Gawd, it can be horrible to read back thru my posts later and realize that, due to my atrtocious typing, it often sounds like english is my SECOND language.
EDIT: Like THAT in blue!
Jesus!
Thanks for the feedback. Let me know what you think.