none
Blend SDKのDataTriggerによる複合条件指定 RRS feed

  • 質問

  • WPFアプリケーションにて、バインド元データの複数のプロパティの状態を元にコントロールの外観が変化するような動きをMultiDataTriggerで実現していたのですが、

    • ConditionのValueの値も元データのプロパティを参照する必要がでてきた
    • BindingとValueの値の比較にEqual以外の比較方法も必要となってきた

    以上のように要求が変わってきたことから、上記の条件を満たすBlend SDKのDataTriggerの使用を検討しています。 

    しかし、Blend SDKのDataTriggerではMultiDataTriggerのように複数条件を指定するような方法が無いようなので、今回の要件をどうクリアするかで悩んでいます。

    イメージとしては、こんな感じで使えればと思っていたのですが…

    <ei:MultiDataTrigger>
        <ei:MultiDataTrigger.Conditions>
            <ei:Condition Binding="{Binding State}" 
                         Value="{Binding State2}"
                         Comparison="NotEqual"/>
            <ei:Condition Binding="{Binding State3}" 
                         Value="{Binding State4}"
                         Comparison="LessThan"/>
        </ei:MultiDataTrigger.Conditions>
        <ei:ChangePropertyAction PropertyName="Fill" Value="Red"/>
    </ei:MultiDataTrigger>
    


    Blend SDKのDataTriggerでも上記のような動作を実現する方法がある、
    あるいは別の方法で実現可能である、
    など、もし何かご存じの方がいらっしゃればぜひご教示願います。

    ちなみに環境としては
    .Net Framework 4
    WPF4
    Blend 4 SDK 
    になります。 

    2011年10月21日 6:24

回答

    • BindingとValueの値の比較にEqual以外の比較方法も必要となってきた

    これについては以下の方法があります。

    A Comparable DataTrigger
    http://blogs.msdn.com/mikehillberg/archive/2008/09/30/ComparableDataTrigger.aspx

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク furoyama 2011年10月21日 12:22
    2011年10月21日 9:16
    モデレータ
  • WPFだと、ConditionのBindingにMultiBindingを使えばいくらでも条件を組み合わせられますよね。
    条件の判定はコンバーターに任せます。

    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:app="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <StackPanel.Resources>
                <app:TextCompareCheckConverter x:Key="txtCmp" />
                <app:IntRangeCompareCheckConverter  x:Key="intCmp" />
            </StackPanel.Resources>
            
            <StackPanel.DataContext>
                <app:TestData />
            </StackPanel.DataContext>
            
            <TextBox x:Name="txb1" />
            <TextBox x:Name="txb2" />
            <TextBox x:Name="txb3" Text="{Binding Path=Value1}"/>
            <TextBox x:Name="txb4" Text="{Binding Path=Value2}"/>
            
            <Rectangle  Width="50" Height="50" RadiusX="10" RadiusY="10"  
                        Stroke="Red" StrokeThickness="3"  >
                <Rectangle.Style>
                    <Style TargetType="{x:Type Rectangle}">
                        <Style.Triggers>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    
                                    <Condition Value="true" >
                                        <Condition.Binding>
                                            <MultiBinding Converter="{StaticResource txtCmp}">
                                                <Binding Path="Text" ElementName="txb1"  />
                                                <Binding Path="Text" ElementName="txb2"  />
                                            </MultiBinding>
                                        </Condition.Binding>
                                    </Condition>
                                    
                                    <Condition Value="true" >
                                        <Condition.Binding>
                                            <MultiBinding Converter="{StaticResource intCmp}"
                                                          ConverterParameter="&lt;=">
                                                <Binding Path="Value1" />
                                                <Binding Path="Value2" />
                                            </MultiBinding>
                                        </Condition.Binding>
                                    </Condition>
                                    
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter Property="Fill" Value="Green" /> 
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>
                        </Style.Triggers>
                    </Style>
                </Rectangle.Style>
            </Rectangle>
        </StackPanel>
    </Window>
    
    

        class CheckConveter<T1, T2, P1> : IMultiValueConverter
        {
            public Func<T1, T2, P1, bool> Checker { get; set; }
    
            #region IMultiValueConverter メンバ
    
            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (values == null || values.Length < 2 || Checker == null)
                {
                    return System.Windows.DependencyProperty.UnsetValue;
                }
    
                if (values[1] is T1 && values[0] is T2)
                {
                    return Checker((T1)values[1], (T2)values[0], (P1)parameter);
                }
                else
                {
                    return Checker((T1)values[0], (T2)values[1], (P1)parameter);
                }
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    
            #endregion
        }
    
        class TextCompareCheckConverter : CheckConveter<string, string, object>
        {
            public TextCompareCheckConverter()
            {
                this.Checker = check;
            }
    
            private static bool check(string v1, string v2, object parameter)
            {
                return v1 == v2;
            }
        }
    
        class IntRangeCompareCheckConverter : CheckConveter<int, int, object>
        {
            public IntRangeCompareCheckConverter()
            {
                this.Checker = check;
            }
    
            private static bool check(int v1, int v2, object parameter)
            {
                string comp = parameter.ToString().Trim();
                if (comp == "=" || comp == "==")
                {
                    return v1 == v2;
                }
                else if (comp == "<>" || comp == "!=")
                {
                    return v1 != v2;
                }
                else if (comp == "<")
                {
                    return v1 < v2;
                }
                else if (comp == ">")
                {
                    return v1 > v2;
                }
                else if (comp == "<=")
                {
                    return v1 <= v2;
                }
                else if (comp == ">=")
                {
                    return v1 >= v2;
                }
                else
                {
                    throw new ApplicationException("パラメータ間違い");
                }
            }
        }
    
        class TestData : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }
            }
    
            public int Value1
            {
                get { return _Value1; }
                set { _Value1 = value; OnPropertyChanged("Value1"); }
            }
            private int _Value1;
    
            public int Value2
            {
                get{return _Value2;}
                set{_Value2 = value; OnPropertyChanged("Value2");}
            }
            private int _Value2;
        }
    

    すべてXAMLでまかなうのが条件ならダメですが
    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
    • 回答としてマーク furoyama 2011年10月21日 12:20
    2011年10月21日 10:00

すべての返信

    • BindingとValueの値の比較にEqual以外の比較方法も必要となってきた

    これについては以下の方法があります。

    A Comparable DataTrigger
    http://blogs.msdn.com/mikehillberg/archive/2008/09/30/ComparableDataTrigger.aspx

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク furoyama 2011年10月21日 12:22
    2011年10月21日 9:16
    モデレータ
  • 判断ロジックが複雑になってきたのでしたら、コードに複数のプロパティを比較して結果を返すプロパティを定義して、それをXAMLから参照する方がいいと思うのですがどうでしょうか?

    あまりXAMLに複雑な処理を定義しておくのは、可読性とメンテナンス性的にもよろしくないようなにおいがします。


    かずき Blog:http://d.hatena.ne.jp/okazuki/ VS 2010のデザイナでBlendのBehaviorをサポートするツール公開してます。 http://vsbehaviorsupport.codeplex.com/
    2011年10月21日 9:50
  • WPFだと、ConditionのBindingにMultiBindingを使えばいくらでも条件を組み合わせられますよね。
    条件の判定はコンバーターに任せます。

    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:app="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <StackPanel.Resources>
                <app:TextCompareCheckConverter x:Key="txtCmp" />
                <app:IntRangeCompareCheckConverter  x:Key="intCmp" />
            </StackPanel.Resources>
            
            <StackPanel.DataContext>
                <app:TestData />
            </StackPanel.DataContext>
            
            <TextBox x:Name="txb1" />
            <TextBox x:Name="txb2" />
            <TextBox x:Name="txb3" Text="{Binding Path=Value1}"/>
            <TextBox x:Name="txb4" Text="{Binding Path=Value2}"/>
            
            <Rectangle  Width="50" Height="50" RadiusX="10" RadiusY="10"  
                        Stroke="Red" StrokeThickness="3"  >
                <Rectangle.Style>
                    <Style TargetType="{x:Type Rectangle}">
                        <Style.Triggers>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    
                                    <Condition Value="true" >
                                        <Condition.Binding>
                                            <MultiBinding Converter="{StaticResource txtCmp}">
                                                <Binding Path="Text" ElementName="txb1"  />
                                                <Binding Path="Text" ElementName="txb2"  />
                                            </MultiBinding>
                                        </Condition.Binding>
                                    </Condition>
                                    
                                    <Condition Value="true" >
                                        <Condition.Binding>
                                            <MultiBinding Converter="{StaticResource intCmp}"
                                                          ConverterParameter="&lt;=">
                                                <Binding Path="Value1" />
                                                <Binding Path="Value2" />
                                            </MultiBinding>
                                        </Condition.Binding>
                                    </Condition>
                                    
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter Property="Fill" Value="Green" /> 
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>
                        </Style.Triggers>
                    </Style>
                </Rectangle.Style>
            </Rectangle>
        </StackPanel>
    </Window>
    
    

        class CheckConveter<T1, T2, P1> : IMultiValueConverter
        {
            public Func<T1, T2, P1, bool> Checker { get; set; }
    
            #region IMultiValueConverter メンバ
    
            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (values == null || values.Length < 2 || Checker == null)
                {
                    return System.Windows.DependencyProperty.UnsetValue;
                }
    
                if (values[1] is T1 && values[0] is T2)
                {
                    return Checker((T1)values[1], (T2)values[0], (P1)parameter);
                }
                else
                {
                    return Checker((T1)values[0], (T2)values[1], (P1)parameter);
                }
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    
            #endregion
        }
    
        class TextCompareCheckConverter : CheckConveter<string, string, object>
        {
            public TextCompareCheckConverter()
            {
                this.Checker = check;
            }
    
            private static bool check(string v1, string v2, object parameter)
            {
                return v1 == v2;
            }
        }
    
        class IntRangeCompareCheckConverter : CheckConveter<int, int, object>
        {
            public IntRangeCompareCheckConverter()
            {
                this.Checker = check;
            }
    
            private static bool check(int v1, int v2, object parameter)
            {
                string comp = parameter.ToString().Trim();
                if (comp == "=" || comp == "==")
                {
                    return v1 == v2;
                }
                else if (comp == "<>" || comp == "!=")
                {
                    return v1 != v2;
                }
                else if (comp == "<")
                {
                    return v1 < v2;
                }
                else if (comp == ">")
                {
                    return v1 > v2;
                }
                else if (comp == "<=")
                {
                    return v1 <= v2;
                }
                else if (comp == ">=")
                {
                    return v1 >= v2;
                }
                else
                {
                    throw new ApplicationException("パラメータ間違い");
                }
            }
        }
    
        class TestData : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }
            }
    
            public int Value1
            {
                get { return _Value1; }
                set { _Value1 = value; OnPropertyChanged("Value1"); }
            }
            private int _Value1;
    
            public int Value2
            {
                get{return _Value2;}
                set{_Value2 = value; OnPropertyChanged("Value2");}
            }
            private int _Value2;
        }
    

    すべてXAMLでまかなうのが条件ならダメですが
    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
    • 回答としてマーク furoyama 2011年10月21日 12:20
    2011年10月21日 10:00
  • ご回答ありがとうございます。

    リンク先の手法、面白いですね。
    MultiDataTriggerのConditionに使えば、目的の動作は実現できそうな気がします。

    参考にさせていただきました! 
    ありがとうございました!
    • 編集済み furoyama 2011年10月21日 12:22
    2011年10月21日 11:43
  • 回答ありがとうございます。

    すみません、私の質問の記述が不十分でした。
    多分おっしゃるとおりで、本来的にはコードで解決したほうがいい領域なんだと思います。
    ただ、今回の要件では処理の多くをXAMLで解決する必要があり、
    質問で挙げさせていただいた2点はXAML内で記述できないといけないんです。
    (trapemiyaさんやgekkaさんからご教示いただいたような、コンバーターを用意するようなアプローチであれば大丈夫です)

    ともあれ、かずきさんのお話もその通りだと思うので、可読性・メンテナンス性の低下には十分留意したいと思います。

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


    #かずきさんに回答していただけたことにすごく感激してます!
    #勝手ながら、私はかずきさんのことを師匠だと思っています。
    #且つ、ファンです:o)

    #WPFの習得を始めた時からかずきさんのブログには何回も救われてきました。
    #多分かずきさんのブログが無かったら今日までWPFを続けて来れなかっただろうし、
    #私が仕事としてWPFのプロジェクトに携われるだけの知識を得られたのもかずきさんのブログのお陰です。
    #本当にありがとうございます!
    #これからも色々勉強させていただきます! 
    2011年10月21日 12:14
  • 回答ありがとうございます。

    gekkaさんがご教示してくださった内容で、期待している動作が実現できました!
    本当にありがとうございます!
    教えていただいたアプローチで検討を進めたいと思います。

    MultiBindingって、一つの入力欄から複数の値にBindするような用途で使う程度のイメージしかなくて、こんなアプローチがあるとは思いもしませんでした。
    新しい知見が得られて凄く嬉しいです。
    このやり口だとコンバーターの作り次第で本当に何でもできますね!

    ありがとうございました! 
    2011年10月21日 12:19