none
WPFをMVVMで使用した場合のバインド内容の変換について RRS feed

  • 質問

  • 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側で変換することができるのでしょうか?

    簡単なサンプルコードなどを紹介していただけると助かります。

    回答よろしくお願いします。

    2015年3月26日 6:52

回答

  • こんにちは。

    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
    }


    2015年3月26日 7:28
    モデレータ
  • Converterはご存知でしょうか?
    一般的にはConverterを作成し、それを利用してViewで変換して表示するのが素直です。
    基本的な考え方は書式指定子と同じで、例えばDateTime型のデータを書式指定子でどのように表示するのかと同じ考え方です。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク h--s 2015年3月26日 8:34
    2015年3月26日 7:36
    モデレータ
  • View側で同じようにConverterなどを使って、Stateを判定してEnabledの値を切り替えることも出来ます。
    ボタンであればCommandをバインドしていると思うのでViewModelでCommandのCanExecuteによって、Enableを切り替える方法もあります。

    後者については、かずきさんの四則演算のサンプルがわかりやすいかと。

    https://code.msdn.microsoft.com/windowsdesktop/MVVM-d8261534


    2015年3月26日 8:02
    モデレータ
  • 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
    2015年3月26日 8:09
    モデレータ

すべての返信

  • こんにちは。

    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
    }


    2015年3月26日 7:28
    モデレータ
  • Converterはご存知でしょうか?
    一般的にはConverterを作成し、それを利用してViewで変換して表示するのが素直です。
    基本的な考え方は書式指定子と同じで、例えばDateTime型のデータを書式指定子でどのように表示するのかと同じ考え方です。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク h--s 2015年3月26日 8:34
    2015年3月26日 7:36
    モデレータ
  • 早い返信ありがとうございます。

    やはりViewで処理することなんですね。

    DataTriggerやConverterについては勉強不足でした。

    もう一つ踏み込んで質問をさせて下さい。

    例えば上記例の場合でModelの状態からWindow上のボタンのEnable要素が変化するような場合、

    • Stopped状態 押せる
    • Idling状態 押せる
    • Running状態 押せない

    この場合もConverterなどを駆使して表現方法をView側で変えるものなのでしょうか?

    それともModelあるいはViewModelでExecutableのような名前のプロパティを用意し、それとバインドするのが適切なのでしょうか?

    どこまでがView要素でどこまでがModel要素かみたいな曖昧な話になるのかもしれませんが、見解を聞かせていただくと助かります。

    2015年3月26日 7:48
  • View側で同じようにConverterなどを使って、Stateを判定してEnabledの値を切り替えることも出来ます。
    ボタンであればCommandをバインドしていると思うのでViewModelでCommandのCanExecuteによって、Enableを切り替える方法もあります。

    後者については、かずきさんの四則演算のサンプルがわかりやすいかと。

    https://code.msdn.microsoft.com/windowsdesktop/MVVM-d8261534


    2015年3月26日 8:02
    モデレータ
  • 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
    2015年3月26日 8:09
    モデレータ