65.9K
CodeProject 正在变化。 阅读更多。
Home

C#/WinForms 中的模型-视图-控制器 (MVC) 模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (126投票s)

2012 年 5 月 11 日

CPOL

5分钟阅读

viewsIcon

932338

downloadIcon

50172

简明扼要地介绍 MVC 的实现,不进行冗长的讨论或细节的阐述。

引言

本文档用于演示在 .NET 中使用 C#/WinForm 实现 MVC 模式。

这里是一个简单的“用户信息管理器”应用程序,它按照模型-视图-控制器 (MVC) 模式进行组织。

该应用程序显示联系人列表,并允许您添加、修改和删除现有联系人。核心思想是将用户界面分离为视图 (View)(负责创建显示,并在需要时调用模型获取信息)和控制器 (Controller)(负责响应用户请求,并在需要时与视图和模型进行交互)。MVC 模式的主要优势是松散耦合。所有层都与其自身的功能分离。更换某个层与其他类型的层非常容易。换句话说,MVC 模式是将 UI 行为分解为独立的部分,以提高重用性和可测试性。我使用 Visual Studio 2010 Ultimate 和 .NET 4.0 来创建此应用程序。

背景

顾名思义,模型-视图-控制器 (Model-View-Controller) 考虑了三个部分:

  • 模型 (Model): 应负责应用程序域的数据。
  • 视图 (View): 在用户界面中展示模型的外观。
  • 控制器 (Controller): 它是 MVC 的核心,是连接模型和视图的中间人,即它接收用户输入,操作模型并促使视图更新。
  •                   
有关 MVC 的更多信息,请参阅 维基百科上的这篇文章。

解决方案

用户信息管理器是一个可以存储客户联系信息的应用程序。该应用程序显示联系人列表,并允许您添加、修改和删除现有联系人。所有客户都具有 ID、名字、姓氏和性别。操作员维护客户列表的屏幕可能看起来像这样:

       

可以查看、添加、删除和更新客户列表(目前仅包含 V.I.P. 会员,如果您想成为俱乐部会员,请随时询问 Smile | <img src= " src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" /> .... 没问题,免费的)。添加新用户后,其 ID 不能再更改。

类图

在系统设计中,会识别出许多类并将它们分组到类图中,这有助于确定对象之间的关系。

组件说明

控制器部分

为了将逻辑从视图中分离出来,我们必须使视图尽可能“无能”,因此我们倾向于让控制器完成所有繁重的工作,只向视图传递一些不需要进一步处理的简单命令。根据我们的设计,我们通过定义一个 `IUsersView` 接口来实现这一点,视图必须实现该接口。此接口仅包含我们需要的属性/方法的签名。

using System;
using WinFormMVC.Model;

namespace WinFormMVC.Controller
{
    public interface IUsersView
    {
        void SetController(UsersController controller);
        void ClearGrid();
        void AddUserToGrid(User user);
        void UpdateGridWithChangedUser(User user);
        void RemoveUserFromGrid(User user);
        string GetIdOfSelectedUserInGrid();
        void SetSelectedUserInGrid(User user);

        string FirstName     { get; set; }
        string LastName      { get; set; }
        string ID    { get; set; }
        string Department    { get; set; }
        User.SexOfPerson Sex { get; set; }  
        bool CanModifyID     {      set; }
    }
}

现在我们有了一个相当不错的接口,包含许多方法。即使 MVC 模式正式声明控制器应接收事件并对视图做出反应,但通常更实用、更容易让视图订阅事件,然后将处理委托给控制器。

最后,我将展示控制器的实际实现(请参阅 `UsersController` 类)。它将模型(`User` 类)与视图(`UserView` 类)连接起来。

public class UsersController
{
    //Notice we only use the interfaces. This makes the test more 
    //robust to changes in the system.
    IUsersView _view;
    IList      _users;
    User       _selectedUser;

    //The UsersController depends on abstractions(interfaces).
    //It's easier than ever to change the behavior of a concrete class. 
    //Instead of creating concrete objects in UsersController class, 
    //we pass the objects to the constructor of UsersController
    public UsersController(IUsersView view, IList users)
    {
       _view = view;
       _users = users;
       view.SetController(this);
    }

    public IList Users
    {
       get { return ArrayList.ReadOnly(_users); }
    }

    private void updateViewDetailValues(User usr)
    {
       _view.FirstName   =  usr.FirstName;
       _view.LastName    =  usr.LastName;
       _view.ID  =  usr.ID;
       _view.Department  =  usr.Department;
       _view.Sex =  usr.Sex;
    }

    private void updateUserWithViewValues(User usr)
    {
       usr.FirstName     =  _view.FirstName;
       usr.LastName      =  _view.LastName;
       usr.ID    =  _view.ID;
       usr.Department    =  _view.Department;
       usr.Sex   =  _view.Sex;
    }

    public void LoadView()
    {
       _view.ClearGrid();
       foreach (User usr in _users)
       _view.AddUserToGrid(usr);

       _view.SetSelectedUserInGrid((User)_users[0]);
    }

    public void SelectedUserChanged(string selectedUserId)
    {
       foreach (User usr in this._users)
       {
           if (usr.ID == selectedUserId)
           {
               _selectedUser = usr;
               updateViewDetailValues(usr);
               _view.SetSelectedUserInGrid(usr);
               this._view.CanModifyID = false;
               break;
           }
       }
    }

    public void AddNewUser()
    {
       _selectedUser = new User(""   /*firstname*/, 
       ""  /*lastname*/, 
       "" /*id*/, 
       ""/*department*/,
       User.SexOfPerson.Male/*sex*/);
     
       this.updateViewDetailValues(_selectedUser);
       this._view.CanModifyID = true;
    }

    public void RemoveUser()
    {
       string id = this._view.GetIdOfSelectedUserInGrid();
       User userToRemove = null;

        if (id != "")
        {
            foreach (User usr in this._users)
            {
                if (usr.ID == id)
                {
                    userToRemove = usr;
                break;
                }
            }

            if (userToRemove != null)
            {
                int newSelectedIndex = this._users.IndexOf(userToRemove);
                this._users.Remove(userToRemove);
                this._view.RemoveUserFromGrid(userToRemove);

            if (newSelectedIndex > -1 && newSelectedIndex < _users.Count)
            {
                this._view.SetSelectedUserInGrid((User)_users[newSelectedIndex]);
            }
        }
    }
}

public void Save()
{
   updateUserWithViewValues(_selectedUser);
   if (!this._users.Contains(_selectedUser))
   {
       //Add new user
       this._users.Add(_selectedUser);
       this._view.AddUserToGrid(_selectedUser);
   }
   else
   {
       //Update existing user
       this._view.UpdateGridWithChangedUser(_selectedUser);
   }
   _view.SetSelectedUserInGrid(_selectedUser);
   this._view.CanModifyID = false;

}
}

控制器类对应用程序非常重要且至关重要。保持其轻量、敏捷并与其他程序组件松散耦合非常重要。

视图部分

本节将重点介绍加载具有用户列表的视图的场景。
如前所述,我们的视图必须实现 `IUsersView` 接口。以下代码显示了实现的一部分。

namespace WinFormMVC.View
{
    public partial class UsersView : Form, IUsersView
    {

`UsersView` 的 `SetController()` 成员函数允许我们告知视图它必须将事件转发给哪个控制器实例,并且所有事件处理程序都会在控制器上调用相应的“事件”方法。如您所见,`UsersView` 也依赖于抽象...

public void SetController(UsersController controller)
{
    _controller = controller;
}

我们还使用 `IUsersView` 接口的几个方法的实现,这些方法使用 `User` 对象。

public void AddUserToGrid(User usr)
{
    ListViewItem parent;
    parent = this.grdUsers.Items.Add(usr.ID);
    parent.SubItems.Add(usr.FirstName);
    parent.SubItems.Add(usr.LastName);
    parent.SubItems.Add(usr.Department);
    parent.SubItems.Add(Enum.GetName(typeof(User.SexOfPerson), usr.Sex));
}

public void UpdateGridWithChangedUser(User usr)
{
    ListViewItem rowToUpdate = null;

    foreach (ListViewItem row in this.grdUsers.Items)
    {
        if (row.Text == usr.ID)
        {
            rowToUpdate = row;
        }
    }

    if (rowToUpdate != null)
    {
        rowToUpdate.Text = usr.ID;
        rowToUpdate.SubItems[1].Text = usr.FirstName;
        rowToUpdate.SubItems[2].Text = usr.LastName;
        rowToUpdate.SubItems[3].Text = usr.Department;
        rowToUpdate.SubItems[4].Text = Enum.GetName(typeof(User.SexOfPerson), usr.Sex);
    }
}

public void RemoveUserFromGrid(User usr)
{

    ListViewItem rowToRemove = null;

    foreach (ListViewItem row in this.grdUsers.Items)
    {
        if (row.Text == usr.ID)
        {
            rowToRemove = row;
        }
    }

    if (rowToRemove != null)
    {
        this.grdUsers.Items.Remove(rowToRemove);
        this.grdUsers.Focus();
    }
}

public string GetIdOfSelectedUserInGrid()
{
    if (this.grdUsers.SelectedItems.Count > 0)
        return this.grdUsers.SelectedItems[0].Text;
    else
        return "";
}

public void SetSelectedUserInGrid(User usr)
{
    foreach (ListViewItem row in this.grdUsers.Items)
    {
        if (row.Text == usr.ID)
        {
            row.Selected = true;
        }
    }
}

public string FirstName 
{
    get { return this.txtFirstName.Text; }
    set { this.txtFirstName.Text = value; }
}

public string LastName 
{
    get { return this.txtLastName.Text; }
    set { this.txtLastName.Text = value; }
}

public string ID
{
    get { return this.txtID.Text; }
    set { this.txtID.Text = value; }
}


public string Department 
{
    get { return this.txtDepartment.Text; }
    set { this.txtDepartment.Text = value; }
}

public User.SexOfPerson Sex
{
    get
    {
        if (this.rdMale.Checked)
            return User.SexOfPerson.Male;
        else
            return User.SexOfPerson.Female;
    }
    set
    {
        if (value == User.SexOfPerson.Male)
            this.rdMale.Checked = true;
        else
            this.rdFamele.Checked = true;
    }
}

public bool CanModifyID
{
    set { this.txtID.Enabled = value; }
}

        ...
}

模型部分

这个 `User` 类是一个模型类。在此示例中,`User` 是一个非常简单的域类,没有任何行为,而在实际的领域模型中,域类可能包含更多的功能。模型独立于用户界面。它不知道它是被用于文本界面、图形界面还是 Web 界面。模型仅以结构化格式保存内存状态。如您所见,该类仅包含私有数据成员和客户端代码可用的公共接口(属性)。

using System;

namespace WinFormMVC.Model
{
 public class User
 {
   public enum SexOfPerson
   {
      Male   = 1,
      Female = 2
   }

   private string    _FirstName;
   public string FirstName 
   {
    get { return _FirstName; } 
    set 
    { 
     if (value.Length > 50)
      Console.WriteLine("Error! FirstName must be less than 51 characters!"); 
     else
      _FirstName = value; 
    } 
   }

   private string _LastName;
   public string LastName
   {
     get { return _LastName; }
     set
     {
      if (value.Length > 50)
       Console.WriteLine("Error! LastName must be less than 51 characters!");
      else
       _LastName = value;
    }
   }

   private string _ID;
   public string ID
   {
    get { return _ID; }
    set
    {
      if (value.Length > 9)
        Console.WriteLine("Error! ID must be less than 10 characters!");
      else
       _ID = value;
    }
   }

   private string _Department;
   public string Department
   {
       get { return _Department; }
       set { _Department = value; }
   }

   private SexOfPerson _Sex;
   public SexOfPerson Sex
   {
       get { return _Sex; }
       set { _Sex = value; }
   }


   public User(string firstname, string lastname, string id, string department, SexOfPerson sex)
   {
       FirstName   = firstname;
       LastName    = lastname;
       ID  = id;
       Department  = department;
       Sex = sex;
   }
 } 

} 

客户端部分

现在是时候展示我们如何有效地使用 MVC 范例了,即我们 MVC 架构的代码组件(请参阅 *UseMVCApplication.csproj*)。

using System.Collections;
using  WinFormMVC.Model;
using  WinFormMVC.View;
using  WinFormMVC.Controller;

namespace UseMVCApplication
{
static class Program
{
    /// The main entry point for the application.
    [STAThread]
    static void Main()
    {
        //Here we are creating a View
        UsersView view = new UsersView();
        view.Visible = false;

        //Here we are creating a list of users
        IList users = new ArrayList();
    
        //Here we are add our "commoners" in the list of users
        users.Add(new User("Vladimir",   "Putin",     
     "122",    "Government of Russia",
            User.SexOfPerson.Male));
        users.Add(new User("Barack",     "Obama",
             "123",    "Government of USA",      
             User.SexOfPerson.Male));
        users.Add(new User("Stephen",    "Harper",    
            "124",    "Government of Canada",      
            User.SexOfPerson.Male));
        users.Add(new User("Jean",       "Charest", 
            "125",    "Government of Quebec",    
             User.SexOfPerson.Male));
        users.Add(new User("David",      "Cameron",
            "126",    "Government of United Kingdom",
            User.SexOfPerson.Male));
        users.Add(new User("Angela",     "Merkel",
            "127",    "Government of Germany",
            User.SexOfPerson.Female));
        users.Add(new User("Nikolas",    "Sarkozy",\
            "128",    "Government of France",
            User.SexOfPerson.Male));
        users.Add(new User("Silvio",     "Berlusconi",
            "129",    "Government of Italy",
            User.SexOfPerson.Male));
        users.Add(new User("Yoshihiko",  "Noda",
            "130",    "Government of Japan",
            User.SexOfPerson.Male));
     
        //Here we are creating a Controller and passing two
        //parameters: View and list of users (models)
        UsersController controller = new UsersController(view, users);
        controller.LoadView();
        view.ShowDialog();
        }
    }
}

为什么它好?

  1. 使用 MVC 模式的主要优点是它使用户界面的代码更具可测试性。
  2. 它为用户界面设计过程提供了一种非常结构化的方法,这本身就有助于编写干净、可测试的代码,并且易于维护和扩展。

模型-视图-控制器 (Model-View-Controller) 是一个经过充分验证的设计模式,用于解决分离数据(模型)和用户界面(视图)关注点的问题,从而使用户界面的更改不会影响数据处理,并且可以在不影响/更改 UI 的情况下更改数据。MVC 通过引入一个中间组件:控制器,来实现数据访问和业务逻辑层与 UI 和用户交互的分离。这种 MVC 架构使得在灵活的程序设计中创建可重用组件成为可能(组件可以轻松修改)。

历史

  • 2012 年 5 月 10 日:初次发布
© . All rights reserved.