LoginSignup
86
95

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-03-29

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();
        }
    }
}
86
95
3

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
86
95