65.9K
CodeProject is changing. Read more.
Home

VS.NET like toolbox control

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (11 votes)

Feb 13, 2004

4 min read

viewsIcon

126095

downloadIcon

868

Collapsing and expanding panel toolbar

Sample Image - screenshot.png

Simple ToolBar control

As I was working on my editor I decided I needed a toolbox control like the one in VS.NET. Instead of going out and trying to find a control on the net that wouldn't do what I wanted anyway, I thought it would be fun just to roll my own.

Overview

This control inherits from the panel object. Basically what it does is draw, position, collapse and expand the panels on screen to get the effect you want out of the control. You can also add any other control (textbox in the case of the demo) you want to the panel object.

Each ToolBar contains multiple ToolPanel's.

Methods and Properties

ToolBar.new([dockStyle], [width]) Overloaded Constructor Creates the ToolBar object
ToolBar.count[ReadOnly] Property Returns the number of panels
ToolBar.item(integer) Property Returns a panel object
ToolBar.Remove(string) Method Removes a panel
ToolBar.View(Object) Method Brings a panel into view
ToolBar.Add(String) Method Adds a panel
ToolPanel.new(String) Constructor Creates a ToolPanel object
ToolPanel.Title[ReadOnly] Property Returns the Title
ToolPanel.State[ReadOnly] Property/Enum Returns the object state
ToolPanel.captionArea[ReadOnly] Property Returns a rectangle of the caption dimensions
ToolPanel.clientArea[ReadOnly] Property Returns a rectangle of the client dimensions
ToolPanel.captionHeight(integer) Property Returns/Sets the size of the caption
ToolPanel.Maximize Method Although public do not call this, resets the docks
ToolPanel.Minimize(Object) Method Although public do not call this, resets the docks

Simple Example

The first thing you need to do is import the namespace. This can be done at the top of your code by typing Imports Toolbar. The next thing you will need to do is declare an instance of the object and add it to your form. There are a bunch of ways to do this, you can declare it as a Friend and add it as a control in initialize components or you can simply declare it as public in the main body of your code. What solution is right for you depends on if it needs to dock in a certain order on your form. For instance if you have a statusbar on your form you will want to declare it as a friend and add it to the initialization. Here are both examples:

Initialization method:

  Private components As System.ComponentModel.IContainer
  ...
  ...
  ...
  Friend Toolbar As New Toolbar.Initalize(DockStyle.Left, 175)
  Friend WithEvents Textbox2 As System.Windows.Forms.TextBox
  Friend WithEvents Button2 As System.Windows.Forms.Button
  Friend WithEvents Label2 As System.Windows.Forms.Label
  <System.Diagnostics.DebuggerStepThrough()> 
      Private Sub InitializeComponent()
    ...
    ...
    ...
  Me.Controls.Add(Me.Toolbar)
  Me.Controls.Add(Me.Textbox2)
  Me.Controls.Add(Me.Button2)
    ...
    ...
    ...

Public Method:

    Public Toolbar as new toolbar()

Now that you have created the object you can use it by calling toolbar.add("My Tab") like this..

    Private Sub Button1_Click(ByVal sender As System.Object, _
                   ByVal e As System.EventArgs) Handles Button1.Click
        Toolbar.add(TextBox.Text)
    End Sub

While this is functional its not very interesting, to spice things up a bit we need to add another control to the panel (no since having an empty panel). For this I have created another class called panelContents. You do not need to do it this way, you could just use Controls.Add(new textbox). Lets take a look at the code:

    ' This class just creates a textbox object already

    ' loaded with a few options

    '

    Public Class panelContents : Inherits TextBox
        Sub New(ByVal txt As String)
            Me.Multiline = True
            Me.Dock = DockStyle.Fill
            Me.BorderStyle = BorderStyle.None
            Me.ScrollBars = ScrollBars.Vertical
            Me.Text = txt
        End Sub
    End Class


    Private Sub Button1_Click(ByVal sender As System.Object, _
                 ByVal e As System.EventArgs) Handles Button1.Click
        
        ' Create a new Panel

        Dim thisPanel As ToolPanel = Toolbar.add(TextBox.Text)


        ' Put a textbox in the panel

        thisPanel.Controls.Add(New panelContents("This is " & TextBox.Text))

    End Sub

One more thing of noted importance is the Remove method. You can call this in you program like so

    Private Sub Button2_Click(ByVal sender As System.Object, _
                  ByVal e As System.EventArgs) Handles Button2.Click
        Toolbar.Remove(Textbox2.Text)
    End Sub

ToolBar Class

I am going to skip the overloaded new constructors and the properties for the class and focus on the important things, namely add, remove and view. There is one important thing you need to know before we gets started, all the ToolBerPanel objects are stored in an internal array called Panels.

Add function:

        '------------------------------------------------------

        '          Sub: add

        '  Description: Create a new panel

        '

        Public Function add(ByVal name As String) As ToolPanel
            MinimizeAll()               ' Make sure they are all minimized


            Dim newPanel = New ToolPanel(name, Me)    
              ' Create the panel w/ a ref back to here

            Panels.Add(newPanel)         ' Add to master array


            Dim orderPanels As New ArrayList ' Array of panels

            Dim thisPanel As ToolPanel


            For Each thisPanel In Panels.ToArray      
                        ' Copy the panels to the array

                orderPanels.Add(thisPanel)
            Next


            orderPanels.Reverse()

            Me.Controls.Clear()                
                ' Clear out the controls

            Me.Controls.Add(newPanel)          
               ' Add the most recent control


            For Each thisPanel In orderPanels.ToArray ' Add the Bottom

                Me.Controls.Add(thisPanel)            
                  ' Addrange isn't working, humm.

            Next

            Console.WriteLine("Panels: " & orderPanels.Count + 1)
            Return newPanel
        End Function

What the add function does is:

  • Collapse all the panels
  • Create a new panel
  • Add it to the internal array
  • Insert the control and append the reverse list of existing controls into the Controls of the component.
  • Returns the new object

Remove Function:

        '------------------------------------------------------

        '          Sub: Remove

        '  Description: Search for the panel and if found

        '               remove it and expand the one before it

        '

        Public Sub Remove(ByVal Title As String)
            Dim tmp As ToolPanel
            Dim index As Integer

            For Each tmp In Panels.ToArray
                If tmp.Title.ToLower = Title.ToLower Then
                    index = Panels.IndexOf(tmp)
                    Panels.Remove(tmp)
                    Me.Controls.Remove(tmp)
                    tmp.Dispose()


                    If index >= 1 Then
                        Panels(index - 1).maximize()
                    End If
                End If
            Next
        End Sub

What the remove function does is:

  • Looks at all the panels to find a match
  • Removes the panel from the array
  • Removes the panel from the control
  • Frees up the memory used by the panel
  • Maximizes the panel above it (so not to have empty space)

The View function

        '------------------------------------------------------

        '          Sub: View

        '  Description: Bring this panel in to view

        '

        Public Sub View(ByVal panel As ToolPanel)
            Dim topPanels As New ArrayList
            Dim bottomPanels As New ArrayList
            Dim middlePanel As ToolPanel
            Dim thisControl As ToolPanel
            Dim top As Boolean = True


            '------------------------------------------------------

            ' Loop threw the panel array and add the panels to the 

            ' docking arrays.  

            '

            For Each thisControl In Panels.ToArray
                If thisControl Is panel Then
                    middlePanel = thisControl      ' Add the middle panel

                    middlePanel.Maximize()         
                       ' Maximize the middle panel and redock

                    top = False  ' Everything after this are bottom panels

                Else
                    If top Then
                        thisControl.Minimize(DockStyle.Top)  
                               ' Minimize and redock

                        topPanels.Add(thisControl)           
                               ' Add the top panels

                    Else
                        thisControl.Minimize(DockStyle.Bottom)  
                               ' Mimize and redock

                        bottomPanels.Add(thisControl)           
                               ' Add the bottom panels

                    End If
                End If
            Next


            '------------------------------------------------------

            ' Add the panels to the control, be careful remember

            ' order is weird: 

            '

            Me.Controls.Clear()         ' Clear out the controls

            topPanels.Reverse()         ' Reverse the top panel array

            Me.Controls.Add(middlePanel) ' Add the middle panel


            For Each thisControl In topPanels.ToArray
                Me.Controls.Add(thisControl)            ' Add the top

            Next

            For Each thisControl In bottomPanels.ToArray
                Me.Controls.Add(thisControl)            ' Add the Bottom

            Next

        End Sub

What the remove function does is:

  • Create arrays for the top, middle and bottom panels
  • Loops the panels and puts them in there proper arrays
  • Clears out the controls
  • Adds the activeControl, a reversed version of the top controls and the bottom controls to the control. (Pay attention to the order the controls are added in)

ToolPanel Class

There is nothing real interesting in this class. It's main job is to draw the control and return it to the parent. It also has a bunch of properties and two methods. If you have any questions about this class please feel free to ask.

Public Class ToolPanel : Inherits Panel
    Private _Title As String
    Private _State As stateInfo = stateInfo.Maximized
    Private _Click As stateClick = stateClick.Normal
    Private _caption As New Rectangle
    Private _client As New Rectangle
    Private _captionSize As Integer = 20
    Private _parent As Toolbar.Initalize


    Sub New(ByVal name As String, ByVal sender As Toolbar.Initalize)
        Title = name
        _parent = sender

        Me.Dock = DockStyle.Fill                ' Make new panels filled

        Me.DockPadding.Top = _captionSize       ' Very important !

    End Sub


    '------------------------------------------------------

    ' *** A bunch of properties are here ***

    '------------------------------------------------------

    
    '------------------------------------------------------

    '          Sub: setAreas

    '  Description: Update the rectangle areas

    '

    Private Sub setAreas()
        _caption = New Rectangle(0, 0, Me.Width, captionHeight)
        _client = New Rectangle(0, Me.Top + captionHeight, 
                    Me.Width, Me.Height - captionHeight)
    End Sub


    '------------------------------------------------------

    '          Sub: Maximize

    '  Description: Change the state value and set the 

    '               toolpanel to fill

    '

    Public Sub Maximize()
        Me._State = stateInfo.Maximized
        Me.Dock = DockStyle.Fill
    End Sub

    '------------------------------------------------------

    '          Sub: Maximize

    '  Description: Change the state value and set the 

    '               toolpanel to whatever you sent me.

    '

    Public Sub Minimize(ByVal dock As DockStyle)
        Me._State = stateInfo.Minimized
        Me.Height = Me.captionHeight
        Me.Dock = dock
    End Sub

    '------------------------------------------------------

    '          Sub: ToolPanel_MouseDown

    '  Description: If they clicked in the captionarea then 

    '               change the click state

    '

    Private Sub ToolPanel_MouseDown(ByVal sender As Object, _
                      ByVal e As System.Windows.Forms.MouseEventArgs) _
                      Handles MyBase.MouseDown

        If e.Button = MouseButtons.Left Then
            If captionArea.IntersectsWith( _
                       New Rectangle(e.X, e.Y, 1, 1)) Then 
                Me._Click = stateClick.Clicked
                Invalidate(captionArea)
            End If
        End If
    End Sub

    '------------------------------------------------------

    '          Sub: ToolPanel_MouseUp

    '  Description: If the click state is clicked then call

    '               the view sub from the parent class

    '

    Private Sub ToolPanel_MouseUp(ByVal sender As Object, _
             ByVal e As System.Windows.Forms.MouseEventArgs) _
             Handles MyBase.MouseUp

        If e.Button = MouseButtons.Left And _Click = stateClick.Clicked Then
            Me._Click = stateClick.Normal
            Invalidate(captionArea)
            Me._parent.View(Me)
        End If
    End Sub

    '------------------------------------------------------

    '          Sub: ToolPanel_Paint

    '  Description: Redraw the controls

    '

    Private Sub ToolPanel_Paint(ByVal sender As Object, ByVal e As _
                 System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        setAreas()

        ' Get Colors and Pens

        '

        Dim captionFont As New Font(Me.Font, FontStyle.Regular)
        Dim captionBrush As Brush = _
             New SolidBrush(ColorTranslator.FromHtml("#ECE9D8"))
        Dim captionRect = New Pen(Color.White, 1)
        Dim captionPen = New Pen(Color.LightGray, 1)

        If _Click = stateClick.Clicked Then
            captionBrush = _
                  New SolidBrush(ColorTranslator.FromHtml("#D8EDEB"))
        End If

        ' Draw it

        '

        Dim g As Graphics = e.Graphics

        g.FillRectangle(captionBrush, captionArea)
        g.DrawRectangle(captionRect, captionArea)

        g.DrawLine(captionPen, _
                   New Point(captionArea.X + captionArea.Width - 1, _
                        captionArea.Y), _
                   New Point(captionArea.X + captionArea.Width - 1, _
                        captionArea.Y + captionArea.Height))

        g.DrawLine(New Pen(Color.Black, 2), _
                   New Point(captionArea.X, captionArea.Y + _
                     captionArea.Height), _
                   New Point(captionArea.Width, captionArea.Y + _
                     captionArea.Height))

        ' Draw Text

        '

        Dim textSize As SizeF = g.MeasureString(Title, captionFont)
        Dim textStart As Integer = (captionHeight / 2) - _
                (textSize.Height / 2)

        g.DrawString(Title, captionFont, Brushes.Black, Me.Left + 5, _
                  textStart)
    End Sub
End Class 

In a nutshell this class

  • Gets the information from the parent about what to create
  • Sets the dockpadding to the size of the header
  • Draws the control and maintains its state information
  • Handles mouse clicks and sends them back to the view function of the parent control