やりたいこと
Model/ViewModel/Viewがあるパターンで、
通常はViewModel(VM)をView(V)のDataContextに設定して、VMのプロパティをVにバインドして画面をコントロールしているが、どうしてもVMからは呼ぶことのできないコントロールのクラスメソッドを呼びたいときなどに、VMからVのメソッドを呼び出したい。
やり方
下記のようにする。
- VM側で、V側のメソッドを登録するためのActionのプロパティを公開する。
- V側で、DataContextの変化のタイミングで、そのActionに登録したいメソッドを登録する。
やり方(コード)
画面(V)側
<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>
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)側
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