Help us understand the problem. What is going on with this article?

WPF用 ViewModelの基底クラスサンプル

More than 3 years have passed since last update.

WPFでアプリケーション開発する場合、MVVMパターンを利用しての開発が推奨されています。アプリケーションを Model, View, ViewModel の3つの層に分割して開発する手法です。

WPFではMVVMパターンを用いて開発するためにデータバインドとかいろいろ機能が用意されているのですが、それらを利用するためには幾つかのお約束があって、ViewModelではINotifyPropertyChangedIDataErrorInfoという2つのインターフェイスを実装することが求めらます(別に必須ではないですが)。また、ViewからViewModelへの通知を受け取る場合、ICommandインターフェイスを介して行われるため、この実装も必要となります。

これらの実装はほぼ定形ですので、ViewModelの基底クラスとして作成しておくのが普通です。最低限の機能を実装した基底クラスを以下に示します。

ViewModelの基底クラスのサンプル

INotifyPropertyChangedIDataErrorInfoの実装、及び、ICommandを実装するためのヘルパークラスを実装しています。

ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfTest
{
    #region ** Class : ViewModelBase
    /// <summary>
    /// ViewModelの基底クラス
    /// INotifyPropertyChanged と IDataErrorInfo を実装する
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
    {
        #region == implement of INotifyPropertyChanged ==

        // INotifyPropertyChanged.PropertyChanged の実装
        public event PropertyChangedEventHandler PropertyChanged;

        // INotifyPropertyChanged.PropertyChangedイベントを発生させる。
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region == implemnt of IDataErrorInfo ==

        // IDataErrorInfo用のエラーメッセージを保持する辞書
        private Dictionary<string, string> _ErrorMessages = new Dictionary<string, string>();

        // IDataErrorInfo.Error の実装
        string IDataErrorInfo.Error
        {
            get { return (_ErrorMessages.Count > 0) ? "Has Error" : null; }
        }

        // IDataErrorInfo.Item の実装
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                if (_ErrorMessages.ContainsKey(columnName))
                    return _ErrorMessages[columnName];
                else
                    return null;
            }
        }

        // エラーメッセージのセット
        protected void SetError(string propertyName, string errorMessage)
        {
            _ErrorMessages[propertyName] = errorMessage;
        }

        // エラーメッセージのクリア
        protected void ClearErrror(string propertyName)
        {
            if (_ErrorMessages.ContainsKey(propertyName))
                _ErrorMessages.Remove(propertyName);
        }

        #endregion

        #region == implemnt of ICommand Helper ==

        #region ** Class : _DelegateCommand
        // ICommand実装用のヘルパークラス
        private class _DelegateCommand : ICommand
        {
            private Action<object> _Command;        // コマンド本体
            private Func<object, bool> _CanExecute;  // 実行可否

            // コンストラクタ
            public _DelegateCommand(Action<object> command, Func<object, bool> canExecute = null)
            {
                if (command == null)
                    throw new ArgumentNullException();

                _Command = command;
                _CanExecute = canExecute;
            }

            // ICommand.Executeの実装
            void ICommand.Execute(object parameter)
            {
                _Command(parameter);
            }

            // ICommand.Executeの実装
            bool ICommand.CanExecute(object parameter)
            {
                if (_CanExecute != null)
                    return _CanExecute(parameter);
                else
                    return true;
            }

            // ICommand.CanExecuteChanged の実装
            event EventHandler ICommand.CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
        }
        #endregion

        // コマンドの生成
        protected ICommand CreateCommand(Action<object> command, Func<object, bool> canExecute = null)
        {
            return new _DelegateCommand(command, canExecute);
        }

        #endregion
    }
    #endregion
}

上記コードを利用したサンプル

ViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfTest
{
    #region ** Class : ViewModel
    /// <summary>
    /// ViewModel
    /// </summary>
    class ViewModel : ViewModelBase
    {
        // プロパティの値を保持するメンバ変数
        private int _property1 = 0;

        // プロパティ
        public int Property1
        {
            get { return _property1; }
            set
            {
                // 値が変更されたら、INotifyPropertyChanged.PropertyChanged を発生させる。
                if (_property1 != value)
                {
                    _property1 = value;

                    // 値は0以上、10以下。それ以外はエラー
                    if (_property1 >= 0 && _property1 <= 10)
                        ClearErrror("Property1");
                    else
                        SetError("Property1", "Error: argument out of range");

                    RaisePropertyChanged("Property1");

                }
            }
        }

        public ICommand Add1Command { get; private set; }

        // コンストラクタ
        public ViewModel()
        {
            Property1 = 0;
            Add1Command = CreateCommand(v => { Property1 = Property1 + 1; });
        }
    }
    #endregion
}
MainWindow.xaml
<Window x:Class="WpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfTest"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:ViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource ViewModel}">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock Text="数値を入力してください:" FontSize="14" Margin="10" />
            <TextBox Text="{Binding Property1, ValidatesOnDataErrors=True, Mode=TwoWay}" FontSize="14" Width="50" Margin="10" HorizontalContentAlignment="Right" />
        </StackPanel>
        <Button Grid.Row="1" Content="1を加算" FontSize="14" Padding="20,10,20,10" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Add1Command}" />
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfTest
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away