C#初心者のWPF備忘録 P.01 ~MVVMパターンと基本のBinding~


※本記事はC#やWPFはあまり経験がないけれど、他の言語は触ったことがある方にお勧めします
※本記事は初心者の備忘録です

WPFとMVVMパターンのプログラミング

早速ですがWPFはMVVMというプログラミングの構造(?)で書かれるようです。
MVVMはModel-View-ViewModelの大文字部分をつなげたもので
Model・・・見た目(UI)には関係のないロジックの部分
View・・・見た目(UI)に関係するデザインの部分
ViewModel・・・ModelとViewの間に立ちModelとViewを直接結ばない役割をもつ部分
という解釈をしています。
(MVVMについては様々な考え方があるようです)
WPFではView(見た目)は基本的にXAML(ザムルと読むようです)を使って書きます。
基本的な書き方は中身のように開始タグと終了タグで挟みます。
例えばLabelであれば
<Label>Hello World!</Label>
と書くことで"Hello World!"と書かれたLabelを置くことができます。
また、TextBoxのように中身が必要のないものについては
<TextBox></TextBox>
と中身を省略するか
<TextBox />
のように書くこともできます。
ModelとViewModelはC#を使って記述するようですが、C#の記述についてはここでは省略します。

ModelとViewの分離と基本のBinding

なぜMVVMという構造をとろうとするのかという話になりますが
ロジック(Model)部分と見た目(View)を分けたいから、ということのようです。
さっそく一つプロジェクトを作成しコードを書いてみます。

MainWindow.xaml
<Window x:Class="BIBOROKU_001.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:BIBOROKU_001"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <TextBox Name="NameBox" TextChanged="TextChanged" />
        <TextBlock Name="Morining" />
        <TextBlock Name="Noon" />
        <TextBlock Name="Evening" />
    </StackPanel>
</Window>
MainWindow.xaml.cs
///usingは省略しています

namespace BIBOROKU_001
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TextChanged(object sender, TextChangedEventArgs e)
        {
            if(NameBox.Text != "")
            {
                Morining.Text = NameBox.Text + "さん、おはようございます!";
                Noon.Text = NameBox.Text + "さん、こんにちは!";
                Evening.Text = NameBox.Text + "さん、こんばんは!"; 
            }
            else
            {
                Morining.Text = "";
                Noon.Text =  "";
                Evening.Text =  "";
            }
        }
    }
}

WindowsFormアプリケーションなんかではこのようにTextBoxのTextChangedイベントに絡めて
TextBlockの中身を書き換えるような記述になると思います。ただこのような記述は修正が大変で
XAML側の"NameBox"という名前を"NameBox1"にしたいとなると、それに合わせてC#側のコードも
書き換えなければなりません。これが分離できていない、という一例だと思います。
そこで新たにMainWindowWPF.xamlというウィンドウを作って次のように書いてみます。

MainWindowWPF.xaml
<Window x:Class="BIBOROKU_001.MainWindowWPF"
        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:BIBOROKU_001"
        mc:Ignorable="d"
        Title="MainWindowWPF" Height="450" Width="800">
    <StackPanel>
        <TextBox Text="{Binding InputName,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding Morining,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding Noon,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding Evening,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
</Window>

なにやら少しにぎやかになってきました。MainWindow.xamlと比べName="xxxxx"という記述が
なくなり代わりにText="{Binding xxxx}"となっています。Bindは結びつけるといった意味ですのでTextBoxを例にすると

<TextBox Text="{Binding InputName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />

TextBoxText
InputNameに結び付けて
ModeOneWayToSource(ソース方向への一方通行)
UpdateSourceTriggerPropertyChanged(プロパティが変わったら)
という意味のようです。
ModeにはほかにTwoWayOneWay(ソースからの一方通行)などが
UpdateSourceTriggerにはExplicitLostFocusなどがあるようです。
またこのときのC#側の記述は

MainWindowWPF.xaml.cs

///usingは省略しています

namespace BIBOROKU_001
{
    /// <summary>
    /// MainWindowWPF.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindowWPF : Window
    {
        public MainWindowWPF()
        {
            InitializeComponent();
            this.DataContext = new MainWindowWPFVM();
        }

        ///実際にはここから先は分離して別ファイルに

        public class MainWindowWPFVM : INotifyPropertyChanged
        {
            public MainWindowWPFVM()
            {
            }
            private string _InputName;
            public string InputName
            {
                get
                {
                    return _InputName;
                }

                set
                {
                    _InputName = value;
                    RaisePropertyChanged();
                    RaisePropertyChanged("Morining");
                    RaisePropertyChanged("Noon");
                    RaisePropertyChanged("Evening");
                }
            }

            public string Morining
            {
                get
                {
                    if (InputName != "")
                        return InputName + "さん、おはようございます!";
                    else
                        return "";
                }
            }
            public string Noon
            {
                get
                {
                    if (InputName != "")
                        return InputName + "さん、こんにちは!";
                    else
                        return "";
                }
            }
            public string Evening
            {
                get
                {
                    if (InputName != "")
                        return InputName + "さん、こんばんは!";
                    else
                        return "";
                }
            }

            //INotifyPropertyChanged実装
            public event PropertyChangedEventHandler PropertyChanged = delegate { };

            //INotifyPropertyChanged.PropertyChangedイベントを発生させる
            private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
            {
                if (propertyName != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }


}

こちらもにぎやかですね。ただMainWindowWPFの中はずいぶんとすっきりしました。
唯一増えたのはthis.DataContext = new MainWindowWPFVM();の部分だけです。
Context自体は"文脈"のような意味があるようですが、分かりにくいのでここでは"情報"と捉えると
この(MainWindowWPF)データの情報はMainWindowWPFVMのインスタンスにありますよ
という感じでしょうか。
そしてそのMainWindowWPFVM内には、XAML側でBindingしたInputNameMorining,Noon,Eveningといったプロパティが含まれています。ただし単にプロパティを定義しただけではだめで、プロパティが変わったら変更通知をしなければならないようです。
INotifyPropertyChangedRaisePRopertyChangedというのがそれにあたるようですが、今回は省略します。

今回のまとめ

初回ということで、WPFとMVVMパターンについて、基本的なBindingについて書きました。
1.構造をModel-View-ViewModelに分ける
2.ViewではViewModelクラスのインスタンスの生成しDataContextにBindingする
3.Bindingではプロパティを変更したら変更した通知がないと更新されない

今回はこのあたりで失礼します。