CBreeze
CBreeze

Reputation: 2965

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>

Upvotes: 30

Views: 29532

Answers (3)

Parsa
Parsa

Reputation: 11584

I used these triggers to make Add button disabled when textboxes are empty:

OR (If one of textboxes get filled, the button will enable)

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding Text, ElementName=Usernametxtbox}" Value=""/>
        <Condition Binding="{Binding Password, ElementName=passtxtbox}" Value=""/>
    </MultiDataTrigger.Conditions>
    <Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>

AND (If both textboxes get filled, the button will enable)

<DataTrigger Binding="{Binding Text, ElementName=Usernametxtbox}" Value="">
    <Setter Property="IsEnabled" Value="False" />
</DataTrigger>

<DataTrigger Binding="{Binding Password, ElementName=passtxtbox}" Value="">
    <Setter Property="IsEnabled" Value="False" />
</DataTrigger>

Upvotes: 1

John Colvin
John Colvin

Reputation: 334

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.

Upvotes: 9

ASh
ASh

Reputation: 35646

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>

Upvotes: 43

Related Questions