トップ回答者
WPFをMVVMで使用した場合のバインド内容の変換について

質問
-
WPFをMVVMに則ってバインドを多用してアプリケーションを作成しようとしています。
環境は下記のとおりです。
- WPF 4.0
- Visual Studio 2013
- MVVMフレームワークLivetを使用検討中
若干WPFから離れてMVVMの概念的な話になるのかもしれないのですが・・・
Model側で状態を示すenum(あるいはboolなど、なんでも構いません)を持ち、それをデータバインディングを用いてWindow(View)に表示する場合に、
「モデル内ではこういう管理方法ではあるけど、View上での表示自体は別であって欲しい」という要求であった場合、
「Model内の表現をViewで見せたい形に変更する処理」はModel, ViewModel, Viewのどこが担うものなのでしょうか?要素的には見せ方、つまりデザインに近いものなのでViewが担うのが妥当のように感じるのですが、データバインディングされている要素をView側で変換することができるのでしょうか?
簡単なサンプルコードなどを紹介していただけると助かります。
回答よろしくお願いします。
回答
-
こんにちは。
Viewで実装するべきだと思います。
Viewで対応したほうがViewModelやModelでやるよりも多言語対応などもしやすいですしね。どういった名称で表示するべきなのかについてはViewが担えば良いと私は思います。
変換の仕方は色々あると思うのでなんでも良いと思いますが。(TriggerでもConverterでも)
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:ViewModel /> </Window.DataContext> <Grid> <TextBox> <TextBox.Style> <Style TargetType="TextBox"> <Setter Property="Text" Value="" /> <Style.Triggers> <DataTrigger Binding="{Binding ModelState}" Value="Stopped"> <Setter Property="Text" Value="停止中" /> </DataTrigger> <DataTrigger Binding="{Binding ModelState}" Value="Idling"> <Setter Property="Text" Value="待機中" /> </DataTrigger> <DataTrigger Binding="{Binding ModelState}" Value="Running"> <Setter Property="Text" Value="実行中" /> </DataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox> </Grid> </Window>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class ViewModel : INotifyPropertyChanged { public ViewModel() { ModelState = State.Idling; } private State _ModelState; public State ModelState { get { return _ModelState; } set { _ModelState = value; } } protected void OnPropertyChanged(string propertyName) { if(this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; } public enum State { Stopped, Idling, Running }
- 編集済み いわさ Tak1waMVP, Moderator 2015年3月26日 7:36
- 回答としてマーク h--s 2015年3月26日 8:34
-
View側で同じようにConverterなどを使って、Stateを判定してEnabledの値を切り替えることも出来ます。
ボタンであればCommandをバインドしていると思うのでViewModelでCommandのCanExecuteによって、Enableを切り替える方法もあります。後者については、かずきさんの四則演算のサンプルがわかりやすいかと。
https://code.msdn.microsoft.com/windowsdesktop/MVVM-d8261534
- 編集済み いわさ Tak1waMVP, Moderator 2015年3月26日 8:10
- 回答としてマーク h--s 2015年3月26日 8:34
-
DataTriggerで制御すれば良いと思います。Styleとしてリソースに定義しておけば、いろいろな画面で使い回しがききます。
私はエラーがあると登録ボタンや変更ボタンを押せないようにする場合、この処理は各画面で共通になりますので、そのようにしています。エラーの有無を表すプロパティ名を決めておき、各ViewModelにはそれがあり、それをDataTriggerで見て、登録ボタンの制御しています。ご質問に書かれているExecutableのようなプロパティですね。
★良い回答には回答済みマークを付けよう! MVP - .NET http://d.hatena.ne.jp/trapemiya/
- 編集済み trapemiyaModerator 2015年3月26日 8:11 一部変更
- 回答としてマーク h--s 2015年3月26日 8:34
すべての返信
-
こんにちは。
Viewで実装するべきだと思います。
Viewで対応したほうがViewModelやModelでやるよりも多言語対応などもしやすいですしね。どういった名称で表示するべきなのかについてはViewが担えば良いと私は思います。
変換の仕方は色々あると思うのでなんでも良いと思いますが。(TriggerでもConverterでも)
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:ViewModel /> </Window.DataContext> <Grid> <TextBox> <TextBox.Style> <Style TargetType="TextBox"> <Setter Property="Text" Value="" /> <Style.Triggers> <DataTrigger Binding="{Binding ModelState}" Value="Stopped"> <Setter Property="Text" Value="停止中" /> </DataTrigger> <DataTrigger Binding="{Binding ModelState}" Value="Idling"> <Setter Property="Text" Value="待機中" /> </DataTrigger> <DataTrigger Binding="{Binding ModelState}" Value="Running"> <Setter Property="Text" Value="実行中" /> </DataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox> </Grid> </Window>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class ViewModel : INotifyPropertyChanged { public ViewModel() { ModelState = State.Idling; } private State _ModelState; public State ModelState { get { return _ModelState; } set { _ModelState = value; } } protected void OnPropertyChanged(string propertyName) { if(this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; } public enum State { Stopped, Idling, Running }
- 編集済み いわさ Tak1waMVP, Moderator 2015年3月26日 7:36
- 回答としてマーク h--s 2015年3月26日 8:34
-
早い返信ありがとうございます。
やはりViewで処理することなんですね。
DataTriggerやConverterについては勉強不足でした。
もう一つ踏み込んで質問をさせて下さい。
例えば上記例の場合でModelの状態からWindow上のボタンのEnable要素が変化するような場合、
- Stopped状態 押せる
- Idling状態 押せる
- Running状態 押せない
この場合もConverterなどを駆使して表現方法をView側で変えるものなのでしょうか?
それともModelあるいはViewModelでExecutableのような名前のプロパティを用意し、それとバインドするのが適切なのでしょうか?
どこまでがView要素でどこまでがModel要素かみたいな曖昧な話になるのかもしれませんが、見解を聞かせていただくと助かります。
-
View側で同じようにConverterなどを使って、Stateを判定してEnabledの値を切り替えることも出来ます。
ボタンであればCommandをバインドしていると思うのでViewModelでCommandのCanExecuteによって、Enableを切り替える方法もあります。後者については、かずきさんの四則演算のサンプルがわかりやすいかと。
https://code.msdn.microsoft.com/windowsdesktop/MVVM-d8261534
- 編集済み いわさ Tak1waMVP, Moderator 2015年3月26日 8:10
- 回答としてマーク h--s 2015年3月26日 8:34
-
DataTriggerで制御すれば良いと思います。Styleとしてリソースに定義しておけば、いろいろな画面で使い回しがききます。
私はエラーがあると登録ボタンや変更ボタンを押せないようにする場合、この処理は各画面で共通になりますので、そのようにしています。エラーの有無を表すプロパティ名を決めておき、各ViewModelにはそれがあり、それをDataTriggerで見て、登録ボタンの制御しています。ご質問に書かれているExecutableのようなプロパティですね。
★良い回答には回答済みマークを付けよう! MVP - .NET http://d.hatena.ne.jp/trapemiya/
- 編集済み trapemiyaModerator 2015年3月26日 8:11 一部変更
- 回答としてマーク h--s 2015年3月26日 8:34