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

使用 C#/WinForms 进行 MVC 模式的第一个程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (63投票s)

2013年6月30日

CPOL

5分钟阅读

viewsIcon

272116

downloadIcon

14334

使用 C#/WinForms 进行 MVC 模式的简单示例。

下载演示
下载源

介绍 

尽管我曾有使用 PHP、Java 等其他语言进行 MVC 模式编程的经验,但我遇到了一个使用 C#/WinForms 实现 MVC 的需求。当我尝试搜索 C# 的 MVC 模式文章和示例时,我发现有不少文章,但它们缺乏简洁性,而且大多数文章都是为最常使用 MVC 的 ASP.NET 编写的。换句话说,作为解释 MVC 模式的一部分,文章/教程试图做的事情超出了解释模式本身所需的范围。

因此,我决定写这篇文章来解释如何使用 C#/WinForms 实现 MVC 模式,并尽量保持简单。

背景

模型-视图-控制器,顾名思义,在实现此模型时包含三个组件。

  • 模型 (The Model) - 它应该负责应用程序所需的所有数据操作(换句话说,业务逻辑),从现在开始,实现 MVC 模式的应用程序将被称为 MVC 应用程序。操作可以包括读写数据库数据、通过网络从远程计算机获取信息、耗时的操作等。模型还应该在后台告知视图任何数据变化。
  • 视图 (The View) - 此组件负责将数据呈现给用户。就本文的上下文而言,即 WinForms,视图类将与将显示给用户的窗体关联。
  • 控制器 (The Controller) - 它是 MVC 模式的核心和重要组件,因为它将模型和视图连接在一起。操纵数据的模型和向用户呈现数据的视图彼此之间不知道对方的存在,或者它们不直接相互交互。控制器充当中介,将它们连接在一起。例如,控制器接收用户的输入,如点击按钮,并通知模型采取适当的操作,如果需要采取行动来操纵项目数据。

MVC illustration for C# Forms

使用代码

让我们以一个简单的应用程序用例为例,该应用程序用于递增数字,以免读者被分散注意力,从专注于 MVC 模式而偏离到其他项目细节。这正是我写这篇文章的原因。为了简洁起见,我没有更改窗体或按钮的名称,而是保留了原样,例如 button1 和 textbox1。

组件的描述

控制器

控制器是负责将视图和模型连接在一起的组件。视图不知道数据的检索或处理方式的业务逻辑。每当用户在视图上执行操作时,例如单击按钮或更改文本框中的文本,它都会通知控制器。控制器将根据用户输入决定需要采取什么操作。在评估用户输入的有效性后,它可能会请求模型对数据进行一些处理。

namespace MVCTest
{
    // The Icontroller supports only one functionality that is to increment the value
    public interface IController
    {
        void incvalue();
    }
    public class IncController : IController
    {
        IView view;
        IModel model;
        // The controller which implements the IController interface ties the view and model 
        // together and attaches the view via the IModelInterface and addes the event
        // handler to the view_changed function. The view ties the controller to itself.
        public IncController(IView v, IModel m)
        {
            view = v;
            model = m;
            view.setController(this);
            model.attach((IModelObserver)view);
            view.changed += new ViewHandler<IView>(this.view_changed);
        }
        // event which gets fired from the view when the users changes the value
        public void view_changed(IView v, ViewEventArgs e)
        {
            model.setvalue(e.value);
        }
        // This does the actual work of getting the model do the work
        public void incvalue()
        {
            model.increment();
        }
    }
}

View

视图类是真正实现视图接口的窗体本身。在下面的组件中可以看到,视图接口只包含视图更改时的事件处理程序,并设置控制该视图的控制器。

namespace MVCTest
{
    public delegate void ViewHandler<IView>(IView sender, ViewEventArgs e);
    // The event arguments class that will be used while firing the events
    // for this program, we have only one value which the user changed.
    public class ViewEventArgs: EventArgs 
    {
        public int value;
        public ViewEventArgs(int v) { value = v; }
    }
    // Currently, the interface only contains the method to set the controller to which
    // it is tied. The rest of the view related code is implemented in the form.
    public interface IView
    {
        event ViewHandler<IView> changed;
        void setController(IController cont);
    }
}

模型

模型处理控制器传递给它的数字的递增业务逻辑。它还将数据值的变化(已递增)通知视图。(与模型不直接与视图通信的说法相反,控制器是将其模型与其视图连接起来的人。)视图,即窗体,实现了 `IModelObserver`,该接口在模型需要时会被调用。

namespace MVCTest
{
    public delegate void ModelHandler<IModel>(IModel sender, ModelEventArgs e);
    // The ModelEventArgs class which is derived from th EventArgs class to 
    // be passed on to the controller when the value is changed
    public class ModelEventArgs : EventArgs
    {
        public int newval;
        public ModelEventArgs(int v) 
        { 
            newval = v; 
        }
    }
    // The interface which the form/view must implement so that, the event will be
    // fired when a value is changed in the model.
    public interface IModelObserver
    {
        void valueIncremented(IModel model, ModelEventArgs e);
    }
    // The Model interface where we can attach the function to be notified when value
    // is changed. An actual data manipulation function increment which increments the value
    // A setvalue function which sets the value when users changes the textbox
    public interface IModel
    {
        void attach(IModelObserver imo);
        void increment();
        void setvalue(int v);
    }
    public class IncModel : IModel
    {
        public event ModelHandler<IncModel> changed;
        int value;
        // implementation of IModel interface set the initial value to 0
        public IncModel() 
        { 
            value = 0; 
        }
        // Set value function to set the value in case if the user directly changes the value
        // in the textbox and the view change event fires in the controller
        public void setvalue(int v) 
        { 
            value = v; 
        } 
        // Change the value and fire the event with the new value inside ModelEventArgs
        // This will invoke the function valueIncremented in the model and will be displayed
        // in the textbox subsequently
        public void increment() 
        { 
            value++; 
            changed.Invoke(this, new ModelEventArgs(value));
        }
        // Attach the function which is implementing the IModelObserver so that it can be
        // notified when a value is changed
        public void attach(IModelObserver imo) 
        { 
            changed += new ModelHandler<IncModel>(imo.valueIncremented);
        }
    }
}

连接模型-视图-控制器

视图类是真正实现视图接口的窗体本身。

在下面的组件中可以看到,视图接口只包含视图更改时的事件处理程序,并设置控制该视图的控制器。

namespace MVCTest
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());
            // Create the View, Model and Controller object yourself. 
            // Create a controller object with the concreate implementation IncController 
            // and pass the view and model.
            // Controller will store its associated model and view inside the constructor
            // View which is inside the form will tie the controller automatically
            // Run the view object created by you which is infact the form
            Form1 view = new Form1();
            IModel mdl = new IncModel();
            IController cnt = new IncController(view,mdl);
            Application.Run(view);
        }
    }
}

窗体

最后,窗体代码,即实现 `IView` 接口的 `setController` 函数的真实视图,并保留控制器的引用。对于它需要对模型执行的任何操作,它将使用控制器来发出信号。我们可以从以下代码中看到,当单击按钮时,它会调用控制器来递增值。当用户在文本框中键入时,它会引发一个视图更改事件,通过控制器将新值设置到模型中,因为控制器已附加到此视图事件。实现的函数 `valueIncremented` 是 `IModelObserver` 接口的实现,当模型中的值更改时,模型会调用它。

namespace MVCTest
{
    // Form is reallly the view component which will implement the IModelObserver interface 
    // so that, it can invoke the valueIncremented function which is the implementation
    // Form also implements the IView interface to send the view changed event and to
    // set the controller associated with the view
    public partial class Form1 : Form, IView, IModelObserver
    {
        IController controller;
        public event ViewHandler<IView> changed;
        // View will set the associated controller, this is how view is linked to the controller.
        public void setController(IController cont)
        {
            controller = cont;
        }
        
        public Form1()
        {
            InitializeComponent();
        }
        // when the user clicks the button just ask the controller to increment the value
        // do not worry about how and where it is done
        private void button1_Click(object sender, EventArgs e)
        {
            controller.incvalue();
        }
        // This event is implementation from IModelObserver which will be invoked by the
        // Model when there is a change in the value with ModelEventArgs which is derived
        // from the EventArgs. The IModel object is the one from which invoked this.
        public void valueIncremented(IModel m, ModelEventArgs e)
        {
            textBox1.Text = "" + e.newval;
        }
        // When this event is raised can mean the user must have changed the value.
        // Invoke the view changed event in the controller which will call the method
        // in IModel to set the new value, the user can enter a new value and the
        // incrementing starts from that value onwards
        private void textBox1_Leave(object sender, EventArgs e)
        {
            try
            {
                changed.Invoke(this, new ViewEventArgs(int.Parse(textBox1.Text)));
            }
            catch (Exception)
            {
                MessageBox.Show("Please enter a valid number");
            }
        }
    }
}

关注点

这是我第一次向 CodeProject 社区提交文章。为 CodeProject 做出贡献很有趣,我期待着在我的经验领域贡献越来越多的示例。

历史

  • 2013年6月30日:初稿。
  • 2013年7月2日:添加了演示和源代码
© . All rights reserved.