具有共同功能的Windows窗体的最佳设计


20

过去,我使用继承来允许在应用程序中扩展Windows窗体。如果我所有的表单都具有公共控件,插图和功能,那么我将创建一个实现公共控件和功能的基本表单,然后允许其他控件从该基本表单继承。但是,我在设计时遇到了一些问题。

  1. 控件一次只能放在一个容器中,因此您拥有的任何静态控件都将很棘手。例如:假设您有一个称为BaseForm的基本表单,其中包含一个TreeView,并对其进行了保护和静态处理,以便此类的所有其他(派生)实例都可以修改和显示相同的TreeView。这对于从BaseForm继承的多个类不起作用,因为TreeView一次只能位于一个容器中。它可能在初始化的最后一个表单上。尽管每个实例都可以编辑该控件,但在给定时间只能显示一个。当然,有一些变通方法,但是它们都很丑陋。(这对我来说似乎是一个非常糟糕的设计。为什么多个容器不能存储指向同一对象的指针?无论如何,它就是它。)

  2. 表单之间的状态,即按钮状态,标签文本等,我必须使用全局变量并在Load上重置状态。

  3. Visual Studio的设计师并不能很好地支持此功能。

是否有更好但仍易于维护的设计可供使用?还是形式继承仍然是最好的方法?

更新 从查看MVC到MVP,从观察者模式到事件模式。这是我目前的想法,请批评:

我的BaseForm类将仅包含控件以及与这些控件相关的事件。需要任何逻辑来处理它们的所有事件都将立即传递给BaseFormPresenter类。此类将处理来自UI的数据,执行任何逻辑操作,然后更新BaseFormModel。该模型会将事件(状态更改时将触发)的事件公开给Presenter类,该类将订阅(或观察)该事件。演示者收到事件通知后,它将执行任何逻辑,然后演示者将相应地修改视图。

内存中每个Model类只有一个,但是可能存在许多BaseForm实例,因此有BaseFormPresenter。这将解决我将BaseForm的每个实例同步到相同数据模型的问题。

问题:

哪一层应该存储最后按下的按钮之类的内容,以便我可以在表单之间为用户(例如CSS菜单中)突出显示它?

请批评这个设计。谢谢你的帮助!


我不明白为什么您被迫使用全局变量,但是如果是这样,那么肯定会存在更好的方法。也许工厂通过组合而不是继承来创建通用组件?
stijn 2011年

您的整个设计都有缺陷。如果您已经了解要执行的操作,Visual Studio不支持该功能,它会告诉您一些信息。
Ramhound

1
@Ramhound视觉工作室支持,只是效果不佳。这就是Microsoft告诉您的方法。我只是觉得这很痛苦。msdn.microsoft.com/zh-cn/library/aa983613(v=vs.71).aspx无论如何,如果您有更好的主意,我全是眼睛。
乔纳森·亨森

@stijn我想我可以在每种基本形式中都有一个将控制状态加载到另一个实例中的方法。即LoadStatesToNewInstance(BaseForm实例)。每当我想显示该基本类型的新形式时,都可以调用它。form_I_am_about_to_hide.LoadStatesToNewInstance(this);
乔纳森·汉森

我不是.net开发人员,您可以扩展第1点吗?我可能有解决方案
Imran Omar Bukhsh 2011年

Answers:


6
  1. 我不知道为什么需要静态控件。也许你知道我不知道的东西。我已经使用了很多视觉继承,但是从未见过需要静态控件。如果您具有公共的树视图控件,则让每个表单实例都有其自己的控件实例,并共享绑定到树视图的数据的单个实例。

  2. 在表单之间共享控制状态(与数据相反)也是一个不寻常的要求。您确定FormB确实需要了解FormA上按钮的状态吗?请考虑MVP或MVC设计。可以将每种形式都看作是愚蠢的“视图”,它对其他视图甚至应用程序本身一无所知。使用智能演示者/控制器来监督每个视图。如果有道理,一个演示者可以监督多个视图。将状态对象与每个视图相关联。如果您需要在视图之间共享某些状态,请让主持人对此进行调解(并考虑数据绑定-参见下文)。

  3. 同意,Visual Studio会让您头疼。在考虑表单或用户控件的继承时,您需要仔细权衡收益与表单设计师令人沮丧的怪癖和局限性的潜在(和可能的)成本。我建议将形式继承保持在最低限度-仅在收益很高时使用它。请记住,作为子类的替代方法,您可以创建通用的“基本”表单,并为每个可能的“子”实例实例化一次,然后即时对其进行自定义。与共享方面相比,表单的每个版本之间的差异较小时,这是有意义的。(IOW:复杂的基本形式,只有稍微复杂的子形式)

当它可以帮助您防止UI开发的大量重复时,请使用usercontrols。考虑用户控件的继承,但要应用与表单继承相同的注意事项。

我认为我能提供的最重要的建议是,如果您当前不使用某种形式的视图/控制器模式,我强烈建议您开始这样做。它迫使您学习并欣赏松耦合和分层的好处。

回应您的更新

哪一层应该存储诸如最后按下的按钮之类的东西,以便我可以为用户突出显示它...

您可以在视图之间共享状态,就像在演示者及其视图之间共享状态一样。创建一个特殊的类SharedViewState。为简单起见,您可以将其设置为单例,也可以在主演示者中将其实例化,然后从那里将其传递给所有视图(通过其演示者)。当状态与控件关联时,请尽可能使用数据绑定。大多数Control属性可以是数据绑定的。例如,Button的BackColor属性可以绑定到SharedViewState类的属性。如果在具有相同按钮的所有表单上执行此绑定,则只需设置即可在所有表单上突出显示Button1 SharedViewState.Button1BackColor = someColor

如果您不熟悉WinForms数据绑定,请点击MSDN并做一些阅读。不难 了解有关内容INotifyPropertyChanged,您已经完成了一半。

这是一个以Button1BackColor属性为例的viewstate类的典型实现:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}

感谢您的周到解答。您知道对视图/控制器模式的很好参考吗?
乔纳森·汉森

1
我记得在想,这是一个相当不错的介绍:codebetter.com/jeremymiller/2007/05/22/...
Igby Largeman

请查看我的更新。
乔纳森·亨森

@JonathanHenson:答案已更新。
伊比·拉格曼

5

我基于MVVM模式和更新控件维护一个新的WinForms / WPF应用程序。它始于WinForms,然后由于外观良好的UI的市场重要性而创建了WPF版本。我认为维护一个应用程序以相同的后端代码(视图模型和模型)支持两种完全不同的UI技术将是一个有趣的设计约束,我必须对这种方法感到非常满意。

在我的应用程序中,每当需要在UI的各个部分之间共享功能时,我都会使用自定义控件或用户控件(从WPF UserControl或WinForms UserControl派生的类)。在WinForms版本中,TabPage的派生类中的另一个UserControl中有一个UserControl,该控件最终位于主窗体上的Tab控件中。实际上,WPF版本将UserControls嵌套了一层。使用自定义控件,您可以轻松地根据之前创建的UserControls组成新的UI。

由于我使用MVVM模式,因此我将尽可能多的程序逻辑放入ViewModel(包括Presentation / Navigation Model)或Model(取决于代码是否与用户界面相关)中,但是由于使用了相同的ViewModel由WinForms视图和WPF视图使用,ViewModel不允许包含为WinForms或WPF设计的代码或直接与UI交互的代码;这样的代码必须进入视图。

同样在MVVM模式中,UI对象应避免彼此交互!相反,它们与视图模型进行交互。例如,一个TextBox不会询问附近的ListBox选中了哪个项目。相反,列表框会将对当前选定项的引用保存在viewmodel层中的某个位置,而TextBox将查询viewmodel层以找出当前选择的内容。这解决了您从另一表格中找出以一种形式按下哪个按钮的问题。您只需在两个表单之间共享一个Navigation Model对象(这是应用程序的viewmodel层的一部分),然后在该对象中放置一个属性,该属性表示按下了哪个按钮。

WinForms本身并不很好地支持MVVM模式,但是Update Controls提供了自己独特的方法MVVM作为位于WinForms之上的库。

我认为,这种程序设计方法非常有效,我计划在以后的项目中使用它。它之所以如此有效的原因是:(1)Update Controls自动管理依赖关系;(2)通常非常清楚应该如何构造代码:与UI对象交互的所有代码都属于View,所有与UI对象交互的代码都属于View。与UI相关,但不必与UI对象进行交互属于ViewModel。通常,您会将代码分为两部分,一部分用于View,另一部分用于ViewModel。我在系统中设计上下文菜单时遇到了一些困难,但最终我也为此设计了一个设计。

我也很热衷于博客上的更新控制。但是,这种方法确实需要很多时间来适应,并且在大型应用程序中(例如,如果列表框包含数千个项目),由于当前自动依赖项管理的局限性,您可能会遇到性能问题。


3

即使您已经接受了答案,我也要回答这个问题。

正如其他人所指出的那样,我不明白为什么您必须使用静态的东西。听起来您做错了什么。

无论如何,我遇到了与您相同的问题:在WinForms应用程序中,我有几种共享某些功能和控件的表单。另外,我所有的表单都已经从基本表单(我们称其为“ MyForm”)派生而来,该基础表单添加了框架级的功能(与应用程序无关)。Visual Studio的Forms Designer应该支持编辑从其他表单继承的表单,但是在练习仅在您的表单仅执行“ Hello,world!-OK-Cancel”之类的操作时才有效。

我最终要做的是:我保留了非常复杂的通用基类“ MyForm”,并继续从中派生所有应用程序的形式。但是,我绝对不会在这些表单中执行任何操作,因此VS Forms Designer毫无问题地对其进行编辑。这些表单仅包含表单设计器为其生成的代码。然后,我有一个独立的并行对象层次结构,称为“代理”,其中包含所有特定于应用程序的功能,例如初始化控件,处理由表单和控件生成的事件等。一对一我的应用程序中代理类和对话框之间的对应关系:有一个对应于“ MyForm”的基础代理类,然后派生了另一个与“ MyApplicationForm”相对应的代理类,

每个代理都接受特定类型的表单作为构造时参数,并通过注册其事件将其附加到表单。它还将基础一直委托给接受“ MyForm”的“ MySurrogate”。该代理向表单的“ Disposed”事件注册,以便当表单被销毁时,代理会对其自身调用一个可覆盖对象,以便它及其所有后代可以执行清除操作。(从事件等中注销)

到目前为止,它一直运行良好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.