WindowsFormsアプリでのMVVMパターンサンプル

以下はWindowsFormsアプリでMVVMパターンを実装したサンプルコードです

MVVMとは

MVVMは、Model-View-ViewModelの略で、Windows Presentation Foundation (WPF) や Universal Windows Platform (UWP) などのXAMLベースのプラットフォームで広く使用されるデザインパターンの一つです。

MVVMは、View、ViewModel、Modelの3つのコンポーネントによって構成されています。

  • View: ユーザーインターフェースの部分で、ユーザーがアプリケーションを操作するためのUI要素を持ちます。
  • ViewModel: ViewとModelの仲介役で、Viewに表示するためのデータや操作ロジックを提供し、Viewから受け取ったイベントを処理します。ViewModelは、Viewとは完全に独立しているため、Viewの変更がViewModelに影響を与えず、またViewModelの変更がViewに反映されます。
  • Model: アプリケーションのビジネスロジックやデータを持ち、ViewModelからアクセスされます。Modelは、データの取得や保存などの実際の処理を担当します。

MVVMは、ViewとViewModelを分離することで、ビジネスロジックやデータモデルを変更せずにUIを変更できる柔軟性を提供します。また、ViewModelは、テストしやすく、再利用性が高いため、アプリケーションの品質を向上させることができます。

MVVMは、WPFやUWPなどのXAMLベースのプラットフォームでの開発に最適化されていますが、他のプラットフォームでも使用することができます。しかし、MVVMを使用する場合は、コードの複雑さが増すため、より高度な開発スキルが必要になります。

クラス図

サンプル概要

サンプルでは、Modelクラスは単純な文字列を持ち、ViewModelクラスはModelをラップして、Viewに表示するためのプロパティを提供しています。Viewクラスでは、ViewModelを生成して、ViewModelのTextプロパティとViewのTextプロパティをバインドしています。

このように実装することで、ViewとViewModelが疎結合になり、アプリケーションの拡張性やテストのしやすさが向上します。また、ViewModelがINotifyPropertyChangedインターフェースを実装することで、Viewに変更を通知することができます。

注意点として、Windows FormsアプリケーションでMVVMパターンを実装する場合、ViewとViewModelのバインディングにはいくつかの方法がありますが、上記の例ではDataBindingsを使用しています。ただし、DataBindingsはバグがある場合があり、WPFやUWPのような他のフレームワークと比較すると制限があります。また、ViewModelの初期化や状態管理に関する課題もあります。したがって、Windows FormsアプリケーションでMVVMパターンを使用する場合は、よく考慮して実装する必要があります。

実行結果

2つのTextBox(FirstNameとLastName)に文字を入力するイベントで、プロパティが更新されます

フォームデザイン

ラベルとボタンを2つずつ配置します

ソリューション

コード

Model

namespace MVVMSample
{
    public class PersonModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

ViewModel

using System.ComponentModel;

namespace MVVMSample
{
    public class PersonViewModel : INotifyPropertyChanged
    {
        private PersonModel _person;

        public event PropertyChangedEventHandler PropertyChanged;

        public string FirstName
        {
            get { return _person.FirstName; }
            set
            {
                _person.FirstName = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FirstName)));
            }
        }

        public string LastName
        {
            get { return _person.LastName; }
            set
            {
                _person.LastName = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LastName)));
            }
        }

        public PersonViewModel()
        {
            _person = new PersonModel();
        }
    }
}

View

namespace MVVMSample
{
    public partial class MainForm : Form
    {
        private PersonViewModel _viewModel;

        public MainForm()
        {
            InitializeComponent();

            _viewModel = new PersonViewModel();

            firstNameTextBox.DataBindings.Add(nameof(firstNameTextBox.Text), _viewModel, nameof(_viewModel.FirstName));
            lastNameTextBox.DataBindings.Add(nameof(lastNameTextBox.Text), _viewModel, nameof(_viewModel.LastName));
        }
    }
}
namespace MVVMSample
{
    partial class MainForm
    {
        /// <summary>
        ///  Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        ///  Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        ///  Required method for Designer support - do not modify
        ///  the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.firstNameTextBox = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.lastNameTextBox = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // firstNameTextBox
            // 
            this.firstNameTextBox.Location = new System.Drawing.Point(129, 55);
            this.firstNameTextBox.Name = "firstNameTextBox";
            this.firstNameTextBox.Size = new System.Drawing.Size(100, 23);
            this.firstNameTextBox.TabIndex = 0;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(63, 58);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(60, 15);
            this.label1.TabIndex = 1;
            this.label1.Text = "FirstName";
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(63, 101);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(59, 15);
            this.label2.TabIndex = 3;
            this.label2.Text = "LastName";
            // 
            // lastNameTextBox
            // 
            this.lastNameTextBox.Location = new System.Drawing.Point(129, 98);
            this.lastNameTextBox.Name = "lastNameTextBox";
            this.lastNameTextBox.Size = new System.Drawing.Size(100, 23);
            this.lastNameTextBox.TabIndex = 2;
            // 
            // MainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.lastNameTextBox);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.firstNameTextBox);
            this.Name = "MainForm";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private TextBox firstNameTextBox;
        private Label label1;
        private Label label2;
        private TextBox lastNameTextBox;
    }
}

Program.cs

namespace MVVMSample
{
    internal static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            Application.Run(new MainForm());
        }
    }
}