UserControl作为界面,但在Designer中可见

时间:2009-04-24 01:00:39

标签: c# .net winforms user-controls custom-controls

所以我们有一个C#WinForms项目,其中包含一个包含大量UserControl s的表单。除了自己的特定成员之外,每个UserControl自然会公开所有UserControl方法,属性等。

我一直在想减少处理这些UserControl的复杂性的一种方法是通过接口访问它们。因此,而不是拖放将UserControl放在窗体上,在构造函数中是这样的:

public class MyGiantForm
{
    ICustomerName cName;

    public MyForm()
    {
        InitializeComponent();

        var uc = new SomeCustomerNameUserControl();
        this.Controls.Add(uc);
        cName = uc;
    }
}

SomeCustomerNameUserControl自然地实现ICustomerNameICustomerName包含我真正关心的特定属性(例如FirstNameLastName)。通过这种方式,我可以引用UserControlcName成员,而不是被所有UserControl成员击败,我只会获得ICustomerName中的成员。< / p>

一切都很好,但问题是,如果我这样做,我在Designer中看不到SomeCustomerNameUserControl。有没有人知道我可以做到这一点,但仍然看到表单设计图面上的UserControl

编辑:执行此操作的一种方法是将控件放在基本表单上,这种方法并不过分复杂。默认情况下(在C#中)控件成员是私有的。然后我为每个控件创建一个属性,通过接口公开它。

但是,我会对其他一些方法感兴趣,即使它更复杂。似乎有一些方法可以用IDesignerHost来做,但我找不到任何适用的例子。

8 个答案:

答案 0 :(得分:6)

如果SomeCustomerNameUserControl定义如下:

class SomeCustomerNameUserControl : UserControl, ICustomerName
{
}

您仍然可以在设计器(创建someCustomerNameUserControl1)中删除此控件,并在需要时执行此操作:

ICustomerName cName = someCustomerNameUserControl1;

也许我错过了什么,但我认为就这么简单。

答案 1 :(得分:6)

有一种方法可以实现你想要的东西 - 隐藏你不想看到的成员 - 但让它自动应用,而不需要其他人使用自定义界面进行合作。您可以通过重新引入您不想看到的所有成员,并使用属性标记它们来实现。

这就是Windows Forms所做的事情,例如,基类属性对特定的后代没有任何意义。例如,Control有一个Text属性,但是TextControl对一个Text属性没有意义。因此,TabControl会覆盖Text属性,并向其覆盖添加属性,说“顺便说一下,不要在Property Grid或Intellisense中显示我的Text属性”。该物业仍然存在,但由于你从未见过它,它不会妨碍你。

如果向成员(属性或方法)添加 [EditorBrowsable(EditorBrowsableState.Never)] 属性,则Intellisense将不再在其代码完成列表中显示该成员。如果我正确理解你的问题,这是你想要实现的重大事情:使应用程序代码难以偶然使用该成员。

对于属性,您可能还想添加 [可浏览(false)] 以隐藏属性网格中的属性,并 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 防止设计者将属性的值写入.designer.cs文件。

这会使意外使用方法/属性变得非常困难。但是,它们仍然不是保证。如果您确实需要保证,那么也要输入 [已废弃] 属性,并使用“将警告视为错误”进行构建 - 然后您就会得到保护。

如果基本成员是虚拟的,您可能想要覆盖它,并让覆盖只是调用base。不要抛出异常,因为在正常的事件过程中,被覆盖的成员可能会被基类调用。另一方面,如果基本成员不是虚拟的,那么你想使用“new”而不是“override”,你可以决定你的实现应该调用base,还是只抛出异常 - 没有人应该使用无论如何你重新引入的成员,所以无所谓。

public class Widget : UserControl
{
    // The Text property is virtual in the base Control class.
    // Override and call base.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The Text property does not apply to the Widget class.")]
    public override string Text
    {
        get { return base.Text; }
        set { base.Text = value; }
    }

    // The CanFocus property is non-virtual in the base Control class.
    // Reintroduce with new, and throw if anyone dares to call it.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The CanFocus property does not apply to the Widget class.")]
    public new bool CanFocus
    {
        get { throw new NotSupportedException(); }
    }

    // The Hide method is non-virtual in the base Control class.
    // Note that Browsable and DesignerSerializationVisibility are
    // not needed for methods, only properties.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("The Hide method does not apply to the Widget class.")]
    public new void Hide()
    {
        throw new NotSupportedException();
    }
}

是的,这是一项相当多的工作,但你只需要做一次......每个成员,每班......嗯,是的。但是,如果那些基层成员真的不适用于你的班级,并且拥有它们会导致混乱,那么可能值得去努力。

答案 2 :(得分:4)

'我希望ICustomerName成为唯一的选项,用于访问UserControl的变量。这个想法是开发人员不必“只记得”来施放它。'

您遇到的问题是您的表单及其托管的控件有两种完全不同的用途。 Visual Studio或winforms中没有内置技巧可以为您解决这个问题。这可能是有可能的,但是有一种更清晰,面向对象的方式来分离与控件交互的两种方法。

如果您想隐藏这些对象从UserControl继承的事实,并且只想将它们视为IDoSomeThingYouShouldDealWith,则需要将处理表示问题(设计器+ UI逻辑)的逻辑与您的逻辑分开商业逻辑。

你的表单类,应该正确处理控件如UserControls,对接,锚定等等,这里没什么特别的。您应该将需要处理ICustomerName.FirstName = etc的所有逻辑放入一个完全独立的类中。这个类不关心或不了解字体和布局,它只知道还有另一个可以呈现客户名称的实例;或者将DateTime作为“选择出生日期”正确控制等。

这是一个非常蹩脚的例子,但我现在必须走了。您应该能够获得idea covered here in more detail

public interface ICustomerName
    {
        void ShowName(string theName);
    }

    public partial class Form1 : Form, ICustomerName
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region ICustomerName Members

        public void ShowName(string theName)
        {
            //Gets all controls that show customer names and sets the Text propert  
            //totheName
        }

        #endregion
    }

    //developers program logic into this class 
    public class Form1Controller
    {
        public Form1Controller(ICustomerName theForm) //only sees ICustomerName methods
        {
            //Look, i can't even see the Form object from here
            theForm.ShowName("Amazing Name");
        }
    }

答案 3 :(得分:3)

使用设计器添加UserControl后,可以在“属性”窗口中将GenerateMember设置为false以禁止生成成员。

然后,您可以在构造函数中使用其他一些技术来分配您的cName引用,例如:

foreach(Control control in this.Controls)
{
    cName = control as ICustomerName;
    if (cName != null) break;
}

cName将是对UserControl的唯一引用。

答案 4 :(得分:1)

您可以编写一个扩展方法,允许您返回实现接口的表单上的任何控件。

public static class FormExtensions
{
    public static IDictionary<string, T> GetControlsOf<T>(this Form form) 
           where T: class
    {
        var result = new Dictionary<string, T>();
        foreach (var control in form.Controls)
        {
            if ((control as T) != null)
                result.Add((control as T).Tag, control as T);
        }
        return result;
    }
}

然后在您的表单中,您可以随意调用它:

this.GetControlsOf<ICustomerName>()["NameOfControlHere"];

如果它返回多个用户控件,您需要处理一些方法,可能通过向接口添加Tag属性来唯一地跟踪每个用户控件或其他内容,如此

public partial class UserControl1 : UserControl, ICustomerName
{
     public string Tag { get { return this.Name; } }
}

然后,您可以将设计器上的用户控件拖放到表单上。 Tag将始终返回控件的名称,这将允许您通过IDictionary的界面直接访问控件。你是开发人员可以在控件的名称中放置他们想要的任何唯一标识符,它将继续进行到接口。

此外,应该注意的是,此方法还允许您在解决方案中的所有表单上使用此方法。

您需要做的唯一事情就是将GenerateMember设置为false。

答案 5 :(得分:0)

你可以像鲍勃所说的那样做,但是在构造函数中分配所有成员变量,然后将它放在一个地方。

答案 6 :(得分:0)

您似乎想要实现中介模式。您无需直接处理每个庞大的UserControl,而是通过调解器与它们进行交互。每个介体都会定义您希望从每个控件中看到的纤薄界面。这可以通过使您的设计更加明确和简洁来降低整体复杂性。例如,您不需要在其中一个控件上使用20个属性和50个方法。相反,您将处理该控件的介体,该控件定义了您真正关心的2个属性和5个方法。一切都会出现在设计师中,但是你应用的其他部分不会与那些控件互动 - 他们会与调解员互动。

这种方法的一大优势是它大大简化了您的维护。如果您因为实现不好而决定需要重写MyCrappyUserControl,则只需更新该控件的中介类。与控件交互的所有其他类都通过介体完成,并且不会改变。

最终归结为纪律:你和你的团队需要足够的纪律来使用调解器/接口/而不是直接控制。如果您的团队处于学科规模的低端,则由领导者程序员进行代码审查。

答案 7 :(得分:-3)

假设MyUserControl的定义如下:

class MyUserControl : UserControl, IMyInterface
{
    // ...
}

然后在你的表格中,你应该有这样的东西:

public class MyForm : Form
{
    IMyInterface cName;

    public MyForm()
    {
        InitializeComponent();

        cName = new MyUserControl();
        Controls.Add((UserControl)cName);
    }
}

这样,cName是访问这个usercontrol实例的唯一方法。