WPF、ComboBoxのGotFocusイベント MVVM版

3 min read読了の目安(約3300字

WPF、ComboBoxのGotFocusイベントを、MVVMにしてみましょう。

準備

NuGetで次をインストールします。

  • Microsoft.Xaml.Behaviors.Wpf 1.1.31
  • ReactiveProperty 7.8.3

方法1 Behaviorsを利用する

  • BehaviorsのInvokeCommandActionで、PassEventArgsToCommand="True" とすることで、EventArgsを渡すことが出来ます。
  • ICommandを利用しても良いですが、ReactiveCommandにはICommandが含まれているのでそれを利用すると簡潔なコードになります。

View

xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
        <ComboBox Width="120" Height="30">
            <bh:Interaction.Triggers>
                <bh:EventTrigger EventName="GotFocus">
                    <bh:InvokeCommandAction Command="{Binding ComboBoxFocus}" PassEventArgsToCommand="True" />
                </bh:EventTrigger>
            </bh:Interaction.Triggers>
            <ComboBoxItem>Test Item1</ComboBoxItem>
            <ComboBoxItem>Test Item2</ComboBoxItem>
            <ComboBoxItem>Test Item3</ComboBoxItem>
        </ComboBox>
  • ModelになるMainManagerのコードは、ここでは利用しませんので、省略しています。
  • ComboBoxにフォーカスがあたった時の処理を、Viewで行うか、ViewModelで行うか、Modelで行うかは、その処理の内容がどの領分にあたるのかによって、決めれば良いです。

ViewModel

    public class MainViewModel : INotifyPropertyChanged, IDisposable
    {

        private MainManager Model { get; }
        public ReactiveCommand<RoutedEventArgs> ComboBoxFocus { get; } = new();
	
        public MainViewModel(MainManager model)
        {
            Model = model;
            ComboBoxFocus.Subscribe(e => ComboBox_GotFocus(e)).AddTo(Disposable);
        }

        private void ComboBox_GotFocus(RoutedEventArgs e)
        {
            var type = e.OriginalSource.GetType();
            if (type == typeof(ComboBox))
            {
                Debug.WriteLine("ComboBox Foucus");
            }
            else if (type == typeof(ComboBoxItem))
            {
                Debug.WriteLine("ComboBoxItem Foucus");
                Debug.WriteLine(((ComboBoxItem)e.OriginalSource).Content);
            }
        }

     // INotifyPropertyChanged を実装
     // Dispose を実装

方法2 Behaviorsを利用しない

Behaviorsを利用しないなら、Viewのコードビハインドにイベントを定義します。Viewから、VMの処理を呼び出せばOKです。
VMの、ComboBox_GotFocusは、Publicにします。

View

    public partial class MainView : Window
    {
        MainViewModel vm;
        public MainView(MainViewModel viewModel)
        {
            InitializeComponent();
            this.vm = viewModel;
            this.DataContext = vm;
        }

        private void ComboBox_GotFocus(object sender, RoutedEventArgs e)
        {
            vm.ComboBox_GotFocus(e);
        }
    }

環境

.NET5, WPF

MVVMについて

MVVMにしようとすると、Behaviorsを利用する方法では、XAMLに5行も書かないといけません。長すぎます。従って、コードの保守も難しくなります。ReactiveCommandを利用しないと、さらにコードは長くなります。
WPF、ComboBoxのGotFocusイベントなら、簡潔ですよね。けれども、コードビハインドでなにもかもするのは、複雑なプログラムでは破綻しやすいと思います。すると、MVVMを利用せざる得ないわけです。

MVVMは、やたらとコードが長くなるのが、デメリット極まりないです。これは、Prismを使うと解消するのでしょうか? なんどかPrismにトライしようと調べるのですが、とてもそうは思えないです。また、破壊的変更もままあり、Prismのバージョンアップに対応しきれないと判断し、利用しないことにしました。

MVVMに足りていないことの1つは、それを実現するためのフレームワークや、.NETの機能でしょう。例えば、次のコードはエラーになりますが、このようにComboBoxのイベントにバインディングできれば、ぐっと簡潔になると思います。

        <ComboBox GotFocus="{Binding ComboBox_GotFocus}" />

こうした実装は議論はされたのだとは思いますが、未だなっていないということは、デメリットがあるのか、実装が大変すぎるのか・・・
いずれにしろ、現状の.NET環境で、MVVMに即してコードを書くのは、未だ課題が多いということです。