有关用户控件,嵌套控件和封装的问题. [英] A question about usercontrols, nested controls and encapsulation.

查看:103
本文介绍了有关用户控件,嵌套控件和封装的问题.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近,我一直在为我的公司开发一些WinForms控件.有时我继承一个已经存在的控件,有时又继承了一个带有其他控件的复合控件.但是,我有一个问题,或更确切地说是一个问题.
现在我的问题在于如何正确封装复合用户控件中包含的控件?
假设我有ControlHost和ControlChild. ControlHost是承载ControlChild的复合控件.现在,ControlChildEvent是ControlChild中的一个重要事件.所以我在ControlHost中创建一个事件,并在ControlChild引发事件时将其触发?这似乎是一项艰巨的工作,如果ControlChild曾经获得一个新事件(或Property,Method等),我也需要更改ControlHost,而这并不是我真正想要的!
因此,我可以在ControlHost中仅具有一个(ReadOnly)属性,该属性公开ControlChild,但在这种情况下,ControlHost的用户可能会以我的ControlHost无法应付的方式更改ControlChild的外观...

在控件中处理此问题的正确方法是什么?我是否应该将ControlChild的所有方法都真正暴露给ControlHost,并且应该在ControlChild更改时更改ControlHost?

谢谢.

Recently I have been developing some WinForms Controls for my company. Sometimes I inherit an already existing control and sometimes it''s a composite control with different other controls on it. However, I have a problem, or rather a question.
Now my problem lies in how to properly encapsulate a Control contained in a composite user control?
Let''s say I have ControlHost and ControlChild. ControlHost is a composite control that hosts ControlChild. Now an important event in ControlChild is the ControlChildEvent. So I create an Event in ControlHost and have it fired when ControlChild raises it''s event? This seems like a lot of work, and if ControlChild ever gets a new event (or Property, Method etc.) I need to change ControlHost too, and that''s not really what I want!
So I could just have a (ReadOnly) Property in ControlHost that exposes ControlChild, but in this case the user of ControlHost could alter the appearance of ControlChild in ways that my ControlHost can''t cope with...

What is a proper way of dealing with this in Controls? Should I really expose every method of ControlChild to ControlHost and should I change ControlHost when ControlChild changes?

Thanks.

推荐答案

好,让我们从一个示例开始,但首先,我想拥有对主机控件进行委派或事件处理的技术不仅适用于Forms,而且适用于WPF,更广泛地说,在所有情况下,如果您组成了启用事件的成员,这些成员应该保持私有.我在解决方案1中进行了讨论.

让我们考虑一些Forms示例,其中包含一些私有控件的用户控件,并公开一些事件.让我们考虑一下由设计者以传统方式创建的情况:

OK, let''s start from some example, but first of all, I want to have the techniques of delegation or events to a host control which would work not only with Forms, but also with WPF, and, more generally, in all cases where you have composed event-enabled members which should remain private. I discussed it in Solution 1.

Let''s consider some Forms example, a user control with some private controls in it, and expose some events. Let''s consider the case where it is created traditionally, with the designer:

partial class HostControl {
    private void InitializeComponent() { /* ... */ }
    //...
    private System.Windows.Forms.Label labelTitle;
    private System.Windows.Forms.TextBox textBoxTitle;
    private System.Windows.Forms.Panel panelSpacer;
    private System.Windows.Forms.Button buttonSubmit;
}

public partial class HostControl : UserControl {
    public HostControl() {
        InitializeComponent();
        Setup(); //see below
    } //HostControl
} //class HostControl



我宁愿不要触摸自动生成的代码.最后的部分声明是在开发人员的代码中添加的位置,但我更喜欢仅对其进行最少的改动.我刚刚将调用添加到Setup中,该方法将为同一个类添加另一个局部声明,以分隔我们自己编写的代码.这样,设计师就不会碰它.

另外,我想结束有关使用设计器处理事件的讨论.我们不应该使用它并用代码编写所有内容.一旦与设计师合作,我们立即将其变为手动工作,并且会忘记使我们的工作自动化的所有可能性.不,只能通过代码.对于许多其他方面也是如此,但是我们的目标现在不同.我们只需要介绍HostControl的一些事件,表格的开发者就可以处理子事件.

首先,让我们尝试在不使用任何实用程序的情况下即刻进行此操作,然后再来看一下,我们可以找到更好的方法.例如:



I prefer not touching auto-generated code. Last partial declaration is where the developer''s code is added, but I prefer touch it only to the bare minimum. I just added the call to Setup, a method I am going to put in yet another partial declaration for the same class, to separate the code we are writing with our own hands. In this way, designer will not touch it.

Also, I want to close the discussion of using the designer for working with events. We should not use it and write everything in code. As soon as we do such things with designer, we immediately make it a manual work and can forget every possibility to automate our work. No, only through the code. This is true for many other aspects, but our goal is different right now. We only need to introduce some events of HostControl the way the developer of the form can handle events of the children.

To start with, let''s try to do it ad hoc, without using any utility, and see it we can find a better way later. For example:

public partial class HostControl : UserControl {

    void Setup() {
        this.buttonSubmit.Click += (sender, eventArgs) => {
            if (SubmitButtonClick != null) SubmitButtonClick.Invoke(sender, eventArgs);
        };
        this.textBoxTitle.TextChanged += (sender, eventArgs) => {
            if (TitleChanged != null) TitleChanged.Invoke(sender, eventArgs);
        };
        this.panelSpacer.MouseMove += (sender, eventArgs) => {
            if (PanelMouseMove != null) PanelMouseMove.Invoke(sender, eventArgs);
        };
    } //Setup

    public event EventHandler<EventArgs> SubmitButtonClick;
    public event EventHandler<EventArgs> TitleChanged;
    public event EventHandler<MouseEventArgs> PanelMouseMove;

} //class HostControl



而已.从这一刻开始,表单的开发人员可以使用宿主控件的事件SubmitButtonClickTitleChangedPanelMouseMove处理子事件.而且,可以添加多个处理程序,并且可以向同一控件添加更多的公开事件".问题已经解决了.我认为这还不错.我为每个控件的每个事件仅添加了三行(可以是4行或5行,具体取决于格式,甚至是1行).

我只想显示它对.NET v.2.0的外观,其中无法使用lambda样式,但是已经引入了匿名方法. (我认为我们可以合理地假设v.2.0之前的.NET版本不存在.)对于此版本,Setup应该以更长的方式重写:



That''s it. From this moment, the developer of the form can use the events SubmitButtonClick, TitleChanged or PanelMouseMove of the host control to handle the events of the children. Moreover, one can add multiple handlers, and we can add more "exposed events" to the same controls. The problem is solved. I think it is not bad already. I added just three lines per event per control (could be 4 or 5 lines, depending on formatting style, even 1).

I only want to show how it might look for .NET v.2.0, where lambda style could not be used, but anonymous methods were already introduced. (I think we can reasonable assume that .NET versions prior to v.2.0 do not exist.) For this version, Setup should be re-written in a little longer way:

void Setup() {
    this.buttonSubmit.Click += delegate(object sender, EventArgs eventArgs) {
        if (SubmitButtonClick != null) SubmitButtonClick.Invoke(sender, eventArgs);
    };
    this.textBoxTitle.TextChanged += delegate(object sender, EventArgs eventArgs) {
        if (TitleChanged != null) TitleChanged.Invoke(sender, eventArgs);
    };
    this.panelSpacer.MouseMove += delegate(object sender, MouseEventArgs eventArgs) {
        if (PanelMouseMove != null) PanelMouseMove.Invoke(sender, eventArgs);
    };
} //Setup



我认为这也还不错. 很多工作在哪里?"

现在,我想说明一下这种方法几乎是最优的.

我们可以为实用程序使用什么以使其更加通用.首先,我们可以抽象出宿主控件的事件.让我们看看:



I think this is also not too bad. Where is "a lot of work"?

Now, I want to illustrate that this approach is nearly optimal.

What can we use for a utility to make it more universal. First of all, we can abstract out the events of the host control. Let''s see:

public static class EventDelegationUtility<ARG> where ARG : EventArgs {
    public static void Invoke(EventHandler<ARG> eventInstance, object sender, ARG eventArgs) {
        if (eventInstance != null)
            eventInstance.Invoke(sender, eventArgs);
    } //Invoke
} //EventDelegationUtility



首先,请注意,它eventInstance是委托实例的类型(不要与委托类型混在一起);并且我们需要使用它来传递表示主机控件事件的事件对象的实例.可能吗.是的!但这仅是因为应在宿主控件的代码中使用此代码,而在其他任何地方都不能使用.这就是为什么我们可以为实施事件"做这件事的原因.
我们还可以抽象目标事件实例",例如buttonSubmit.ClickpanelSpacer.MouseMove吗?

不!我们不能编写诸如void Delegate(ref EventHandler<EventArgs> implementingEvent, EventHandler<EventArgs> targetEvent, …)之类的东西,然后传递诸如buttonSubmit.Click之类的事件实例.我们只能使用声明类中的事件,否则编译器会告诉use我们只能在"= +"或-="运算符的左侧"使用它.有充分的理由.与常规"委托实例相反,这是事件实例的防呆功能之一.请查看我过去的回答:
由于我们有多播委托,所以为什么我们需要事件吗? [ ^ ].

因此,让我们忘记它,看看上一个示例中显示的实用程序的使用情况如何:



First of all, note that it eventInstance is of the type of the delegate instance (don''t mix up with delegate types); and we need to use it to pass an instance of the event object representing event of the host control. Is it possible. Yes! but only because this code should be used in the code of the host control, nowhere else. That''s why we can do it for "implementing events".

Can we also abstract our "target event instances", such as buttonSubmit.Click, panelSpacer.MouseMove?

No! We cannot write something like void Delegate(ref EventHandler<EventArgs> implementingEvent, EventHandler<EventArgs> targetEvent, …) and then pass event instance like buttonSubmit.Click. We can only work with events in the declaring class, otherwise the compiler will tell use that we only can use it "on the left of ''=+'' or ''-='' operator". For the good reasons. This is one of the fool-proof features of event instances, as opposed to the "regular" delegate instances. Please see my past answer:
Since we have multicast delegates, why do we need events?[^].

So, let''s forget it and see how the use of the utility shown in the last sample will look like:

void Setup() {
    this.buttonSubmit.Click += (sender, eventArgs) => {
        EventDelegationUtility<EventArgs>.Invoke(this.SubmitButtonClick, sender, eventArgs);
    };
    this.textBoxTitle.TextChanged += (sender, eventArgs) => {
        EventDelegationUtility<EventArgs>.Invoke(this.TitleChanged, sender, eventArgs);
    };
    this.panelSpacer.MouseMove += (sender, eventArgs) => {
        EventDelegationUtility<MouseEventArgs>.Invoke(this.PanelMouseMove, sender, eventArgs);
    };
} //Setup


真的好多了吗?我不会这样说.它的时间要长一点.有人会说这更好,但我不会说这证明了努力的合理性.

现在,我们可以抽象子类型吗?好吧,我们可以,但是那会导致太多无聊的声明.如果我们需要编写许多相同类型的用户控件并多次处理同一事件集,那么这可能会有所回报.这不是很平常的工作,因此不应在普遍水平上进行.如果是这样的重复性工作,是的,可以完成,但是请看一下它的外观:


Is it really much better? I would not say so. It''s event a bit longer. Some would say it''s better, but I would not say it justifies the effort.

Now, can we abstract our the children types? Well, we can, but that would lead to too many boring declarations. It could pay off if we need to write many user controls of the same set of type and handle the same set of events many times. This is not very usual work, so it should not be done on a universal level. If this repetitive work is the case, yes, it could be done, but just see how it might look:

internal static void ExposeClick(Control target, EventHandler<EventArgs> implementor) { /* ... */ } 
internal static void ExposeTextChanged(TextBox target, EventHandler<EventArgs> implementor) { /* ... */ } 
internal static void ExposeMouseMove(Control target, EventHandler<MouseEventArgs> implementor) { /* ... */ } 
internal static void ExposeMouseUp(Control target, EventHandler<MouseEventArgs> implementor) { /* ... */ } 
internal static void ExposeMouseDown(Control target, EventHandler<MouseEventArgs> implementor) { /* ... */ }
// yes, "internal"; using this approach in the universal-layer library would not pay off, I think...



你能看到它的去向吗?问题在于方法之间的主要区别只是事件的名称.没有办法将其抽象出来:它不是任何实例,也不是使用泛型抽象出来的类型.这种方法只能在我们处理由具体应用程序指示的有限事件集并多次重复使用的情况下才能获得回报.这不是很典型,在某些情况下只能在应用程序级别决定.

结论:首先显示的方法仍然是最佳方法.

—SA



Can you see where it goes? The problems is that the major difference between the methods is just the name of event. There is no way to abstract it out: it is not the instance of anything, and this is not the type to be abstracted out using generics. This approach can only payoff it we handle limited set of events dictated by concrete application and re-use it many times. This is not very typical, can be decided only on the application level only in some cases.

Conclusion: the approach shown first is still the best.

—SA


我认为将ControlChild事件暴露给ControlHost界面的方式已经描述为很多工作"最合适.

也许您可以使用以下技术做得更好:您可以创建一些表示每个嵌套控件的接口,并将其全部实现在宿主控件中.它不会建立一种减少开发工作量的机制(实际上,将需要一些额外的工作),但是它可以改善代码的维护,减少忘记暴露一些重要事件"之类的错误.仅因为您可以使用显式接口实现,否则它只能使执行大量工作"的过程更加容易和常规,然后部分键入"工作将由Intellisense完成.

除此之外,我建议您遵循您已经自己解释的方式.

另一种方法是将引用公开给嵌套控件(子控件).我敢肯定,您在提到封装时会理解为什么这不是很好.是的,肯定会违反封装要求,并且有时可以用于快速和肮脏的解决方案.是的,与诚实"方法相比,它相当快,而且足够脏.由于我一点也不喜欢脏事,所以我想把这部分总结一下.我只需要补充一点,这也将使子控件的属性对设计器不可用,但对我来说,设计器几乎没有价值.我尝试仅将Designer用于一般布局,例如,从不使用它来设置事件处理程序.

但是,这个问题既有趣又重要,因此让我有兴趣思考一下:是否有可能创建一个可以大大改善嵌套用户控件开发的框架?也许有些事情要考虑.

—SA
I think the way of exposing an event of a ControlChild to the interface of ControlHost you have described as "a lot of work" is the most appropriate.

Perhaps you can do a little better using the following technique: you can create some interfaces representing each of the nested controls and implement them all in the host control. It won''t make a mechanism reducing amount of development work (actually, will requires some extra work), but it can improve maintenance of the code, reduce the likeness of the mistakes like "forgot to expose some important event". It can only make the process of doing this "a lot of work" more easy and regular, just because you can use explicit interface implementation, and then part of that "typing" work will be done by the Intellisense.

Other than that, I would advice you to follow the way you already explained by yourself.

The alternative would be to expose the reference to the nested controls (children). I''m sure you understand why this is not very good as you mention encapsulation. Yes, it would be certain violation of encapsulation and sometime can be used for quick-and-dirty solution. Yes, quite quick compared to "honest" approach, and, well, dirty enough. As I don''t like quick-and-dirty at all, I want to wrap this part up. I only need to add that it would also make the property of child controls not accessible to the Designer, but to me the Designer has very little value; I try to use the Designer only for general layout and, for example, never use it to set up event handlers.

However, this question is interested and important, so it makes me interested to think some more about it: is it possible to create some framework which could highly improve the development of nesting user controls? Perhaps there is something to think about.

—SA


这篇关于有关用户控件,嵌套控件和封装的问题.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆