LoginSignup
7
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-05-08

やりたいこと

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

7
10
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10