繁体   English   中英

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

[英]UserControl as an interface, but visible in the Designer

所以我们有一个C#WinForms项目,其中包含一个包含数以万计的UserControl的Form。 除了自己的特定成员之外,每个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 )。 通过这种方式,我可以通过cName成员引用UserControl ,而不是被所有UserControl成员击败,我只得到ICustomerName那些。

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

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

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

如果SomeCustomerNameUserControl定义如下:

class SomeCustomerNameUserControl : UserControl, ICustomerName
{
}

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

ICustomerName cName = someCustomerNameUserControl1;

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

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

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

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

对于属性,您可能还希望添加[Browsable(false)]以隐藏属性网格中的属性,以及[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]以防止设计器将属性的值写入.designer.cs文件。

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

如果基本成员是虚拟的,您可能想要覆盖它,并让覆盖只是调用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();
    }
}

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

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

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

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

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

这是一个非常蹩脚的例子,但我现在必须走了。 你应该能够更详细地了解这里想法

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");
        }
    }

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

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

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

然后,cName将是对UserControl的唯一引用。

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

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。

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

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

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

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

假设MyUserControl的定义如下:

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

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

public class MyForm : Form
{
    IMyInterface cName;

    public MyForm()
    {
        InitializeComponent();

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM