例えば「こっちのチェックボックスの状態がチェック済みで、なおかつこちらに文字が入力されている場合に背景色を変える」といったように、WPF(XAML)で複数のトリガー条件を指定したくなることは、タマにあると思います。
さらに稀なことですが、この記事を開いたような方は、複数のトリガー条件を OR で判断したくなることもあるのではないかと思います。
そんな時、すぐに思いつくのは MultiDataTrigger を使う方法ですが、OR 条件を指定しようと思っても単純にはできないようなので、別の方法も含めてなんとかしてみようという趣旨の記事になります。
実際のところ、逆方向から考えれば、わざわざ OR 条件で判断するまでもないケースの方が多かったりもするのですが、どうしても OR で判断しなくてはならないケースと遭遇した時に、お役に立ちましたら。
読み込み中です。少々お待ち下さい
まずは AND から
まずは、より単純に実装できる AND のケースから見ていきましょう。
このような動作を実現したい場合は、MultiDataTrigger を使うと簡単でしょう。
以下のコードは、Visual Studio (2015) の「ファイル」メニュー>「新規作成」>「WPF アプリケーション」から「WpfMultiBindingSample」という名前で新しいプロジェクトを作成し、自動的に生成される MainWindow に上書きすれば、そのまま動作します。
今回は「MainWindow.xaml.cs」の方は一切いじっていないので、「MainWindow.xaml」だけ貼り付けておきます。
MainWindow.xaml
<Window x:Class="WpfMultiBindingSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfMultiBindingSample"
mc:Ignorable="d"
Title="WpfMultiBindingSample" Height="320" Width="480">
<Window.Resources>
<Style x:Key="MultiBindingSampleStyle" TargetType="Label">
<Setter Property="Background" Value="#00BCD4" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=HoverCheckbox, Path=IsChecked}" Value="true" />
<Condition Binding="{Binding ElementName=HoverLabel, Path=IsMouseOver}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#FF5722" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<CheckBox x:Name="HoverCheckbox" Content="ここがチェックされていている時に" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">
<CheckBox.LayoutTransform>
<ScaleTransform ScaleX="2" ScaleY="2" />
</CheckBox.LayoutTransform>
</CheckBox>
<Label x:Name="HoverLabel" Content="ここのマウスオーバーで" HorizontalAlignment="Left" Margin="10,49,10,0" VerticalAlignment="Top" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="60" Width="452" FontSize="28" Background="#FF00BCD4" Foreground="White"/>
<Label x:Name="TargetLabel" Content="ここの背景色が変わります" Style="{StaticResource MultiBindingSampleStyle}" HorizontalAlignment="Left" Margin="10,151,10,10" VerticalAlignment="Top" Height="128" Width="452" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="28" />
</Grid>
</Window>
このように、MultiDataTrigger を使えば、AND 条件を複数指定することが可能です。
OR 条件の場合
さて、それでは、以下のような場合は、どうすれば良いでしょうか。
The MultiTrigger applies the associated setters or actions when all of the conditions are true (binary AND operation).
参照先で説明されているように、MultiTrigger は条件が全て真(true)の時に適用されるので、上と同じようなコードで単純に実現しようとすると、組み合わせを全て書かなくてはならず現実的ではありません。
このような場合は、別途 IMultiValueConverter の実装が必要になってはしまいますが、どうやら Multi(Data)Trigger ではなく MultiBinding を利用した方が良いと思うのですが、いかがでしょうか(AND で済む場合は、MultiDataTrigger を使った方が Converter を書く必要が無いので楽だと思います)。
以下のコードは、Visual Studio (2015) の「ファイル」メニュー>「新規作成」>「WPF アプリケーション」から「WpfAnyConverterSample」という名前で新しいプロジェクトを作成し、「AnyMultiValueConverter.cs」を新規作成して中身を貼り付け&自動的に生成される MainWindow に上書きすれば、そのまま動作します。
例によって「MainWindow.xaml.cs」の方は一切いじっていません。
AnyMultiValueConverter.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace WpfAnyConverterSample
{
// 今回は bool しか扱っていないので以下のように書いていますが、実際は実情に合わせて実装してください。
class AnyMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<bool>().Contains(true);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
MainWindow.xaml
<Window x:Class="WpfAnyConverterSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAnyConverterSample"
mc:Ignorable="d"
Title="WpfAnyConverterSample" Height="320" Width="480">
<Window.Resources>
<local:AnyMultiValueConverter x:Key="AnyConverter" />
<Style x:Key="MultiHoverLabelStyle" TargetType="Label">
<Setter Property="Background" Value="#00BCD4" />
<Style.Triggers>
<DataTrigger Value="true">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource AnyConverter}">
<Binding ElementName="HoverLabel1" Path="IsMouseOver" />
<Binding ElementName="HoverLabel2" Path="IsMouseOver" />
<Binding ElementName="HoverLabel3" Path="IsMouseOver" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="#FF5722" />
</DataTrigger>
<!-- 今回は true / false なので意味はありませんが、Converter の返す値が様々な場合は、こんな風に切り分けて書くことも可能です。
<DataTrigger Value="false">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource AnyConverter}">
<Binding ElementName="HoverLabel1" Path="IsMouseOver" />
<Binding ElementName="HoverLabel2" Path="IsMouseOver" />
<Binding ElementName="HoverLabel3" Path="IsMouseOver" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="#00BCD4" />
</DataTrigger>
-->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Label x:Name="HoverLabel1" Content="ここか" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="60" Width="220" Background="#FF00BCD4" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" />
<Label x:Name="HoverLabel2" Content="ここか" HorizontalAlignment="Left" Margin="242,10,10,0" VerticalAlignment="Top" Height="60" Width="220" Background="#FF00BCD4" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" />
<Label x:Name="HoverLabel3" Content="ここのマウスオーバーで" HorizontalAlignment="Left" Margin="10,78,10,0" VerticalAlignment="Top" Height="60" Width="452" Background="#FF00BCD4" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" />
<Label x:Name="TargetLabel" Content="ここの背景色が変わります" Style="{StaticResource MultiHoverLabelStyle}" HorizontalAlignment="Left" Margin="10,179,10,10" VerticalAlignment="Top" Height="100" Width="452" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" />
</Grid>
</Window>
まぁ、Any と言いつつ実際は Contains を使っているあたりが、我ながらどうかと思う訳ですが。
ともあれ、以上でいちおう実現できますが、例示したのはあくまでどのように実装するかのサンプルであり、アプリケーションの仕様(挙動)的な意味での現実味はありませんので、ここだけ見ると「こんなのいる?」となってしまいがちかも知れません。
また、冒頭でも触れたように、実際はわざわざ OR 条件にしなくても、発想を転換すれば済む場合も多いかと思います。
なので、何をどう考えても OR じゃなければダメだ、という場合のみ、こんな感じで実装することを考慮した方が良いんじゃないかと思います。
また、AND の例として挙げた MultiDataTrigger と組み合わせることで、当然ながら AND と OR の複合条件も実現できますが(例えば MultiDataTrigger の Conditions のひとつとして、AnyMultiValueConverter を登録する)、複雑になり過ぎるのでオススメしません。仕様を見直すか、専用の Converter を書いた方が無難だと思います。
おわりに
他にもっと良い書き方がありましたら、申し訳ないです。
ていうか、MultiDataTrigger.Conditions で AND や OR を Type とかで指定できるようにしてくれたらいいのに......(ボソッ)