子の ViewModel のプロパティの更新通知を受け取って、親の View を更新する方法


ViewModel が子として別の ViewModel を持っているパターンというのは結構よくあることだと思いますが、
その際、子の ViewModel のプロパティの更新通知を受け取って親の View を更新したいということもあると思います。
(例えば、リストビューの要素数をステータスバーに表示したい、など。)

これを、できれば XAML の指定のみでやりたいと思って調べて、実際にやってみました。

XAML の記述

まずは、XAML の記述からです。

MainWindow.xaml
<Window x:Class="WPF_MVVM.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:WPF_MVVM"
        xmlns:local_vm="clr-namespace:WPF_MVVM.ViewModels"
        xmlns:local_v="clr-namespace:WPF_MVVM.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local_vm:MainViewModel />
    </Window.DataContext>
    <Grid>
        <Label x:Name="label" Content="{Binding TextBoxViewModel.Text}" HorizontalAlignment="Left" Margin="39,31,0,0" VerticalAlignment="Top"/>
        <local_v:TextBoxView x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="39,82,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" DataContext="{Binding TextBoxViewModel}"/>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="53,149,0,0" VerticalAlignment="Top" Width="75" Command="{Binding ButtonCommand}" />
    </Grid>
</Window>
TextBoxView.xaml
<TextBox x:Class="WPF_MVVM.Views.TextBoxView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPF_MVVM.Views"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             Text="{Binding Text}">
</TextBox>

MainWindow.xaml がウィンドウの XAML、
TextBoxView.xaml が配置しているテキストボックスの XAML です。

ウィンドウには

  • ラベル
  • テキストボックス
  • ボタン

を配置しています。

ラベル、テキストボックスそれぞれに TextBoxViewModel::Text をバインドしています。
ボタンを押すたびに TextBoxViewModel::Text「True」「False」というテキストが交互に設定され、
それがラベル、テキストボックスの双方に表示される、という仕様になっています。

注目するのはここです。

<local_v:TextBoxView ... DataContext="{Binding TextBoxViewModel}"/>

これは、テキストボックスの DataContext に Window.DataContext.TextBoxViewModel をバインドしています。
こうすることで、子として持っている ViewModel と親の View で配置されているコントロールの DataContext を XAML のみでバインドできます。

C# コードの記述

次に、C# コードの記述です。実装には Livet を使用しています。

MainViewModel.cs
using Livet;
using Livet.Commands;

namespace WPF_MVVM.ViewModels
{
    // ウィンドウの ViewModel
    class MainViewModel : ViewModel
    {
        //  ボタンの Command とバインドしているプロパティ
        public ViewModelCommand ButtonCommand
        {
            get
            {
                return _ButtonCommand ?? (_ButtonCommand = new ViewModelCommand(ClickButton));
            }
        }
        private ViewModelCommand _ButtonCommand;

        //  テキストボックスの ViewModel
        public TextBoxViewModel TextBoxViewModel
        {
            get
            {
                return _TextBoxViewModel;
            }
            set
            {
                _TextBoxViewModel = value;
            }
        }
        public TextBoxViewModel _TextBoxViewModel;

        //  コンストラクタ
        public MainViewModel()
        {
            _TextBoxViewModel = new TextBoxViewModel();
        }

        //  テキスト変更用のフラグ
        private bool _Flag = false;

        //  ボタンを押した時に実行される処理
        private void ClickButton()
        {
            _Flag = !_Flag;
            _TextBoxViewModel.Text = _Flag.ToString();
        }
    }
}
TextBoxViewModel.cs
using Livet;

namespace WPF_MVVM.ViewModels
{
    // テキストボックスの ViewModel
    class TextBoxViewModel : ViewModel
    {
        // テキストボックスに表示するテキスト
        public string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
                RaisePropertyChanged(nameof(Text));
            }
        }
        private string _Text;
    }
}

MainWindowView.cs がウィンドウの ViewModel、
TextBoxViewModel.cs が配置しているテキストボックスの ViewModel です。

データバインドに使用しているプロパティ TextBoxViewModel::Text は自分の更新通知を出しているだけです。