Easy ToolStrip - 让您的UserControl显示超出容器/窗体限制 (C# 和 VB.NET)





5.00/5 (17投票s)
实现 ToolStripControlHost 和 ToolStripDropDown 类最简单的方法,在 UserControl 中,可以以很少的代码行,显示一个超出其父容器和父窗体限制的容器/控件。让我们化繁为简...

引言
当我(第一次)尝试以实际方式理解如何显示一个 UserControl **超出其限制** 的任何潜在父容器和父窗体时,我遇到了几个例子。大多数都很好,有些具有惊人的样式和视觉效果,然而所有这些都很复杂,纠缠不清,有时依赖于 UserControl 本身之外的操作。
由于我是一个简单的编码爱好者,本文的目的是展示如何通过在代码的三个不同的“关键部分”中添加 **非常少的代码行** 来将此技术应用于“下拉”UserControl。
- 关键部分 1 - 变量定义
- 关键部分 2 - UserControl 加载
- 关键部分 3 - “下拉”操作控制器事件
此技术可应用于容器(如 Panel、GroupBox、TabControl 等)或控件(如 ListView、ListBox、TreeView、DataGridView 等)。
Using the Code
在此示例中,我们将构建一种下拉列表组合框(称为 CbBox),它由三个控件组成
- 一个标签(称为 LBox),用于显示选定的项目。
- 一个按钮(称为 bDrop),用于打开/显示或关闭/隐藏项目列表。
- 一个ListView(称为 _List),无标题,用于存储可选项。
● 标签和按钮由设计器创建,是 UserControl 的可见且可调整大小的部分。

● ListView 声明为实例变量,在 UserControl 加载时(仅在运行时)进行设置(只有一个列)。
关键部分 1 - 变量定义
只需定义 3 个变量
- _List(ListView 类),用于存储数据项列表。在 VB.NET 中,是- Friend WithEvents,以便处理 ListView 事件- ItemSelectionChanged。
- tsHost(ToolStripControlHost 类),用于托管控件(- _List)。
- tsDrop(ToolStripDropDown 类),用于显示托管控件(- _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
关键部分 2 - UserControl 加载
在 UserControl 的 Load 事件 中,但仅在运行时
- 使用适当的 属性设置 ListView,无标题,只有一个列(在本例中)。
- 在 C# 中,添加 ListView 的 ItemSelectionChanged事件处理程序
- 将 ListView 附加到 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
关键部分 3 - “下拉”操作控制器事件
在按钮(或标签)的 Click 事件 中
- 如果 _IsOpen为 false,则显示 ToolStripDropDown- 调整 ListView 的大小- 宽度等于 UserControl 的宽度。
- 高度是预定义的- _MaxItemsDisp与总数据项数之间的最小行数(如果没有数据项则为 1),
 乘以 17(每行 16 像素加上网格线 1 像素)再加上最后的 1 像素行。
 
- 防止显示不必要的行(边距、内边距)。
- 从ToolStripControlHost(它托管ListView)获取要由 ToolStripDropDown 显示的项。
- 显示 ToolStripDropDown,相对于 UserControl 的可见部分(标签和按钮),在指定的左上角位置(水平、垂直)。
 
- 调整 ListView 的大小
- 如果 _IsOpen为 true,则隐藏 ToolStripDropDown。
    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
其他一些功能
UserControl 实际上非常简单。但是,除了上面显示的 3 个关键部分之外,还有一些其他代码片段
● ListView 的 ItemSelectionChanged 事件
当 ToolStripDropDown **显示**时,需要此事件来
- 将选定的项目保存到变量 Item中
- 隐藏 ToolStripDropDown
- 如果项目确实发生了更改- 将用户的选择复制到 UserControl 的标签控件
- 触发 UserControl 的 SelectedItemChanged事件
 
    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
● UserControl 的 Resize 事件
当 UserControl 调整大小时,高度 不能小于 15,否则文本将被截断。
    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
当 宽度 调整大小时,按钮的宽度不受影响(19 像素)。但当高度减小或增大时,按钮的高度会相应改变
 (高度 = 15 像素)
 (高度 = 15 像素)
 (高度 = 29 像素)
 (高度 = 29 像素)
● MouseHover 和 MouseLeave 事件
只是一个非常简单的视觉效果。当鼠标悬停在按钮控件上时,鼠标指针和按钮的背景颜色会发生变化
    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
● 属性
一些属性用于添加一些功能...
- BackColor- (读/写)获取或设置 UserControl 的背景颜色
- ForeColor- (读/写)获取或设置 UserControl 的前景色
- MaxItemsDisp- (读/写)获取或设置要显示的行数
- Text- (读/写)获取或设置 UserControl 的文本(标签控件)
- Count- (只读)获取 ListView 的 ItemCollection 中的总项数
    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
● 方法
只需要一个方法
- AddRow- 将字符串添加到ListView 的- ItemCollection。
    public void AddRow(string NewItem)
    {
        _List.Items.Add(NewItem);
    }
    Public Sub AddRow(ByRef NewItem As String)
        _List.Items.Add(NewItem)
    End Sub
● 事件
只有一个事件
- SelectedItemChanged- 当 ListView 的- ItemSelectionChanged事件发生并且选定的项目确实发生更改时触发。
    public event SelectedItemChangedEventHandler SelectedItemChanged;
    public delegate void SelectedItemChangedEventHandler(string Item);
    Public Event SelectedItemChanged(Item As String)
演示项目
是一个简单的 Windows Forms 应用程序,可以在其中测试 UserControl 的所有功能。
UserControl DLL 位于 ...\bin\Debug\CbBox.dll
尽管我更喜欢 **VB.NET**,因为它简单易读(括号和分号很麻烦),
但我不得不承认 **C#** 的性能要高得多……DLL 文件大小
- VB.NET 版本为 28 KB
- C# 版本为 12 KB

- 项目:每个组只能添加一次。
- 其他播放器:输入并添加您想要的任何内容。
- 要**设置属性**,请先点击白色框,然后输入或选择所需的值。颜色将显示一个颜色对话框。
- 事件:每当选定的项目更改时,都会显示一条消息

历史
2016 年 8 月 3 日 - 首次发布。


