【问题标题】:Organising UI code in .NET forms在 .NET 表单中组织 UI 代码
【发布时间】:2010-06-16 09:44:44
【问题描述】:

我是自学编程的人,没有接受过任何正式的 .NET 编程培训。

不久前,我开始使用 C# 来开发一个 GUI 程序来控制传感器,并且该项目已经开花结果。我只是想知道如何最好地在我的表单中组织代码,尤其是 UI 代码。

我的表格目前一团糟,或者至少在我看来是一团糟。

  • 我有一个构造函数,它初始化所有参数并创建事件。
  • 我有一个巨大的 State 属性,当用户通过由 States 枚举控制的应用程序(即:断开连接、连接、设置、扫描)时,它会更新我所有表单控件的 Enabled 状态。
  • 我有 3-10 个通过属性访问的私有变量,其中一些对更改表单元素的值有副作用。
  • 我有很多“UpdateXXX”函数来处理依赖于其他 UI 元素的 UI 元素 - 即:如果更改了传感器,则更改波特率下拉列表。它们被划分为区域
  • 我有很多事件调用这些更新函数
  • 我有一个后台工作人员负责所有的扫描和分析。

我的问题是这看起来很乱,尤其是 State 属性,并且变得无法维护。此外,我的应用程序逻辑代码和 UI 代码在同一个文件中,并且在某种程度上混合在一起,这似乎是错误的,这意味着我需要进行大量滚动才能找到我需要的内容。

您如何构建您的 .net 表单?

谢谢

【问题讨论】:

  • 如何构建我的 .NET 表单?很像,实际上:)
  • 大声笑 :) 你的表格有多大。对我来说,有些是 2000 LOC,维护/更新变得非常烦人
  • 当您开始开发用户控件时,您会注意到表单和应用程序的行数减少了。
  • 我认为这只是因为我有很多响应事件的内置控件,以及很多查看这些控件来决定做什么的应用程序逻辑。
  • +1 用于自省。愿意以批判的眼光看待自己的工作是一个很好的特质。

标签: .net winforms code-organization


【解决方案1】:

有许多模式可以帮助您在应用程序中分离逻辑,从而产生更简洁和更易于维护的代码。 MVP 模式是一个很好的开始。它基于定义 3 个责任领域,即 MVP M = Model、V = View、P = Presenter。如果您熟悉使用接口就可以了,否则这将是一个很好的起点(查看基本的 OO 原则:封装、抽象、多态)。 MVP 的基本原理是将你的应用程序逻辑放在 Presenter 中。演示者通过接口与视图(您的表单)对话,当用户与之交互时,视图会回调演示者(我也为此使用接口)。模型是解决方案的领域对象层次结构,它实现了业务逻辑和实体关系。

大多数 UI 模式(MVP、MCV 等)都在尝试做同样的事情,将您的顾虑分开。下面是一个简单的例子:

//视图界面

interface IUserDetailsView
{

      string Username{set;get;}
      string FirstName{get;set;}
      string LastName{get;set;}
      UserDetailsPresenter Presenter{get;set;}
      void DisplayMessage(string message);


}

//视图实现 //具有文本框、标签、组合等的标准窗体,其中

class UserDetailsView : Form, IUserDetails
{

      public string Username{set{txtUserName.text = value;}get{return txtUserName.text;}}
      public string FirstName{set{txtFirstName.text = value;}get{return txtFirstName.text;}}
      public string LastName{set{txtLastName.text = value;}get{return txtLastName.text;}}

      Public UserDetailsPresenter Presenter{get;set;}

      public void DisplayMaessage(string message)
      {
         MessageBox.Show(message);
      }

      private void saveButton_Click(object sender, EventArgs e)
      {
         Presenter.SaveUserDetails();

      }
}

//表示逻辑

class Presenter UserDetailsPresenter {

  //Constructor
  public userDetailsPresenter(IUserDetailsView view)
  {
    //Hold a reference to the view interface and set the view's presnter
     _view = view;
     _view.Presenter = this;
  }

  private IUserDetailsView _view;

  DisplayUser(string userName)
  {
     //Get the user from some service ...
     UserDetails details = service.GetUser(userName);

     //Display the data vioa the interface
     _view.UserName = details.UserName;
     _view.FirstName = details.FirstName;
     _view.LastName = details.LastName;

  }

  public void SaveUserDetails()
  {

       //Get the user dryaiols from the view (i.e. the screen
       UserDetails details = new UserDetails();

       details.UserName = _view.UserName;
       details.FirstName = _view.FirstName;
       details.LastName = _view.LastName;

       //Apply some business logic here (via the model)
       if(!details.IsValidUserDetails())
       {
          _view.DisplayMessage("Some detail outlining the issues");
         return;
       }

       //Call out to some service to save the data
       service.UpdateUser(details);

  }

}

//最后是模型

public class UserDetails
{

   public UserName {get;set;}
   public FirstName{get;set;}
   public LastName{get;set;}

   public bool IsValidUserDetails()
   {
       if(LastName == "Smith")
       {
          //We do not allow smiths, remember what happened last time ... or whatever
          return false;
       }

       return true;
   }

}

希望这能解释责任是如何分开的。该表单除了显示/格式化等之外没有任何逻辑,它也可以被存根用于测试。演示者是视图和模型之间的中介并调用服务,模型实现您的业务逻辑。正如已经建议的那样,这种模式有一些变化,可以使您的代码更苗条和更灵活,但这概述了基本原则。我希望这会有所帮助。

:-)

【讨论】:

  • 谢谢,举个例子确实有帮助。
【解决方案2】:

对于复杂的表单,我通常将代码拆分为单独的文件。您可以使用“部分课程”来做到这一点。每个源代码文件都根据格式命名。例如 MainForm.cs、MainForm.State.cs、MainForm.Update.cs、MainForm.Menu.cs 等等。如果我有许多复杂的表格,我将为每个表格创建一个子文件夹。这里的一个提示是创建一个 MainForm.Wip.cs 表单。这个部分类表单是您当前正在处理的代码。完成此代码后,您可以重命名它或将代码移动到其他源代码文件。

此外,我还将创建用户控件。这具有代码重用的好处,并且将许多功能移出表单。查看http://msdn.microsoft.com/en-us/library/6hws6h2t.aspx 上的“使用 .NET Framework 开发自定义 Windows 窗体控件”。

http://www.codinghorror.com/blog/2007/12/nobody-cares-what-your-code-looks-like.html 上查看没人关心你的代码是什么样子。在“组织”之前需要考虑的事情。

【讨论】:

  • 好吧,我从没想过使用部分类。 .Wip 有什么意义吗?
  • 进行中的工作 (wip)。没有意义。它只是一个临时工作的部分类源代码文件。 MainForm.Temp 真的没有任何意义,但 Wip 确实如此。
  • 我同意用户控件,但我不同意将类拆分为部分。我发现这只会让事情变得更难理解,除非表单很大,如果表单那么大,我会说它应该重新设计得更小(例如使用用户控件)。我只喜欢生成代码的部分类。
  • 好的。我将来可能会看看,但我认为自定义控件与我正在开发的表单无关。
  • 我认为部分表单可能对我有用,可以将应用程序逻辑内容与 UI 代码的其余部分分开。
【解决方案3】:

查看模型-视图-演示者模式:http://en.wikipedia.org/wiki/Model_View_Presenter

使用这种模式,表单的代码隐藏应该主要包含对演示者的简单级联调用,这反过来会改变模型,将事件级联回视图(有时通过演示者,具体取决于您的实现)。

要点是:您的表单(视图)不应包含任何状态信息;这将在演示者中,它不应该关心从哪里获取数据,只要数据符合指定的合同。这提高了可测试性,因为您可以轻松地在演示器上测试您的状态和数据,并解耦视图,允许 PLAF、相同数据的不同表示和类似数据。

祝你好运:)

【讨论】:

  • 我在 Web 开发中使用了 MVC 模式。我正在阅读从 wiki 页面链接到的 MVC# 文档,但我看不到它在复杂表单上的工作方式。
  • MVC 在 Windows 窗体中并不是一个真正可行的选项,因为您的输入/输出与单个项目(表单)密切相关。 MVP 与 MVC 非常不同 :)
【解决方案4】:

一些快速建议:

尝试将所有非 UI 代码移出表单,如果可能,您只希望在实际表单中包含 GUI 代码。如果一个属性有副作用,它可能应该是一个函数。您的 State 属性几乎肯定应该是一个方法,看看您是否可以将其中的代码分解为单独的方法,因此它只是每个状态的一个函数调用。

【讨论】:

  • 将它作为属性非常方便,我可以这样说:“if (this.State == States.XXX) ...”或“this.State = States.YYY;”。我看不出更改为函数会有多大帮助。我应该将我的 UI 代码移动到哪里(我想你可能是指应用程序代码,在这种情况下我应该把它移动到哪里?)。我的属性中的副作用很有用——这意味着我可以封装访问/设置我的表单元素。不幸的是,这意味着我有 10 多个属性。
  • @sb3700: 是的,错过了 UI 前面的 non 一词,您应该为您的非 ui 代码创建单独的类,并在需要时从您的 UI 代码中调用它们(尝试最小化 UI 的界面代码可以调用)。我可能误解了 State 属性,我以为你的意思是它包含了状态机。我同意它的副作用很容易和方便,但它使代码更难理解,因为您通常不会假设属性会产生副作用。
【解决方案5】:

这是一个经常使用的架构模式的链接。

http://en.wikipedia.org/wiki/Model_View_ViewModel

我也会查找一些其他架构模式,并对此进行更多研究,查找一些示例代码等。

【讨论】:

  • 就个人而言,我不会将 MVVM 用于表单应用程序,因为它严重依赖 WPF 和 XAML 提供的数据绑定模型:)
【解决方案6】:

我倾向于在用户控件或自定义控件中放置尽可能多的代码。组件更易于重用,表单代码更易于阅读。

用户控件还可以处理和公开事件,这可以使动态部分更容易与表单代码分离。

您甚至可以制作在表单上看不到的自定义控件,例如计时器。

【讨论】:

  • 没有用户界面的自定义控件,例如 System.Windows.Forms.Timer 被称为组件。
【解决方案7】:

您应该首先分析您的代码,以区分什么是应用程序逻辑和什么是 UI 逻辑。这两个都不应该在同一个文件中。您的状态属性绝对不是 UI 逻辑,因此请先将其移出您的表单。这将帮助您清除表单代码。

其次,阅读一些设计模式和原则。你可以找到一些很好的例子here,在你的情况下,我会检查行为模式,更具体地说是状态和调解者模式。它们不是解决问题的灵丹妙药,但它应该让您更好地了解如何拆分应用程序和 UI 逻辑。

【讨论】:

    【解决方案8】:

    我使用区域,像这样:

    #Region "_Edit"
        Private Sub _edit_VisibleChanged(...) Handles _edit.VisibleChanged
        End Sub
    #End Region
    

    从上到下,我的表单代码有:

    • 私人声明
    • 好友属性
    • 好友订阅
    • 私有财产
    • 私人潜艇
    • 事件
    • 私有表单/类的事件处理程序

    听起来您的 State 属性需要分解,或者可能将代码移到其他类或例程中,以便更加隐藏复杂性。

    【讨论】:

    • 谢谢,我也一直在使用区域,但它看起来很丑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-05
    • 2014-06-17
    • 2018-10-12
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 2011-06-04
    相关资源
    最近更新 更多