Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MultiDataTrigger with OR instead of AND

I am trying to set multiple DataTriggers on my Button. I did some research and found that MultiDataTrigger allows you to do this. I want the Visibility property of my Button to be set to false if the CCTVPath == string.Empty OR PermissionsFlag == false. This is what I have so far;

<Button Grid.Column="3" x:Name="cctvFeedButton" Content="Live Feed"         Width="100" FontSize="16" HorizontalAlignment="Right" Margin="5" Click="OnCCTVButtonClick">     <Button.Style>         <Style TargetType="Button">             <Style.Triggers>                 <MultiDataTrigger>                     <MultiDataTrigger.Conditions>                         <Condition Binding="{Binding CCTVPath}" Value=""/>                         <Condition Binding="{Binding PermissionsFlag}" Value="False"/>                     </MultiDataTrigger.Conditions>                     <Setter Property="Visibility" Value="Hidden"/>                 </MultiDataTrigger>             </Style.Triggers>         </Style>     </Button.Style> </Button> 

And in my code-behind I set PermissionsFlag like so;

public bool PermissionsFlag { get; set; }  private void OnPageLoaded(object sender, RoutedEventArgs e) {     PermissionsFlag = false; } 

As you can see PermissionsFlag is definitely false, and the there are definitely empty CCTVPath however the Button is never hidden. What am I doing wrong?

UPDATE:

    public event PropertyChangedEventHandler PropertyChanged;      protected virtual void OnPropertyChanged(string propertyName)     {         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));     }      private bool _permissionsFlag;     public bool Flag     {         get { return _permissionsFlag; }         set         {             _permissionsFlag = value;             OnPropertyChanged("PermissionsFlag");         }     }      private void OnPageLoaded(object sender, RoutedEventArgs e)     {         Flag = false;         CCTVImageCollection = GetImages();         imageListBox.ItemsSource = CCTVImageCollection;         DataContext = this;     } 

In my XAML:

<Button.Style>          <Style TargetType="Button">                <Style.Triggers>                       <DataTrigger Binding="{Binding PermissionsFlag}" Value="False">                             <Setter Property="Visibility" Value="Hidden"/>                       </DataTrigger>                </Style.Triggers>          </Style>  </Button.Style> 
like image 503
CBreeze Avatar asked Jul 15 '16 12:07

CBreeze


People also ask

What is multi trigger in WPF?

XAML Values MultiTrigger enables you to set property values or start actions based on a collection of Condition objects. A condition is met when the value of the property (specified by the Property property of the Condition class) of the element matches the specified Value.

How many types of triggers are there in WPF?

There are five types of triggers supported by WPF; they are: Property Trigger. Data Trigger. MultiTrigger.

What is DataTrigger in WPF?

A DataTrigger allows you to set property values when the property value of the data object matches a specified Value. For example, if you are displaying a list of Employee objects, you may want the foreground color to be different based on each Employee's current attendance.


2 Answers

tranform Conditions into two independent DataTriggers

<Style.Triggers>     <DataTrigger Binding="{Binding CCTVPath}" Value="">         <Setter Property="Visibility" Value="Hidden"/>     </DataTrigger>     <DataTrigger Binding="{Binding PermissionsFlag}" Value="False">         <Setter Property="Visibility" Value="Hidden"/>     </DataTrigger> </Style.Triggers> 

make sure that binding paths are correct (check VS Output window for possible exception messages)


also: don't rely only on hidden state of Button, implement permissions properly in code (OnCCTVButtonClick). read why here:

How to Snoop proof your wpf application?



auto-property PermissionsFlag (public bool PermissionsFlag { get; set; }) doesn't notify view about changes.

it is possible to implement INotifyPropertyChanged interface (in my test window it is done like this: public partial class Window3 : Window, INotifyPropertyChanged) and then raise event when property changes.

here is a complete working example which I used for test

public partial class Window3 : Window, INotifyPropertyChanged {     public Window3()     {         InitializeComponent();         this.DataContext = this;         //PermissionsFlag = true;         CCTVPath = "youtube.com";     }      private bool _permissionsFlag = false;     private string _cctvPath;      public bool PermissionsFlag     {         get { return _permissionsFlag; }         set         {             _permissionsFlag = value;             OnPropertyChanged("PermissionsFlag");         }     }      public string CCTVPath     {         get { return _cctvPath; }         set         {             _cctvPath = value;             OnPropertyChanged("CCTVPath");         }     }      public event PropertyChangedEventHandler PropertyChanged;      protected virtual void OnPropertyChanged(string propertyName)     {         if (PropertyChanged != null)             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));     } } 

window xaml:

<Window x:Class="WpfDemos.Views.Window3"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         Title="Window3" Height="300" Width="300">     <StackPanel>         <CheckBox Name="chkPermissionsFlag"                    Content="PermissionsFlag"                    IsChecked="{Binding Path=PermissionsFlag, UpdateSourceTrigger=PropertyChanged}"/>          <TextBox Text="{Binding Path=CCTVPath, UpdateSourceTrigger=PropertyChanged}"/>          <Button x:Name="cctvFeedButton" Content="Live Feed"                     Width="100" FontSize="16" HorizontalAlignment="Right" Margin="5">             <Button.Style>                 <Style TargetType="Button">                     <Style.Triggers>                         <DataTrigger Binding="{Binding Path=CCTVPath}" Value="">                             <Setter Property="Visibility" Value="Hidden"/>                         </DataTrigger>                         <DataTrigger Binding="{Binding Path=PermissionsFlag}" Value="False">                             <Setter Property="Visibility" Value="Hidden"/>                         </DataTrigger>                     </Style.Triggers>                 </Style>             </Button.Style>         </Button>     </StackPanel> </Window> 
like image 181
ASh Avatar answered Sep 21 '22 10:09

ASh


An alternative solution is to use a single DataTrigger with a MultiBinding. You could make it work by defining a 'special-case' IMultiValueConverter that assumes 2 items in the object array, and returns true if the first item is an empty string OR the second item is false. However, you probably won't ever use that converter anywhere else in your code. So if you are willing to do a little more work up front, you could define 3 simple/reusable converters.

1) an [IMultiValueConverter] 'OrConverter,' which would look something like this:

public class BooleanOrConverter : IMultiValueConverter {    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {       return values.OfType<bool>().Any(b => b);    }     public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {       throw new NotImplementedException();    } } 

2) An [IValueConverter] 'IsNullOrEmpty' string converter:

public class StringIsNullOrEmptyConverter : IValueConverter {    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {       return string.IsNullOrEmpty(value as string);    }     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {       throw new NotImplementedException();    } } 

3) And an [IValueConverter] 'NotConverter:'

public class BooleanNotConverter : IValueConverter {    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {       return !(bool)value;    }     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {       throw new NotImplementedException();    } } 

Then, in your xaml, the DataTrigger would be defined like this:

<Button x:Name="cctvFeedButton" Content="Live Feed"   Width="100" FontSize="16" HorizontalAlignment="Right" Margin="5">   <Button.Style>     <Style TargetType="Button">       <Style.Triggers>          <DataTrigger Value="True">            <DataTrigger.Binding>              <MultiBinding Converter="{StaticResource OrConverter}">                <Binding Path="PermissionFlag" Converter="{StaticResource NotConverter}"/>                <Binding Path="CCTVPath" Converter="{StaticResource IsNullOrEmptyConverter}"/>              </MultiBinding>            </DataTrigger.Binding>            <Setter Property="Visibility" Value="Hidden"/>         </DataTrigger>       </Style.Triggers>     </Style>   </Button.Style> </Button> 

I prefer this solution over the use of 2 separate DataTriggers for readability; it better expresses the behavior you are defining - it is 'or' logic: a singular set of 2 conditions that should hide the button.

like image 43
John Colvin Avatar answered Sep 19 '22 10:09

John Colvin