所以我们有一个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
自然地实现ICustomerName
,ICustomerName
包含我真正关心的特定属性(例如FirstName
和LastName
)。通过这种方式,我可以引用UserControl
到cName
成员,而不是被所有UserControl
成员击败,我只会获得ICustomerName
中的成员。< / p>
一切都很好,但问题是,如果我这样做,我在Designer中看不到SomeCustomerNameUserControl
。有没有人知道我可以做到这一点,但仍然看到表单设计图面上的UserControl
?
编辑:执行此操作的一种方法是将控件放在基本表单上,这种方法并不过分复杂。默认情况下(在C#中)控件成员是私有的。然后我为每个控件创建一个属性,通过接口公开它。
但是,我会对其他一些方法感兴趣,即使它更复杂。似乎有一些方法可以用IDesignerHost来做,但我找不到任何适用的例子。
答案 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实例的唯一方法。