none
コレクションのバインディングについて RRS feed

  • 質問

  • お世話になります。

    バインディングについて、質問があります。

    よくあるキー値とバリュー値を持つDictionary型のコレクションをデータとして準備します。

    // リストデータ
    private readonly Dictionary<string, string> _myDataList = new Dictionary<string, string>()
    {
       {"1","data1"},
       {"2","data2"}
    };

    TextBoxとTextBlockがある画面で、TextBoxにキー値を入力したら、そのキー値に対応したバリュー値をTextBlockに表示する機能を考えています。
    以下のようにView側とViewModel側を定義すればできるのですが、もっとスマートな方法がないかなと思い質問させていただきました。

    MainWindow.xaml

            <StackPanel>
                <TextBox Name="MyCode" Text="{Binding MyCode}"></TextBox>
                <TextBlock Name="MyName" Text="{Binding MyName}"></TextBlock>
                <TextBlock Name="MyName2" Text="{Binding MyName}"></TextBlock>
                <TextBox ></TextBox>
            </StackPanel>

    MainWindowViewModel.cs

            // コード
            private string _mycode=string.Empty;
            public string MyCode
            {
                get
                {
                    return _mycode;
                }
                set
                {
                    if (_mycode.Equals(value)) return;
                    _mycode = value;
                    RaisePropertyChanged(() => MyCode);
                    RaisePropertyChanged(() => MyName);
                }
            }
            // 名前
            public string MyName
            {
                get
                {
                    if (string.IsNullOrEmpty(MyCode))
                        return string.Empty;
                    else
                    {
                        if (MyData.ContainsKey(MyCode))
                            return _myDataList[MyCode];
                        else
                            return string.Empty;
                    }
                        
                }
            }

    出来れば、MyNameというプロパティは用意しないで、コレクション型を直接公開し、TextBoxに入力されたキー値を利用してバインドできればベストです。

    コンバーターを作成すれば、一番良いのかもしれませんがXAMLだけで実現可能なのかを確認したい次第です。

    よろしくお願いいたします。

    2013年7月12日 7:35

回答

  • #ところで、TextBoxの値が変わったら即座にDataが表示されるようにしたつもりなんですが、ダミーのテキストボックスをクリックしないと表示されないのは何でだろうな…

    Text プロパティの場合、UpdateSourceTrigger の規定値は LostFocus だからです。TextBox の値を変更して直ちにバインディングを反映させたい場合は、以下のように UpdateSourceTrigger に PropertyChanged を指定します。詳しくは Binding.UpdateSourceTrigger のドキュメントをご覧ください。

    <TextBox Name="MyCode" Text="{Binding MyCode, UpdateSourceTrigger=PropertyChanged  }"/>


    ひらぽん http://d.hatena.ne.jp/hilapon/


    2013年7月12日 10:40
    モデレータ
  • 他にいい方法があるかもしれないですが、自分の場合は、マルチバインディングとコンバーターを使います。

    XAMLは、こんな感じで書きます

    <Window x:Class="test11.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
            xmlns:v="clr-namespace:test11.Views"
            xmlns:converter="clr-namespace:test11.Converter"
            xmlns:vm="clr-namespace:test11.ViewModels"
            Title="MainWindow" Height="350" Width="525">
        
        <Window.DataContext>
            <vm:MainWindowViewModel/>
        </Window.DataContext>
    
        <Window.Resources>
            <converter:NameToDataConverter x:Key="NameToDataConverter" />
        </Window.Resources>
    
    
        <Grid>
            <StackPanel>
                <TextBox Name="MyCode" Text="{Binding MyCode}"></TextBox>
                <TextBlock Name="MyName" Background="#FFE4E4E4">
                    <TextBlock.Text>
                        <MultiBinding Converter="{StaticResource NameToDataConverter}" UpdateSourceTrigger="PropertyChanged">
                            <Binding Path="MyCode"/>
                            <Binding Path="MyData" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
                <TextBox></TextBox>
            </StackPanel>
        </Grid>
    </Window>

    コンバーターのコードはこんな感じです。ValueConverter.cs みたいなファイルを作って追加して下さい。

    namespace test11.Converter
    {
    	/// <summary>
    	/// A Value converter
    	/// </summary>
    	public class ValueConverter : IValueConverter
    	{
    		#region IValueConverter Members
    
    		/// <summary>
    		/// Modifies the source data before passing it to the target for display in the UI. 
    		/// </summary>
    		/// <param name="value">The source data being passed to the target </param>
    		/// <param name="targetType">The Type of data expected by the target dependency property.</param>
    		/// <param name="parameter">An optional parameter to be used in the converter logic.</param>
    		/// <param name="culture">The culture of the conversion.</param>
    		/// <returns>The value to be passed to the target dependency property. </returns>
    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException();
    		}
    
    		/// <summary>
    		/// Modifies the target data before passing it to the source object. This method is called only in TwoWay bindings. 
    		/// </summary>
    		/// <param name="value">The target data being passed to the source.</param>
    		/// <param name="targetType">The Type of data expected by the source object.</param>
    		/// <param name="parameter">An optional parameter to be used in the converter logic. </param>
    		/// <param name="culture">The culture of the conversion.</param>
    		/// <returns>The value to be passed to the source object.</returns>
    		public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException();
    		}
    
    		#endregion
    	}
    
    	public class NameToDataConverter : IMultiValueConverter
    	{
    		public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (values.Length == 2)
    			{
    
    				string key = values[0] as string;
    				Dictionary<string, string> dic = values[1] as Dictionary<string, string>;
    				if (String.IsNullOrEmpty(key) ==false && dic != null )
    				{
    					try
    					{
    						return dic[key];
    					}
    					catch (KeyNotFoundException ex)
    					{
    						return ex.Message;
    					}
    				}
    			}
    			return String.Empty;
    		}
    		public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException("Not Supported");
    		}
    	}
    }

    こちらでやってみたところ、うまく動きました。

    Keyがない文字列を入れると、Exceptionのメッセージが表示されます。

    #ところで、TextBoxの値が変わったら即座にDataが表示されるようにしたつもりなんですが、ダミーのテキストボックスをクリックしないと表示されないのは何でだろうな…

    • 回答としてマーク r1user 2013年7月15日 23:21
    2013年7月12日 9:45

すべての返信

  • テストプロジェクトは以下に置きました。

    https://dl.dropboxusercontent.com/u/14501064/test11.zip

    MVVMインフラはLivetを利用させていただいております。

    2013年7月12日 7:43
  • 他にいい方法があるかもしれないですが、自分の場合は、マルチバインディングとコンバーターを使います。

    XAMLは、こんな感じで書きます

    <Window x:Class="test11.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
            xmlns:v="clr-namespace:test11.Views"
            xmlns:converter="clr-namespace:test11.Converter"
            xmlns:vm="clr-namespace:test11.ViewModels"
            Title="MainWindow" Height="350" Width="525">
        
        <Window.DataContext>
            <vm:MainWindowViewModel/>
        </Window.DataContext>
    
        <Window.Resources>
            <converter:NameToDataConverter x:Key="NameToDataConverter" />
        </Window.Resources>
    
    
        <Grid>
            <StackPanel>
                <TextBox Name="MyCode" Text="{Binding MyCode}"></TextBox>
                <TextBlock Name="MyName" Background="#FFE4E4E4">
                    <TextBlock.Text>
                        <MultiBinding Converter="{StaticResource NameToDataConverter}" UpdateSourceTrigger="PropertyChanged">
                            <Binding Path="MyCode"/>
                            <Binding Path="MyData" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
                <TextBox></TextBox>
            </StackPanel>
        </Grid>
    </Window>

    コンバーターのコードはこんな感じです。ValueConverter.cs みたいなファイルを作って追加して下さい。

    namespace test11.Converter
    {
    	/// <summary>
    	/// A Value converter
    	/// </summary>
    	public class ValueConverter : IValueConverter
    	{
    		#region IValueConverter Members
    
    		/// <summary>
    		/// Modifies the source data before passing it to the target for display in the UI. 
    		/// </summary>
    		/// <param name="value">The source data being passed to the target </param>
    		/// <param name="targetType">The Type of data expected by the target dependency property.</param>
    		/// <param name="parameter">An optional parameter to be used in the converter logic.</param>
    		/// <param name="culture">The culture of the conversion.</param>
    		/// <returns>The value to be passed to the target dependency property. </returns>
    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException();
    		}
    
    		/// <summary>
    		/// Modifies the target data before passing it to the source object. This method is called only in TwoWay bindings. 
    		/// </summary>
    		/// <param name="value">The target data being passed to the source.</param>
    		/// <param name="targetType">The Type of data expected by the source object.</param>
    		/// <param name="parameter">An optional parameter to be used in the converter logic. </param>
    		/// <param name="culture">The culture of the conversion.</param>
    		/// <returns>The value to be passed to the source object.</returns>
    		public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException();
    		}
    
    		#endregion
    	}
    
    	public class NameToDataConverter : IMultiValueConverter
    	{
    		public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			if (values.Length == 2)
    			{
    
    				string key = values[0] as string;
    				Dictionary<string, string> dic = values[1] as Dictionary<string, string>;
    				if (String.IsNullOrEmpty(key) ==false && dic != null )
    				{
    					try
    					{
    						return dic[key];
    					}
    					catch (KeyNotFoundException ex)
    					{
    						return ex.Message;
    					}
    				}
    			}
    			return String.Empty;
    		}
    		public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    		{
    			throw new NotImplementedException("Not Supported");
    		}
    	}
    }

    こちらでやってみたところ、うまく動きました。

    Keyがない文字列を入れると、Exceptionのメッセージが表示されます。

    #ところで、TextBoxの値が変わったら即座にDataが表示されるようにしたつもりなんですが、ダミーのテキストボックスをクリックしないと表示されないのは何でだろうな…

    • 回答としてマーク r1user 2013年7月15日 23:21
    2013年7月12日 9:45
  • #ところで、TextBoxの値が変わったら即座にDataが表示されるようにしたつもりなんですが、ダミーのテキストボックスをクリックしないと表示されないのは何でだろうな…

    Text プロパティの場合、UpdateSourceTrigger の規定値は LostFocus だからです。TextBox の値を変更して直ちにバインディングを反映させたい場合は、以下のように UpdateSourceTrigger に PropertyChanged を指定します。詳しくは Binding.UpdateSourceTrigger のドキュメントをご覧ください。

    <TextBox Name="MyCode" Text="{Binding MyCode, UpdateSourceTrigger=PropertyChanged  }"/>


    ひらぽん http://d.hatena.ne.jp/hilapon/


    2013年7月12日 10:40
    モデレータ
  • ひらぽんさん、補足ありがとうございます。

    たしかに、そっちのBindingのUpdateSourceTriggerを忘れてましたね…

    2013年7月12日 10:50
  • NIM5さん、回答ありがとうございます。

    やはりコンバーターですね。
    コンバーターを採用する事にします

    ありがとうございました。

    2013年7月15日 23:27