none
MVVMでの画面の制御方法(behavior利用以外) RRS feed

  • 質問

  • はじめまして、山田と申します。
    WPFを勉強しております。

    MVVMについて勉強しており、色々なサイトを閲覧させていただきサンプルなどをみさせて頂き、試しにコードを書いて勉強しております。
    MVVMで書いた際に疑問になったことがございまして、質問させて頂きました。

    スキルがついてけてないのですが、MVVMでは、Commandを利用して、イベントを処理していくのですが、
    画面に対する制御、例えば、CheckboxにCheckが入ったときに、他のTextBoxをDisableにするなどの処理を行う場合、
    behaviorを定義して、制御することは調べてわかりました。他に、画面の表示の制御方法として行うことはできないのでしょうか?
    申し訳ございませんが、アドバイス宜しくお願いします。


    MVVMでのコードの書き方は、すっきりした感じもありますが、コードの記述量が多く初期のJavaServletをやっているような感覚に思えました。
    2009年8月10日 10:18

回答

  • Converterを介したbindingの方法ですが・・・

    ・CheckBoxのIsCheckedと、TextBoxのVisibilityの両方でBindingが設定されていますが、片方だけでいいと思います
     TextBoxがCheckBoxのチェック有無を参照する方が自分はしっくりくるので、↓のサンプルではそうしています。

    ・TextBoxがCheckBoxのIsCheckedを参照する場合、コンバーターが逆になるので、ConvertとConvertBackを入れ替えます


    動作を確認したコードを貼りますので参考にしてください。

    <Window x:Class="Wpf_CheckBoxConverter.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Wpf_CheckBoxConverter"
        Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <local:MyCheckedConverter x:Key="myConv" />
        </Window.Resources>
        <StackPanel>
            <CheckBox x:Name="checkBox" HorizontalAlignment="Left" >CheckBox</CheckBox>
            <TextBox Visibility="{Binding IsChecked, Converter={StaticResource myConv}, ElementName=checkBox, Mode=Default}">文字</TextBox>
        </StackPanel>
    </Window>

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    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 Wpf_CheckBoxConverter
    {
    	/// <summary>
    	/// Window1.xaml の相互作用ロジック
    	/// </summary>
    	public partial class Window1 : Window
    	{
    		public Window1()
    		{
    			InitializeComponent();
    		}
    	}
    
    	public class MyCheckedConverter : IValueConverter
    	{
    		public object ConvertBack(object value, Type targetType,object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (value is Visibility)
    			{
    
    				if ((Visibility)value == Visibility.Visible)
    					return true;
    			}
    			return false;
    		}
    
    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (value is bool)
    			{
    
    				if ((bool)value == false)
    					return Visibility.Collapsed;
    			}
    			return Visibility.Visible;
    		}
    	}
    }
    

    • 回答としてマーク a_山田 2009年8月14日 4:57
    2009年8月11日 9:17

すべての返信

  • チェックボックスとTextBoxのDisableを連動するだけなら、MVVM関係なく、二つのコントロールのプロパティ間をDataBindingさせるだけでいいと思います。
    bool と Visibility のConverterを介してBindさせればできました。
    (参考)
    http://blogs.jp.infragistics.com/blogs/dikehara/archive/2009/05/29/tips-xamdatagrid-4-1-wpf.aspx

    イベントハンドラで処理するとしても、Viewクラスだけで完結できると思います。

    複数のView間など、複雑になるようであれば、ViewModelにプロパティを作って、それぞれのコントロールと、ViewModelのプロパティをバインドさせることも可能だと思います。
    ユーザーコントロールが絡んでくる場合は、こちらの質問のえむナウさんの解答も参考になると思います。
    http://social.msdn.microsoft.com/Forums/ja-JP/wpfja/thread/31d30f80-7eb6-45ee-9e84-cc2af2d1b5b7
    2009年8月10日 21:11
  • ちょっと前にまとめてみました。ただ、基本はbehaviorかなと今のところ思っています。

    【WPF】 MVVMでListBoxのSelectionChangedイベントをViewModelで拾いたい。
    http://blogs.wankuma.com/trapemiya/archive/2009/08/04/179782.aspx?Pending=true


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年8月11日 3:56
    モデレータ
  • ご回答ありがとうございます。

    bool と Visibility のConverterを介してBindさせる方法を、まず考えてみました。
    Sampleを作りながら、試してみたのですが、Converterでコントロール間のバインディングしてみたのですが、
    うまく設定できづじまいでした。

    ConveterのconvatBackメソッドまでは呼ばれているのですが、Checkboxが外れたときに、
    TextBoxがVisibleになってくれません。
    お手数をおかけしますが、ご指摘いただけるとありがたいです。

    また、trapemiya さんが紹介されているページをみさせていただいたのですが、
    ViewModelから、Viewのコントールの利用方法自体が、まだ、私がわかっていないため、
    まだ、試すことができませんでした。すいません。

    お手数をおかけしますが、宜しくお願い致します。


    window1.xaml
    <Window x:Class="XDG_HideFields_CS.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        mlns:local="clr-namespace:XDG_HideFields_CS"
        Title="Window1" Height="300" Width="600">
        <Window.Resources>
           <local:MyCheckedConverter x:Key="myConv" />
        </Window.Resources>
    
    
        <Grid>
           <CheckBox HorizontalAlignment="Left" Margin="49,131,0,115" Width="77"
               IsChecked="{Binding IsChecked, Converter={StaticResource myConv}}">CheckBox</CheckBox>
           <TextBox Margin="127,128,0,0" HorizontalAlignment="Left" Width="120"
               Visibility="{Binding TextVisible, Converter={StaticResource myConv}, FallbackValue=Collapsed}" Height="23" VerticalAlignment="Top" />
           <Button Height="23" Margin="84,0,0,56" Name="button1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="75">Button</Button>
        </Grid>
    
    
    </Window>
    
    



    window1.xaml.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    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 XDG_HideFields_CS
    {
    
    
        /// <summary>
        /// Window1.xaml の相互作用ロジック
        /// </summary>
        public partial class Window1 : Window
        {
            public Person person;
            public Window1()
            {
                person = new Person();
                InitializeComponent();
                DataContext = person;
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                Console.WriteLine("test");
            }     } }



    MyCheckedConverter.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Data;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace XDG_HideFields_CS
    {
    
    public class MyCheckedConverter : IValueConverter { #region IValueConverter メンバ
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is Visibility) { if ((Visibility)value == Visibility.Visible) return true; } return false; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is bool) { if ((bool)value == false) return Visibility.Collapsed; } return Visibility.Visible; }
    #endregion } }
     


    • 編集済み a_山田 2009年8月11日 5:37 コードに不用な改行がはいったため
    2009年8月11日 5:07
  • Converterを介したbindingの方法ですが・・・

    ・CheckBoxのIsCheckedと、TextBoxのVisibilityの両方でBindingが設定されていますが、片方だけでいいと思います
     TextBoxがCheckBoxのチェック有無を参照する方が自分はしっくりくるので、↓のサンプルではそうしています。

    ・TextBoxがCheckBoxのIsCheckedを参照する場合、コンバーターが逆になるので、ConvertとConvertBackを入れ替えます


    動作を確認したコードを貼りますので参考にしてください。

    <Window x:Class="Wpf_CheckBoxConverter.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Wpf_CheckBoxConverter"
        Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <local:MyCheckedConverter x:Key="myConv" />
        </Window.Resources>
        <StackPanel>
            <CheckBox x:Name="checkBox" HorizontalAlignment="Left" >CheckBox</CheckBox>
            <TextBox Visibility="{Binding IsChecked, Converter={StaticResource myConv}, ElementName=checkBox, Mode=Default}">文字</TextBox>
        </StackPanel>
    </Window>

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    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 Wpf_CheckBoxConverter
    {
    	/// <summary>
    	/// Window1.xaml の相互作用ロジック
    	/// </summary>
    	public partial class Window1 : Window
    	{
    		public Window1()
    		{
    			InitializeComponent();
    		}
    	}
    
    	public class MyCheckedConverter : IValueConverter
    	{
    		public object ConvertBack(object value, Type targetType,object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (value is Visibility)
    			{
    
    				if ((Visibility)value == Visibility.Visible)
    					return true;
    			}
    			return false;
    		}
    
    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (value is bool)
    			{
    
    				if ((bool)value == false)
    					return Visibility.Collapsed;
    			}
    			return Visibility.Visible;
    		}
    	}
    }
    

    • 回答としてマーク a_山田 2009年8月14日 4:57
    2009年8月11日 9:17
  • ViewModel は,
    View と Model 間の データ型のインピーダンスの調整機能のほか,
    Model に存在しないけれど,View に必要なデータを置いておく場所でもありますよね?
    MVVM についての理解が足りないのかもしれませんが
    あってますよね?


    CheckBox が チェックされている状態かどうかが,
    Model に存在しないならば,
    その ViewModel 上でメンバデータを作成して,
    それにCheckBoxはバインドしておいて,
    TextBoxが見える見えないも,
    (上記のようなコンバータを介して)やはりそのデータにバインドするのが
    いいんじゃないですかね。

    稍丼 / yayadon
    2009年8月13日 5:00
  • 私も今回の場合は、ViewModelに意味のあるフラグ(・・・モード みたいな)を作って、そちらとバインドするのがいいと思います。

    ただ、 View と ViewModel で、Viewで処理すべきもの、すべきでないものの切り分けは、私も勉強中で、明確に理解できてないです。

    上のチェックボックスのコードは、今まさに私が作っていたアプリで同じコトをやろうとしていたもので、最初はViewModelに bool の依存関係プロパティを配置しようと思ったんですが、結局View内だけで完結するように書きました。
    単純にUIの処理だけだったら、View内に置いちゃってもいいのかなと。

    Viewには一切ビハインドコードを置くべきでない、ビハインドコードが必要な処理は、ViewModelに書くべき、という切り分けをしたとしても、XAML自体も処理を含むコードみたいなものなので、例えば標準のビヘイビアをXAMLだけでViewのインスタンスに記載した場合は、もっと複雑なことをViewだけでやってるよなぁとか、色々考えてしまいます。

    今後XAMLの機能が拡張されていけば、(やろうと思えば)、ますますViewだけで完結してしまう処理が増えていくと思うんですが、再利用性や、複数人でのプロジェクトの運用性といった観点で、どうするのが理想的なのか経験を積んで理解しなければと思っています。
    2009年8月13日 5:25

  • > 画面に対する制御、例えば、CheckboxにCheckが入ったときに、
    > 他のTextBoxをDisableにするなどの処理を行う場合、
    > behaviorを定義して、制御することは調べてわかりました。

    これも認識不足で断定的には書けないんですが,
    添付プロパティ を利用した 添付ビヘイビア の使いどころは
    以下の場合だと認識しています。

    コントロールの機能(ビヘイビア)を追加したいときに,
    よくある手は,そのコントロールのクラスを継承して
    新しい機能を追加することです。

    で,
    コントロール・テンプレート 等のテンプレートで
    継承しなくてもコントロールの機能を高めていけるWPFの流儀にそったのが
    添付プロパティ を利用した 添付ビヘイビア というやり方でしょう。

    > CheckboxにCheckが入ったときに、
    > 他のTextBoxをDisableにするなどの処理を行う

    ような時は,使いどころか?というと,
    (私は)ちょっと違うんじゃないかと思います。

    少なくとも,
    画面に対する制御 ---> 添付ビヘイビアを使う
    というような直結した感じで,添付ビヘイビアを使うようになって行ってしまうと
    それは違う方向へ行ってしまうような気がします。
     

    (コードビハインドにコードを書くのを避けて)
    View と ViewModel だけに分ける目的の 一番?の目的は,
    ViewModelだけで,単体テストがしやすくなるってことですよね。

    CheckBox を使って,他を表示/非表示ってことは,

    □ I Agree.

    のようなものだろうから,このチェックしたか?っていう情報は,
    ViewModel 側で必要な筈で,
    ViewModel 側に置き場がないのなら,作らないといけないでしょう。
    (あくまで,例で CheckBox を出したのだとは思いますが)

    逆から見れば,
    単体テストに必要か?ということを考えれば,
    ViewModel に必要かどうかを判断できます。


    基本は,ViewModel で単体テストをしておいて,
    View と ViewModel の間では バインディング で繋ぐのを確認するのみ
    という形にしないと,
    なんとなく面倒なMVVMにした意味がなくなります。
    ということは,
    Viewだけで完結してしまっては元も子もなくなる可能性もあるので,
    気をつけないといけない気がします。

    稍丼 / yayadon
    2009年8月13日 14:11
  • >View と ViewModel だけに分ける目的の 一番?の目的は,
    >ViewModelだけで,単体テストがしやすくなるってことですよね。

    なるほど。「単体テスト」は、自分のなかでViewModelへ持って行くかどうかの判断材料として、すごくヒントになるキーワードでした。
    逆に、ViewModel側では単体テストのしようががないプロパティ(他の要素と一切関わり合いを持たないプロパティ)はViewに閉じこめてもよいのではないかと。


    今回質問された、チェックボックス + テキストボックス っていうのは、よく考えると、エキスパンダーそのものですね。
    「エキスパンダーを使ってはどうでしょう?」って解答にしてもいいくらい。

    エキスパンダーの IsExpanded プロパティは、コントロール内部、つまりはView側だけがもっていて、必要であればViewModelに新たに設置したプロパティとバインドさせます。
    例えば、エキスパンダーが開いているときはステータスバーの状態を変えるとか。

    同様に、「チェックボックス付きテキストボックス」というユーザーコントロールを作った場合、そのコントロールとしては チェックボックスの IsChecked と TextBox の Visibility を直接データバインドさせておき、さらに他のコントロールと連動させる必要があるような場合は、ViewModelのプロパティと IsChecked をバインドさせる、という形がベストなのかなと思いました。

    もちろん、はじめから他のコントロールと連動することがわかりきっていて、いちいちユーザーコントロールにしないような場合は、最初からViewModelのプロパティを介する方法を選択すると思います。

    2009年8月13日 16:16
  • ご回答ありがとうございました。

    MVVM の View と ViewModelの切り分けが難しいですね。
    MVVMは、JavaでWebの開発をしているときのMVCに考え方は
    近い感じはしました。

    今回、私が感じたことは、Web開発に当てはめると、

    View(xaml) : JavaScrit + HTML =
    ViewModel(model view class) : Control(servlet class)
    Model: Model(Dto)

    になるのかと思いました。
    今後、MVVMを勉強していって、どういう使い方がよいか、経験をつみたいと思います。

    誠にありがとうございました。
    2009年8月14日 4:57