7

所以我们有一个 C# WinForms 项目,其中的 Form 包含一个 bazillion UserControls。除了自己的特定成员外,每个都UserControl自然地公开所有方法、属性等。UserControl

我一直在想降低处理这些UserControls 复杂性的一种方法是通过接口访问它们。因此,与其拖放以将其UserControl放在表单上,​​不如在构造函数中使用类似这样的内容:

public class MyGiantForm
{
    ICustomerName cName;

    public MyForm()
    {
        InitializeComponent();

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

SomeCustomerNameUserControl实现ICustomerName,自然,并ICustomerName包含我真正关心的特定属性(比如,FirstNameLastName)。通过这种方式,我可以UserControl通过cName成员引用,而不是被所有UserControl成员击倒,我只得到ICustomerName.

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

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

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

4

8 回答 8

6

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

例如,当基类属性对特定后代没有任何意义时,这就是 Windows 窗体所做的。例如,Control 具有 Text 属性,但 Text 属性在 TabControl 上毫无意义。所以 TabControl 覆盖 Text 属性,并为其覆盖添加属性,说“顺便说一下,不要在属性网格或 Intellisense 中显示我的 Text 属性。” 该属性仍然存在,但是由于您从未见过它,因此它不会妨碍您。

如果将[EditorBrowsable(EditorBrowsableState.Never)]属性添加到成员(属性或方法),则 Intellisense 将不再在其代码完成列表中显示该成员。如果我正确理解了您的问题,那么这就是您要实现的重要目标:使应用程序代码很难意外使用该成员。

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

这些将使意外使用该方法/属性变得非常困难。但是,它们仍然不能保证。如果您确实需要保证,那么也加入一个[Obsolete]属性,并使用“将警告视为错误”进行构建——那么您就可以得到照顾了。

如果基本成员是虚拟的,您可能想要覆盖它,并让您的覆盖简单地调用 base. 不要抛出异常,因为被覆盖的成员可能会在正常的事件过程中被基类调用。另一方面,如果 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();
    }
}

是的,这是一项相当多的工作,但你只需要做一次......每个成员,每个班级......嗯,是的。但是如果这些基类成员真的不适用于你的类,并且让他们在那里会引起混乱,那么可能值得努力。

于 2009-05-05T04:27:21.263 回答
6

如果SomeCustomerNameUserControl是这样定义的:

class SomeCustomerNameUserControl : UserControl, ICustomerName
{
}

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

ICustomerName cName = someCustomerNameUserControl1;

也许我错过了一些东西,但我认为就这么简单。

于 2009-04-24T01:36:52.107 回答
4

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

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

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

你的表单类,应该正确地处理用户控件、停靠、锚定等控件,这里没什么特别的。您应该将需要处理 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");
        }
    }
于 2009-05-08T20:49:09.777 回答
3

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

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

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

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

于 2009-05-03T19:36:41.610 回答
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。

于 2009-05-08T19:07:15.543 回答
0

几乎看起来你想要实现一个中介者模式。您不必直接处理每个数不胜数的 UserControl,而是通过中介与它们进行交互。每个中介都会定义您希望从每个控件中看到的苗条界面。这将通过使您的设计更加明确和简洁来降低整体复杂性。例如,您不需要其中一个控件提供 20 个属性和 50 个方法。相反,您将处理该控件的中介,它定义了您真正关心的 2 个属性和 5 个方法。一切仍会显示在设计器中,但应用程序的其他部分不会与这些控件交互——它们会与中介交互。

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

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

于 2009-05-08T09:03:13.680 回答
0

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

于 2009-04-24T06:49:43.667 回答
-3

假设 MyUserControl 定义如下:

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

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

public class MyForm : Form
{
    IMyInterface cName;

    public MyForm()
    {
        InitializeComponent();

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

这样,cName 是访问我们用户控件的这个实例的唯一方法。

于 2009-05-10T16:22:23.230 回答