65.9K
CodeProject 正在变化。 阅读更多。
Home

创建自定义下拉控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (68投票s)

2009年4月7日

CPOL

7分钟阅读

viewsIcon

215733

downloadIcon

9213

解释了如何有效地创建几乎任何类型的下拉控件

QuickColor_Preview.JPG

引言

本文演示了如何有效地创建自己的下拉控件,其灵活性远超通过捕获 Windows 标准 ComboBoxPaint 事件来实现。我见过的大多数免费自定义下拉控件都名不副实。其中许多控件会冻结在屏幕上,无法正常关闭,有些代码臃肿不堪。

我终于下定决心,决定从头开始创建自己的控件。由于日程安排的限制,我没有花很多时间开发设计时支持,但目前的方法效果很好。我们将通过一个创建 QuickColor 控件的例子来进行讲解,该控件就是您在上面看到的。在本文的范围内,我将假设您熟悉继承的概念。

Using the Code

我设计了一个基类用于继承控件,名为 DropDownControl。此类处理所有与下拉相关的函数,因此我们可以专注于自定义控件的外观。

创建新控件的第一步是向项目中添加一个新的 UserControl,切换到代码视图,并将其设置为继承 DropDownControl

public partial class QuickColor : UserControl //<-- replace with DropDownControl { ... } 

切换回设计视图后,您首先会注意到该控件顶部已绘制了一个组合框。下面的剩余区域将用于设计下拉内容。

QuickColor_Blank_Preview.JPG

图 1

让我们深入了解一下它的工作原理。为了使下拉控件在运行时正常工作,我们必须调用 InitializeDropDown() 方法。这应该在 InitializeComponent() 方法之后立即完成。InitializeDropDown 方法接受一个 Control 类型的参数,该参数决定下拉窗口的外观和大小。例如,如果您传递一个宽度为 150 像素、高度为 50 像素的 Label 控件,DropDownControl 的窗口大小将设置为 150 x 50,然后 Label 控件将被加载到下拉窗口中,如图 2 所示。

public partial class QuickColor : DropDownControl
{
     public QuickColor()
     {
         InitializeComponent();
         InitializeDropDown(label1);
     }
}

Parameter_Example.JPG

图 2

显然,这个简单的例子在实际场景中作用不大,但它应该能清楚地演示如何初始化下拉窗口。对于 QuickColor 的例子,我们需要将三个控件加载到下拉窗口中:

  1. “无颜色”按钮
  2. 颜色网格
  3. “更多纯色”按钮

多个下拉项

如果下拉窗口只接受一个参数,我们如何才能有多个项呢?对于多个控件,您应该使用 Panel 控件作为容器项。这就是用于容纳 QuickColor 下拉窗口中的三个控件的内容。如您所知,Panel 类扩展了 Control 类,因此它可以作为 InitializeDropDown() 方法中的参数。

绘制锚点

让用户知道下拉窗口中选定了什么始终是一个好主意。对于 QuickColor,用户需要知道选择了什么颜色。有一个名为 AnchorClientBoundsDropDownControl 属性,其中包含锚点内绘制内容的坐标。如果使用标准的 ClientRectangle 属性,组合框的渲染将被覆盖。使用 AnchorClientBounds 可确保锚点内容直接在组合框内渲染。对于 QuickColor,我设计了一个名为 ColorPanel 的控件,用于锚点区域。ColorPanel 会检查颜色的 alpha 分量(透明度),并在必要时绘制类似 Photoshop 的网格作为背景,使 alpha 分量更加明显。ColorPanel 的边界由 AnchorClientBounds 属性设置,该属性正如您在第一个图像中看到的,完美地适合锚点内。

执行下拉

当用户单击锚点上的任何位置时,窗口会自动下拉。但是,如果您有一个覆盖锚点的控件,例如 ColorPanel,则必须捕获其鼠标事件并以编程方式强制窗口下拉。在 QuickColor 的情况下,我们需要重写 ColorPanelClick 事件来启动下拉。

private void colorPanel1_Click(object sender, EventArgs e)
{
     OpenDropDown();
}

关闭下拉窗口

如果用户单击下拉窗口边界之外的任何位置,窗口会自动关闭。但是,如果鼠标单击发生在下拉窗口边界内,您必须确定关闭窗口的操作,并调用 CloseDropDown() 方法。在 QuickColor 的情况下,从颜色网格中选择一种颜色会关闭下拉窗口。

private void colorGrid1_SelectedIndexChange(object sender, EventArgs e)
{
    this.Color = colorGrid1.Color;
    this.CloseDropDown();
    ...
}

冻结下拉窗口

有时需要将下拉窗口冻结在其下拉状态。还记得 QuickColor 下拉窗口中的“更多纯色”按钮吗?该按钮会启动一个新的对话框,允许用户以更多选项创建自定义颜色。让我们来看一下代码。

private void btnMoreSolidColors_Click(object sender, EventArgs e)
{
    this.FreezeDropDown(false); // false=drop visibility
    ColorChooser frm = new ColorChooser(this.Color);
    if (frm.ShowDialog() != DialogResult.Cancel && !frm.Color.Equals(this.Color))
    {
        this.Color = frm.Color;
        this.CloseDropDown();
        ...
    }
    this.UnFreezeDropDown();
}

注意 FreezeDropDown() 方法。这是怎么回事?如果在从下拉窗口打开新窗体时未调用 FreezeDropDown(),下拉窗口将关闭并终止从中启动的任何窗体。如果您是技术爱好者,想了解为什么必须调用此方法,请阅读下面的部分。其余人可以跳过(无聊的)部分。

下拉窗口实现了 IMessageFilter,并监听下拉期间发送的所有应用程序消息。当创建消息时,下拉窗口使用静态 Form 方法 Form.ActiveForm.Equals() 来确保它是活动窗体。当另一个窗体变为活动窗体时,下拉窗口将关闭并将其自身作为 Windows 消息侦听器移除。调用 FreezeDropDown() 方法会导致下拉窗口暂停活动窗体检查,并强制其保持打开状态。(请注意,我不建议在生命周期比下拉窗口长的控件中使用 IMessageFilter。)

DropState 属性

在设计复杂的下拉控件时,您可能会遇到需要了解下拉窗口状态的情况。有一个名为 DropState 的属性,您可以随时检查它来确定下拉窗口的状态。注意DropState 是只读的,因此您不能使用此属性更改下拉窗口的状态。

下面是演示项目中包含的其他自定义下拉控件的一些缩略图。DropState 属性在关闭这两个下拉控件时非常有用。EmployeePicker(图 3)在 ListViewSelectedIndexChange 事件触发时关闭。但是,当下拉窗口正在关闭并将其从控件列表中删除时,会出现一个问题。SelectedIndexChange 事件再次触发!这反过来会导致 CloseDropDown() 方法被递归调用,从而导致抛出异常。为了避免这种情况,我们在 SelectedIndexChange 处理程序中简单地检查 DropState 属性。如果窗口处于 DroppingClosing 状态,我们就会退出处理程序。

private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (this.DropState == eDropState.Dropping || this.DropState == eDropState.Closing)
        return;
    this.CloseDropDown();
    ...
}

AddtionalControls_Preview.JPG

图 3

上述控件是快速创建的演示。它们唯一的目的是激发您对可用可能性的想象。

创建属性更改事件

Windows ComboBox 控件会触发 SelectedIndexChanged 事件来通知侦听器选择了已更改。我们也需要触发某种事件来通知侦听器选择已更改。DropDownControl 提供了一个通用事件 PropertyChanged,但我强烈建议您为自定义控件创建更具意义的事件。对于 QuickColor,我创建了一个 ColorChanged 事件。如果其他开发人员在查看 QuickColor 的事件以进行捕获时,他们会比 PropertyChanged 事件更快地发现 ColorChanged 事件。但是,如果您决定坚持使用 PropertyChanged 事件,可以通过受保护的 protected 方法 OnPropertyChanged() 来触发它。

最后的 remarks

我相信本文将为您创建自己的下拉控件提供一个良好的开端。如果您发现任何可以改进的功能,请告诉我。我也很乐意听到您从本文创建的任何控件。

类似 Photoshop 的颜色对话框的概念来自 Danny Blanchard。我对原始代码进行了几处修改,使其更紧凑、更高效。Danny,做得好!

历史

  • 2010/7/19:将受保护的 protected 方法“GetDropDownLocation()”更改为“GetDropDownBounds()”。该方法现在通过检查其待处理的屏幕边界来验证下拉窗口的位置。特别感谢 Marco Mastropaolo 的建议。
    现在,如果 ParentForm 被移动,下拉窗口会自动更新其位置。感谢 Dan Randolph 的绝佳建议!
  • 2009/11/6:修复了当操作系统不支持 VisualStyles 时出现的组合框渲染问题。感谢 Mathiyazhagan 引起了我们的注意。
© . All rights reserved.