C#
WindowsForm
PRISM
WPF,C#

WindowsFormをそろそろやめてXaml+C#に移行したい話

初めに

こんにちは。
.net系の開発者してます。
最近はずっとWindowsフォーム触っています。
Windowsフォームを作っていて嫌なことが出てきたので、
それを解消すべく他の選択肢について考えてみました。

Windowsフォームでありがちな嫌なこと

  • コード量が増えてくとコードビハインドがぐっちゃぐちゃになる
  • 画面項目にセットするだけの仕様と関係ないコードが多くなる
  • 画面デザインが古臭い(これは自分のデザインセンスにも問題があるかも)

WPF

最新で言うとWindows10向けUWPアプリだけど、
今回はWPFを使用(Xaml+C#ってとこは同じだし)

WPF+Prismを試してみる。

WPF

  • Xamlで画面をレイアウトする。
  • データバインド機能を活かした開発スタイル。

Prism

  • MVVMを簡単に実装する為のフレームワーク

MVVMとは

独自のグラフィカルユーザインタフェース (GUI) を持つアプリケーションを、以下に述べるようなModel-View-ViewModelの3つの部分に分割して設計・実装するソフトウェアアーキテクチャパターンである。
[wikipedia]https://ja.wikipedia.org/wiki/Model_View_ViewModel

mvvm.png

View

ViewModelの状態をバインディングによって表現するためのUI

ViewModel

プロパティと画面の状態、コマンドなど持つもの。
UIの状態(コントロールの有効/無効)もプロパティとして持ち、
データバインドで状態をバインドする。

Model

データアクセスとビジネスロジック、データを持つ

使ってみる

まずはともかくやってみましょう。
①新しいプロジェクトで、WPFアプリを選択。
CreateNewProject.png

②NugetからPrismのライブラリをインストール。
installPrism.png
バージョンは最新の安定板6.3.0で。

③MainWindowViewModel.csを追加。
AddViewModel.png
で、その中には下記のように記述します。

MainWindowViewModel.cs
using Prism.Mvvm;

namespace WpfApp1
{
    public class MainWindowViewModel : BindableBase
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel()
        {
            //画面初期表示時に表示したい文字をセットしておく
            InputedText = "Init Value";
        }

        /// <summary>
        /// Viewモデルにプロパティの変更を通知するための書き方
        /// </summary>
        private string inputedText;
        public string InputedText
        {
            get
            {
                return this.inputedText;
            }
            set
            {
                this.SetProperty(ref this.inputedText, value);
            }
        }
    }
}

④MainWindow.Xamlにプロパティをバインドするコントロールを配置します。

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WrapPanel>
            <TextBlock Text="ラベル:" Width="auto"  />
            <!-- ViewModelにpublicで作成したプロパティを設定する。 -->
            <TextBox Text="{Binding InputedText}" Width="80" />
        </WrapPanel>
    </Grid>
</Window>

⑤MainWIndow.xamlのコードビハインドで、画面のDataContextにMainWIndowViewModelを指定します。

MainWIndow.xaml.cs
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //viewはDataContext経由でViewModelを取得するので、設定する
            this.DataContext = new MainWindowViewModel();
        }
    }
}

⑥デバッグ起動すると値が入った状態で画面が起動します。

MainWindowImg.png

初期値として設定した"Init Value"がデータバインドされたことが確認できますね。

続いて、画面にボタンを置いて、何か処理をさせる流れを作ってみましょう。

⑦Commandクラスを追加して、ICommandインターフェースを実装し、コンストラクタでViewモデルを受け取るようにします。

MainWindowTestCommand.cs
using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApp1
{
    public class MainWindowTestCommand : ICommand
    {
        /// <summary>
        /// ViewModelを触りたいので、コンストラクタで受け取るようにする。
        /// </summary>
        public MainWindowTestCommand(MainWindowViewModel vm)
        {
            _vm = vm;
        }

        private MainWindowViewModel _vm;

        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// コマンドを実行可能か判断するプロパティ
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return !string.IsNullOrEmpty(_vm.InputedText);
        }

        /// <summary>
        /// コマンドの実際の処理
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            //テストなのでメッセージを出すだけにしとく
            MessageBox.Show(_vm.InputedText);
        }
    }
}

で、ViweModelも下記をクラス配下に記述します。

MainWindowViewModel.cs
#region コマンド
private ICommand _TestCommand;
/// <summary>
/// テスト用のコマンド
/// </summary>
public ICommand TestCommand
{
    get { return this._TestCommand ?? (this._TestCommand = new MainWindowTestCommand(this)); }
}

#endregion

そのコマンドを実行するボタンを画面上に配置します。
コマンドはCommandプロパティに設定するだけでOK。

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WrapPanel>
            <TextBlock Text="ラベル:" Width="auto"  />
            <!-- ViewModelにpublicで作成したプロパティを設定する。 -->
            <TextBox Text="{Binding InputedText}" Width="80" />
            <Button Content="ボタン" Command="{Binding TestCommand}" />
        </WrapPanel>
    </Grid>
</Window>

起動してみると、ボタンがついた画面が出てきて、
ボタンを押下するとテキストボックスに書いてある内容がメッセージボックスで出るような動作になります。

MainWindowImg2.png

また、テキストボックスが空の際は、メッセージは表示されません。
これは、CommandクラスのCanExecuteでFalseが返る場合は、Excuteメソッドが実行されないためです。

最後に

と、ただprismの基本的なパターンを作成しただけですが、
最初に挙げたWindowsFormの嫌なところはほぼ解消できそうなのではないでしょうか。

  • コード量が増えてくとコードビハインドがぐっちゃぐちゃになりがち
    ⇒ コードビハインドには一行追記しただけ。
    それ以外の処理は、viewModelのプロパティ経由でViewに通知されるから、画面のコントロールを直接触らなくていい。
    コントロールの状態もプロパティで持っていて通知すればいい。
  • 画面項目にセットするだけの仕様と関係ないコードが多くなりが
    ⇒ データバインドで解消。
    画面のコントロールにひたすらセットするようなコードは書く必要なし。
  • 画面デザインが古臭くなりがち
    ⇒ 今回扱ってないけど、メトロっぽいデザインを適用できるライブラリがある。
    MahApp.MetroやMaterial Design In XAML Toolkitでお手軽に今風にできる。

一回だと書ききれませんでしたが、
prismにはMVVMをもっと突き詰めるための機能がまだまだあります。

今後はそこらへんを試してみて、
ある程度の規模の開発で採用できるような開発基盤とか作れたらいいなと思います。