Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Easy ToolStrip - Let your UserControl display beyond the Containers/Form limits (C# and VB.NET)

0.00/5 (No votes)
3 Aug 2016 1  
The easiest way to implement the ToolStripControlHost and ToolStripDropDown classes, in a UserControl, in order to display a Container/Control beyond the limits of its Parent Container and Parent Form, with very few lines of code. Let's uncomplicate ...

Introduction

When I tried (for the first time) to understand in a practical way how one could display a UserControl beyond the limits of any eventual Parent Container and Parent Form, I came across several examples. Most of them very good, some with stunning styles and visual effects, however all complicated, tangled, sometimes dependent on external actions to the UserControl itself.

As I am fan of a simple coding, the aim of this article is to show how you can apply this technique to a "DropDown" UserControl simply by adding very few lines of code in three different "Key Parts" of the code:

  • Key Part 1 - Variables definition
  • Key Part 2 - UserControl loading
  • Key Part 3 - "DropDown" action controller Event

This technique can be applied to Containers (such as Panel, GroupBox, TabControl, etc.) or Controls (such as ListView, ListBox, TreeView, DataGridView, etc.).

Using the Code

In this example we'll build a kind of DropDownList ComboBox (called CbBox), consisting of three Controls:

  1. A Label (called LBox), to display the selected item.
  2. A Button (called bDrop), to Open/Show or Close/Hide the Items List.
  3. A ListView (called _List), without Headers, to store the selectable items.

● The Label and the Button are created by the Designer and are the visible and resizable part of the UserControl.

● The ListView, declared as an Instance Variable, is set up (with only one Column) when the UserControl is loaded, and only at Runtime.

Key Part 1 - Variables definition

Just define 3 variables:

  1. _List (class ListView), to store the list of data items. In VB.NET, Friend WithEvents, in order to handle the ListView event ItemSelectionChanged.
  2. tsHost (class ToolStripControlHost), to host the Control (_List).
  3. tsDrop (class ToolStripDropDown), to display the list of data items stored in the hosted Control (_List).
// Instance variables
private ListView _List;
private ToolStripControlHost tsHost;
private ToolStripDropDown tsDrop = new ToolStripDropDown();
' Instance variables
Friend WithEvents _List As ListView
Private tsHost As ToolStripControlHost
Private tsDrop As ToolStripDropDown = New ToolStripDropDown

Key Part 2 - UserControl loading

In the UserControl's Load Event, but only at Runtime:

  1. Set up the ListView with the appropriate Properties, no Headers, just one Column (in this example).
  2. In C#, add the ListView's ItemSelectionChanged EventHandler
  3. Attach the ListView to the ToolStripControlHost.
private void CbBox_Load(object sender, System.EventArgs e)
{
    if (this.DesignMode == false)   // Only at Runtime
    {
        _List = new ListView();
        _List.View = View.Details;
        _List.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
        _List.MultiSelect = false;
        _List.FullRowSelect = true;
        _List.HideSelection = false;
        _List.HeaderStyle = ColumnHeaderStyle.None;
        _List.GridLines = true;
        _List.Columns.Add(new ColumnHeader());
        _List.Columns[0].Width = this.Width - 21;
        //
        // Add the ItemSelectionChanged EventHandler
        _List.ItemSelectionChanged += _List_ItemSelectionChanged;
        //
        // Attach the ListView to the ToolStripControlHost
        tsHost = new ToolStripControlHost(_List);
        //
    }
}
Private Sub CbBox_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If Me.DesignMode = False Then   ' Only at Runtime
        _List = New ListView
        _List.View = View.Details
        _List.BorderStyle = Windows.Forms.BorderStyle.FixedSingle
        _List.MultiSelect = False
        _List.FullRowSelect = True
        _List.HideSelection = False
        _List.HeaderStyle = ColumnHeaderStyle.None
        _List.GridLines = True
        _List.Columns.Add(New ColumnHeader)
        _List.Columns(0).Width = Me.Width - 21
        '
        ' Attach the ListView to the ToolStripControlHost
        tsHost = New ToolStripControlHost(_List)
        '
    End If
End Sub

Key Part 3 - "DropDown" action controller Event

In the Button (or Label) Click Event:

  • If _IsOpen is false, the ToolStripDropDown is to be shown:
    1. Resize the ListView:
      • Width equals the UserControl's Width.
      • Height is the lowest number of rows between the predefined _MaxItemsDisp and the total data items (or 1 if no data items),
        multiplied by 17 (16 pixels per row plus 1 pixel for the gridline) plus 1 pixel for final line.
    2. Prevent unnecessary lines display (Margin, Padding).
    3. Get the items to display by the ToolStripDropDown, from the ToolStripControlHost (which hosts the ListView).
    4. Show the ToolStripDropDown, relative to the UserControl's visible part (Label & Button), at the specified upper left corner location (horizontal, vertical).
  • If _IsOpen is true, the ToolStripDropDown is to be hidden.
private void bDrop_Click(object sender, EventArgs e)
{
    // Shows or Hides the List, depending on _IsOpen
    // _IsOpen: True->tsDrop is shown, False->tsDrop is hidden
    if (_IsOpen == false)   // If hidden, show it
    {
        _List.Size = new Size(this.Width, Math.Min(_MaxItemsDisp, Math.Max(_List.Items.Count, 1)) * 17 + 1);
        if (tsHost != null)
        {
            tsHost.Margin = new Padding(0);
            tsDrop.Padding = new Padding(0);
            tsDrop.Items.Add(tsHost);
            tsDrop.Show(this, new Point(-1, this.Height - 2));
        }
    }
    else                    // If shown, hide it
    {
        tsDrop.Hide();
    }
    _IsOpen = !_IsOpen;     // Invert value
    bDrop.Focus();
}
Private Sub bDrop_Click(sender As Object, e As EventArgs) Handles bDrop.Click, LBox.Click
    ' Shows or Hides the List, depending on _IsOpen
    ' _IsOpen: True->tsDrop is shown, False->tsDrop is hidden
    If _IsOpen = False Then ' If hidden, show it
        _List.Size = New Size(Me.Width, Math.Min(_MaxItemsDisp, Math.Max(_List.Items.Count, 1)) * 17 + 1)
        If tsHost IsNot Nothing Then
            tsHost.Margin = New Padding(0)
            tsDrop.Padding = New Padding(0)
            tsDrop.Items.Add(tsHost)
            tsDrop.Show(Me, New Point(-1, Me.Height - 2))
        End If
    Else                    ' If shown, hide it
        tsDrop.Hide()
    End If
    _IsOpen = Not (_IsOpen) ' Invert value
    bDrop.Focus()
End Sub

Some other features

The UserControl is actually very simple. But beyond the 3 Key Parts shown above, there are a few more pieces of code:

● The ListView's ItemSelectionChanged Event

When the ToolStripDropDown is shown, this Event is needed, to:

  • Save the Selected Item into a variable Item
  • Hide the ToolStripDropDown
  • If the Item really changed:
    • Copy the User's selection to the UserControl's Label Control
    • Raise the UserControl's SelectedItemChanged Event
private void _List_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
    string Item = e.Item.Text;
    tsDrop.Hide();
    _IsOpen = false;
    if (LBox.Text != Item)
    {
        LBox.Text = Item;
        if (SelectedItemChanged != null)
        {
            SelectedItemChanged(Item);
        }
    }
}
Private Sub _List_ItemSelectionChanged(sender As Object, e As ListViewItemSelectionChangedEventArgs) _
            Handles _List.ItemSelectionChanged
    Dim Item As String = e.Item.Text
    tsDrop.Hide()
    _IsOpen = False
    If LBox.Text <> Item Then
        LBox.Text = Item
        RaiseEvent SelectedItemChanged(Item)
    End If
End Sub

● The UserControl's Resize Event

When the UserControl is resized, the Height may not decrease below 15, otherwise the text would be cut.

private void CbBox_Resize(object sender, System.EventArgs e)
{
    if (this.Height < 15)
    {
        this.Height = 15;
    }
}
Private Sub CbBox_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
    If Me.Height < 15 Then
        Me.Height = 15
    End If
End Sub

When the Width is resized, the Button's Width is not affected (19 pixels). But when the Height is decreased or increased, the Button's Height changes accordingly:

(Height = 15 pixels)

(Height = 29 pixels)

● The MouseHover and MouseLeave Events

Just a very simple visual effect. When the Mouse passes over the Button Control, the Mouse pointer and the Button's BackColor are changed:

private void bDrop_MouseHover(object sender, EventArgs e)
{
    this.Cursor = Cursors.Hand;
    bDrop.BackColor = Color.Orange;
}

private void bDrop_MouseLeave(object sender, EventArgs e)
{
    this.Cursor = Cursors.Default;
    bDrop.BackColor = Color.Lavender;
}
Private Sub bDrop_MouseHover(sender As Object, e As EventArgs) Handles bDrop.MouseHover
    Me.Cursor = Cursors.Hand
    sender.BackColor = Color.Orange
End Sub

Private Sub bDrop_MouseLeave(sender As Object, e As EventArgs) Handles bDrop.MouseLeave
    Me.Cursor = Cursors.Default
    sender.BackColor = Color.Lavender
End Sub

● The Properties

A few Properties to add some functionality...

  • BackColor - (Read/Write) Gets or Sets the UserControl's Back Color
  • ForeColor - (Read/Write) Gets or Sets the UserControl's Fore Color
  • MaxItemsDisp - (Read/Write) Gets or Sets the Maximum number of Rows to display
  • Text - (Read/Write) Gets or Sets the UserControl's Text (Label Control)
  • Count - (ReadOnly) Gets the total number of Items in the ListView's Item Collection
public override Color BackColor
{
    get { return LBox.BackColor; }
    set { LBox.BackColor = value; }
}

public override Color ForeColor
{
    get { return LBox.ForeColor; }
    set { LBox.ForeColor = value; }
}

public int MaxItemsDisp
{
    get { return _MaxItemsDisp; }
    set { _MaxItemsDisp = value; }
}

public override string Text
{
    get { return LBox.Text; }
    set { LBox.Text = value; }
}

public int Count
{
    get { return _List == null ? 0 : _List.Items.Count; }
}
Public Overrides Property BackColor As Color
    Get
        Return LBox.BackColor
    End Get
    Set(value As Color)
        LBox.BackColor = value
    End Set
End Property

Public Overrides Property ForeColor As Color
    Get
        Return LBox.ForeColor
    End Get
    Set(value As Color)
        LBox.ForeColor = value
    End Set
End Property

Public Property MaxItemsDisp() As Integer
    Get
        Return _MaxItemsDisp
    End Get
    Set(ByVal value As Integer)
        _MaxItemsDisp = value
    End Set
End Property

Public Overrides Property Text() As String
    Get
        Return LBox.Text
    End Get
    Set(ByVal value As String)
        LBox.Text = value
    End Set
End Property

Public ReadOnly Property Count() As Integer
    Get
        Return _List.Items.Count
    End Get
End Property

● The Methods

Only one Method is needed:

  • AddRow - Adds a String to the ListView's ItemCollection.
public void AddRow(string NewItem)
{
    _List.Items.Add(NewItem);
}
Public Sub AddRow(ByRef NewItem As String)
    _List.Items.Add(NewItem)
End Sub

● The Events

Only one Event:

  • SelectedItemChanged - Is raised when the ListViews's ItemSelectionChanged Event occurs and the Selected Item really changed.
public event SelectedItemChangedEventHandler SelectedItemChanged;
public delegate void SelectedItemChangedEventHandler(string Item);
Public Event SelectedItemChanged(Item As String)

The Demo project

Is a simple Windows Forms Application, where all the UserControl's features can be tested.

The UserControl DLL is located at ...\bin\Debug\CbBox.dll

Although I prefer VB.NET for the sake of simplicity and readability (braces and semicolons are a bummer),
I have to recognize that the performance of C# is much higher... DLL file sizes:

  • VB.NET version is 28 KB
  • C# version is 12 KB

  • Items: each one of the groups can be added only once.
  • Other player: Type and Add whatever you want.
  • To Set a Property, first Click the white box and type or choose the desired value. Colors will show a ColorDialog.
  • Event: Whenever the Selected Item changes, a message will be displayed:

History

03.Aug.2016 - First posted.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here