设计嵌套控件






4.94/5 (35投票s)
一篇关于在设计时允许嵌套控件接受子控件的文章。
引言
本文演示了如何允许一个作为另一个Control
子控件的Control
在设计时接受控件的拖放。 这不是一篇很长的文章,代码也不多,这可能不是“官方”的最佳方法。 然而,它确实有效,并且在我能够测试的范围内是稳定的。
背景
自从 VS2003 以来,我大部分时间都在业余时间使用 C#。在那期间,我曾偶尔尝试设计各种类型的控件,这些控件又包含其他控件。 当这些控件被放置在Form
或Panel
或其他控件上时, nunca 就不可能将TextBox
等控件拖放到内部控件上,并将其添加为该控件的子控件。 我曾寻找解决这个问题的方法,但从未找到。 最近,我在 C# 论坛上试图帮助别人时,与其说是运气,不如说是判断,我找到了一个我认为能解决问题的方法。 在我帮助完其他 CPian 后,我匆忙制作了一个非常快速的演示并写了这篇文章。 我写这篇文章部分是因为我终于找到了解决问题的方法,但主要是因为我没有在任何地方看到过这种技术的示例,我认为将其传播出去很重要。
Using the Code
本文配套的演示应用程序由应用程序主窗体和两个控件库组成。 其中第一个NestedControlDesignerLibrary
包含TestControl
和TestControlDesigner
的代码。
TestControl
由一个UserControl
组成,该UserControl
又包含两个Panel
控件。 一个停靠在顶部,包含一个Label
用作整个控件的标题;第二个是我想让它能够接受子控件的控件,它占满了剩余的空间。
这是TestControl
的代码。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace NestedControlDesignerLibrary
{
/// <summary>
/// A test Control to demonstrate allowing nested Controls
/// to accept child controls at design time.
/// </summary>
[
Designer(typeof(NestedControlDesignerLibrary.Designers.TestControlDesigner))
]
public partial class TestControl : UserControl
{
public TestControl()
{
InitializeComponent();
}
#region TestControl PROPERTIES ..........................
/// <summary>
/// Surface the Caption to allow the user to
/// change it
/// </summary>
[
Category("Appearance"),
DefaultValue(typeof(String), "Test Control")
]
public string Caption
{
get
{
return this.lblCaption.Text;
}
set
{
this.lblCaption.Text = value;
}
}
[
Category("Appearance"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)
]
/// <summary>
/// This property is essential to allowing the designer to work and
/// the DesignerSerializationVisibility Attribute (above) is essential
/// in allowing serialization to take place at design time.
/// </summary>
public Panel WorkingArea
{
get
{
return this.pnlWorkingArea;
}
}
#endregion
}
}
它很简单,但它实际上并没有做什么;作为一个控件,它几乎是无用的。 它的唯一目的是演示这种方法。
有两个重要部分
- 类声明正上方的
Designer
属性,它告诉 VS Forms Designer 该控件有自己的设计器以及在哪里找到它。 WorkingArea
属性及其相关属性。 此属性在 Designer 中用于设置控件托管功能。 该属性确保当控件在设计时拖放到WorkingArea
上时,它会被序列化。 作为序列化过程的一部分,Designer 确保拖放的控件会被自动添加到WorkingArea
的Controls
集合中,而不是主控件的Controls
集合中。
设计器代码
namespace NestedControlDesignerLibrary.Designers
{
public class TestControlDesigner : ParentControlDesigner
{
public override void Initialize(System.ComponentModel.IComponent component)
{
base.Initialize(component);
if (this.Control is TestControl)
{
this.EnableDesignMode((
(TestControl)this.Control).WorkingArea, "WorkingArea");
}
}
}
}
就是这样。 我发现一个困扰我多年的问题可以用这么少的代码解决,真是令人沮丧。 我绝不是 Designer 方面的专家,但即使是我也能理解这一点。 TestControlDesigner
继承自ParentControlDesigner
,根据 MSDN 的说法,它是
用于扩展支持嵌套控件的Control
的设计模式行为的基设计器类。
由于我们这里处理的是嵌套控件,这似乎是合适的。 当设计器初始化时,它会调用其基类的Initialize
以确保默认行为的发生,然后它会调用EnableDesignMode
,并将要接收设计时拖放控件的控件指定为WorkingArea
。 主控件需要被转换为正确的控件类型,因为设计器通常将此信息存储为对Control
的引用,并使用名称作为字符串向用户公开控件。 可以为每个需要设计时功能的子控件重复调用EnableDesignMode
。 有关更多信息,请参阅 MSDN 上的 EnableDesignMode。
上面呈现的代码和控件存在一个显著的缺点。WorkArea
在属性窗口中的行为类似于SplitContainer
的panel1
和panel2
。 这意味着,如果展开它,您将可以看到它的所有公共属性。 这包括Dock
属性,对于这个控件来说,这没有意义,因为WorkArea
的停靠是设计的基础,允许用户更改它简直是愚蠢的。
在座的各位经验丰富者将知道如何克服这个问题,但对于新的编码者,我在ImprovedNestedControlDesignLibrary
中创建了一个ImprovedTestControl
。与普通控件唯一的区别是,在改进版本中,WorkArea
是一个继承自Panel
的 User Control,而不是一个简单的Panel
。我选择这样做是因为它使我能够为其提供自己的 Designer,这是我选择用来隐藏不需要的属性的技术。还有很多其他方法可以做到这一点,并且可以在 CodeProject 上找到一些优秀的关于如何做到这一点的文章。在主页的搜索框中输入“hiding inherited properties”然后按 Enter。我找到的第一篇文章是 Hiding Inherited Properties from the PropertyGrid,作者是Shaun Wilde,专门处理这个问题;其他文章或多或少也涉及到这个问题。请随意浏览并找到您喜欢的方法来用于您自己的代码。
总之,这是新的WorkArea
的代码。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ImprovedNestedControlDesignerLibrary
{
[
Designer(typeof(ImprovedNestedControlDesignerLibrary.Designers.WorkingAreaDesigner))
]
public partial class WorkingAreaControl : Panel
{
public WorkingAreaControl()
{
InitializeComponent();
base.Dock = DockStyle.Fill;
}
}
}
如前所述,此控件继承自Panel
。
由于我隐藏了Dock
属性的方式,在设计ImprovedTestControl
时它将不可用,因此有必要在构造函数中对其进行适当的设置。就这样了,除了 Designer。
这是代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms.Design;
namespace ImprovedNestedControlDesignerLibrary.Designers
{
public class WorkingAreaDesigner : ScrollableControlDesigner
{
protected override void PreFilterProperties(
System.Collections.IDictionary properties)
{
properties.Remove("Dock");
base.PreFilterProperties(properties);
}
}
}
这个 Designer 所做的就是覆盖PreFilterProperties
方法。该方法只有一个参数,即其控件所有公共属性的Dictionary
。因此,只需要删除任何您不想显示在属性窗口中的属性,然后将现在更精简的Dictionary
传递给基方法。搞定!
演示应用程序是一个简单的窗体,带有一个TestControl
和一个ImprovedTestControl
。
添加TestControl
后,我在上面放了一个ComboBox
并为其添加了一些项。 对于ImprovedTestControl
,我添加了一个 Label 和一个ListBox
,同样也添加了一些项。
关注点
在撰写本文的过程中,我学到了许多有趣的东西。 尤其是,我学到了帮助他人也能帮助自己。 我也得到了证实,微软关于 Designer 主题的文档是多么糟糕。
以下是一些关于 Windows Forms 设计过程的文章链接。
我希望这篇文章能让你们中的一些人觉得有用,因为我找到这个问题的解决方案时高兴极了。
历史
- 版本 1: 2009 年 7 月 1 日。