Edited at

[C#/WPF]ビューモデルからビューのメソッドを呼ぶ


やりたいこと

Model/ViewModel/Viewがあるパターンで、

通常はViewModel(VM)をView(V)のDataContextに設定して、VMのプロパティをVにバインドして画面をコントロールしているが、どうしてもVMからは呼ぶことのできないコントロールのクラスメソッドを呼びたいときなどに、VMからVのメソッドを呼び出したい。


やり方

下記のようにする。


  • VM側で、V側のメソッドを登録するためのActionのプロパティを公開する。

  • V側で、DataContextの変化のタイミングで、そのActionに登録したいメソッドを登録する。


やり方(コード)


画面(V)側


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="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Name="MyTextBlock">ABC</TextBlock>
<Button Command="{Binding MyCommand}">ボタン</Button>
</StackPanel>
</Grid>
</Window>


MainWindow.xaml.cs

using System.Windows;

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

// ★データコンテキスト登録(変化)時、VM側のActionに自分(View側)のメソッドを登録する
this.DataContextChanged += (s, e) =>
{
if (e.NewValue is ViewModel)
{
var vm = e.NewValue as ViewModel;
vm.ViewsideAction = this.ViewsideAction; // ★スライダーの範囲表示更新処理をViewModelに登録
}
};

// DataContextを登録
this.DataContext = new ViewModel();
}

private void ViewsideAction()
{
MyTextBlock.Text = "ビュー側のメソッドが呼ばれました。";
}
}
}



ビューモデル(VM)側


ViewModel.cs

using System;

namespace WpfApp1
{
class ViewModel : BindingBase
{
// ★ビュー側のメソッドを登録するためのAction
public Action ViewsideAction { get; set; }

// ボタンを押したときの処理
public DelegateCommand MyCommand { get; private set; }

public ViewModel()
{
// ボタンを押したときの処理
MyCommand = new DelegateCommand(
() =>
{
// ★登録したメソッドの呼び出し
ViewsideAction?.Invoke();
},
() => { return true; });
}
}
}



要点


VM側で、Actionを公開する

VM側で、V側のメソッドを登録するためのActionを公開する。

登録は、V側で実施する。

public Action ViewsideAction { get; set; }


V側で、VMのActionを登録する

VMとVが紐づけられた時 = V側のDataContextにVMへの参照が入れられた時に、

VM側で公開したActionに、Vのメソッドを登録する。

(DataContextChangedは、this.DataContext = new ViewModel();でDataContextが変化したときに呼ばれるイメージ。)

// データコンテキスト登録(変化)時、VM側のActionに自分(View側)のメソッドを登録する

this.DataContextChanged += (s, e) =>
{
if (e.NewValue is ViewModel)
{
var vm = e.NewValue as ViewModel;
vm.ViewsideAction = this.ViewsideAction; // ★スライダーの範囲表示更新処理をViewModelに登録
}
};


呼び出す

あとは、VM側で、実際に登録したActionを呼び出せばV側のメソッドが呼ばれる。

ViewsideAction?.Invoke();


考察

本当は、VMのプロパティをバインドすることで画面表示を制御したいが、どうしてもVMからは呼ぶことのできないコントロールのクラスメソッドを呼びたい時に、この方法で回避した。

※詳しくは忘れたが、確かListBoxの中の〇番目のitemを表示したい、というときに、ListBox.ScrollIntoView(Object)のメソッドでできるようなことをしたかったのだが、VM側からそれがどうしてもできなかったので、この方法でScrollIntoViewを呼んでやることで回避した記憶がある。

こういう抜け道的なことをした時、自分はMVVMできてないなぁと思ってしまう。

(正しい解決方法はあるのか??)


参考

MVVMパターンでVMからVを操作する方法 再考

https://blog.okazuki.jp/entry/20100925/1285426626