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>
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.
There are five types of triggers supported by WPF; they are: Property Trigger. Data Trigger. MultiTrigger.
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.
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)
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With