Elm Architectureを利用したMVVM --- C# WPF
下記記事にてF#でElm ArchitectureをWPFに導入してみました。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
今回はC#で近いものを実装してみたいと思います。
F#の方ではサポートしたModel→ViewModelの変換は省略していますので、必要な場合は追加で実装してください。
アプリケーションコード
今回もカウンタアプリケーションを実装してみたいと思います。
後述しますDataContextクラスを利用すると、下記のようなアプリケーションコードになります。
F#の時と異なり、Modelの更新は判別共用体を利用するのではなく、メソッドを定義することでCommandとして呼び出されるようにしています。
Xaml
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="100" Width="200"> <Grid Height="23" Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition Width="23"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="23"/> </Grid.ColumnDefinitions> <Button Command="{Binding Decrement}" Content="◀" Grid.Column="0" /> <TextBox Text="{Binding Count}" Grid.Column="1" IsReadOnly="True" TextAlignment="Center" VerticalContentAlignment="Center" /> <Button Command="{Binding Increment}" Content="▶" Grid.Column="2" /> </Grid> </Window>
class Model { public int Count { get; } public Model(int count) => Count = count; } class Updater { public Model Increment(Model model) => new Model(model.Count + 1); public Model Decrement(Model model) => new Model(model.Count - 1); } public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new DataContext(new Model(0), new Updater()); } }
DataContextクラス
今回もDynamicObjectを利用して実装します。
Modelのプロパティと、Updaterで定義されたメソッドを呼び出すCommandを公開しています。
Commandクラスは単にExecuteをラムダで登録できるようにしたクラスです。
public class DataContext : DynamicObject, INotifyPropertyChanged { object _model; Dictionary<string, PropertyInfo> _propertyInfos; Dictionary<string, ICommand> _commands; public event PropertyChangedEventHandler PropertyChanged; public DataContext(object initialModel, object updater) { _model = initialModel; _propertyInfos = initialModel.GetType().GetProperties().ToDictionary(x => x.Name); _commands = updater.GetType().GetMethods().ToDictionary( method => method.Name, method => (ICommand)new Command<object>(parameter => { object prevModel = _model; object[] parameters = parameter == null ? new[] { prevModel } : new[] { prevModel, parameter }; _model = method.Invoke(updater, parameters); // 変更箇所通知 if (prevModel != _model) foreach (var property in _propertyInfos.Values) if (property.GetValue(prevModel) != property.GetValue(_model)) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name)); })); } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = _propertyInfos.ContainsKey(binder.Name) ? _propertyInfos[binder.Name].GetValue(_model) : _commands[binder.Name]; return true; } }