Triggers Based on Properties from DataContext - wpf

Suppose I want to show/hide elements based on Values of Properties from DataContext, how can I acheive it?
// In MainWindow.xaml.cs: DataContext of MainWindow.xaml
public int Mode { get; set; }
In XAML, I want to show hide elements based on the Mode. How can I make the below work? Or what is the appropriate way of implementing this?
<StackPanel>
<StackPanel.Triggers>
<Trigger Property="Mode" Value="1">
<Setter TargetName="txt1" Property="Visibility" Value="Visible" />
<Setter TargetName="txt2" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt3" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Mode" Value="2">
<Setter TargetName="txt1" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt2" Property="Visibility" Value="Visible" />
<Setter TargetName="txt3" Property="Visibility" Value="Collapsed" />
</Trigger>
</StackPanel.Triggers>
<TextBlock Text="TextBlock 1" x:Name="txt1" />
<TextBlock Text="TextBlock 2" x:Name="txt2" />
<TextBlock Text="TextBlock 3" x:Name="txt3" />
</StackPanel>
Currently, the Error I am getting is "Property 'Mode' was not found in type 'StackPanel'. D:\tmp\WpfApplication1\TriggersAndProperties\MainWindow.xaml"

You need to use DataTriggers if you want triggers that can work on bindings. Problem is, DataTriggers are only available on style and template so you need to define one like this:
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Mode}" Value="1">
<Setter TargetName="txt1" Property="Visibility" Value="Visible" />
<Setter TargetName="txt2" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt3" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Mode}" Value="2">
<Setter TargetName="txt1" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt2" Property="Visibility" Value="Visible" />
<Setter TargetName="txt3" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="TextBlock 1" x:Name="txt1" />
<TextBlock Text="TextBlock 2" x:Name="txt2" />
<TextBlock Text="TextBlock 3" x:Name="txt3" />
</StackPanel>
Another solution would be to use an IValueConverter that converts the int from Mode to the Visibility you want, and apply it directly to each text block Visibility property.
Note that Dean Chalk's answer stays valid: you have to use a DependencyProperty or implement INotifyPropertyChanged if you want changes on Mode to trigger.

Your propert 'Mode' needs to be a dependency property in order to be used this way:
public class MainViewModel : DependencyObject
{
readonly DependencyProperty ModeProperty = DependencyProperty
.Register("Mode", typeof(int), typeof(MainViewModel));
public int Mode
{
get { return (int) GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
}

Related

WPF pass ElementName as parameter into a Style

I have 3 TextBlocks whose visibility property depends on a validation rule on 3 different elements (TextBoxes). How can I refactor this code so that the ElementName is abstracted away and I just have one style defined instead of 3? Is it possible to pass in ElementName as a parameter at the TextBlocks which this style is applied to?
<Style x:Key="textBlock_stockPriceWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_taxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_capGainsTaxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
**Update: Per Mark's advice below, I tried using the following ControlTemplate and attached property definition:
<ControlTemplate x:Key="LabelWarning" TargetType="Label">
<Border BorderBrush="AliceBlue"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
public static class ElementNameHelper
{
public static readonly DependencyProperty ElementAliasProperty =
DependencyProperty.RegisterAttached("ElementAlias",
typeof(string), typeof(ElementNameHelper));
public static string GetElementAlias(DependencyObject obj)
{
return obj.GetValue(ElementAliasProperty).ToString();
}
public static void SetElementAlias(DependencyObject target, string value)
{
target.SetValue(ElementAliasProperty, value);
}
}
And invoking it like this:
<Label Content="Stock price should be a positive number."
FontSize="12" HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="Red"
Margin="1"
views:ElementNameHelper.ElementAlias="stockPriceTextBox"
Template="{StaticResource LabelWarning}"/>
But then the XAML compiler tells me that: A 'Binding' cannot be set on the 'ElementName' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject. Do I have this setup correctly and is it just the case that you simply can't use this approach on ElementName? Or am I missing something with the attached property definition?
Usual way to do this is with an attached property.
If you just want a cheap-n-easy solution then you can use Tag, which is described in the documentation as "an arbitrary object value that can be used to store custom information". An attached property is the better way to do it though.

Change Visibility of one Control when it is changed on another one

I got 3 Controls in my XAML.
If the Visibility from one of them changes to Visible, the others Visibility should change to Hidden. So that only one of them can be Visible at a time.
My Xaml
<Control x:Name="Unselected">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selection" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selected" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
Use 3 RadioButton and modify their Template and use their IsChecked property. Only one of them would be checked at a time. Bind your Visibility property to IsChecked property. If IsChecked is true, Visibility = Visible else Visibility = Hidden .
You can use Binding and PropertyChanged. Setting the property will raise PropertyChanged and Triggers will execute.
XAML:
<DockPanel LastChildFill="false">
<TextBox x:Name="Unselected" DockPanel.Dock="Top" Text="Unselected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selection" DockPanel.Dock="Top" Text="Selection">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selected" DockPanel.Dock="Top" Text="Selected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Content="Unselected" DockPanel.Dock="Top" Click="Button_Click"></Button>
<Button Content="Selection" DockPanel.Dock="Top" Click="Button_Click_1"></Button>
<Button Content="Selected" DockPanel.Dock="Top" Click="Button_Click_2"></Button>
</DockPanel>
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Unselected;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selection;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selected;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private Options _enumOptions;
public Options EnumOptions
{
get { return _enumOptions; }
set
{
_enumOptions = value;
OnPropertyChanged("EnumOptions");
}
}
}
public enum Options
{
Unselected,
Selection,
Selected
}
Basically, you will set need to set property like this
EnumOptions = Options.Unselected;
instead of
Unselected.Visibility = true;

Wpf ToggleButton Command Binding Data Trigger

I have a toggle button that has two data triggers that are bound to the buttons IsChecked property. Within the Data triggers I am setting the buttons content and also its command property. The problem that I am having is that when you click the button the data trigger is changing the command value and then firing the command. Is it possible to have the command fire first then the data trigger? Here is the code.
<ToggleButton x:Name="commandToggleButton" Grid.Row="0" Height="30" HorizontalAlignment="Left" Margin="26,24,0,0" VerticalAlignment="Top" Width="75">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=commandToggleButton, Path=IsChecked}" Value="False">
<Setter Property="Content" Value="Build" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Command" Value="{Binding Build}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=commandToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Content" Value="Cancel" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Command" Value="{Binding CancelBuild}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
What I would like to happen is that when the button is clicked the first time for the Build command to be fired, then if it is clicked the second time for the CancelBuild command to be fired.
Keep it simple:
<ToggleButton IsChecked="{Binding EnableBuild}" FontWeight="Bold">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content" Value="Build" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Cancel" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
ViewModel:
public Command Build {get;set;}
public Command Cancel {get;set;}
//...
private bool _enableBuild;
public bool EnableBuild
{
get { return _enableBuild; }
set
{
_enableBuild = value;
NotifyPropertyChange(() => EnableBuild);
if (value)
Build.Execute();
else
Cancel.Execute();
}
}

How to pass the binding path to DataTrigger in a WPF style?

I've several style with only a little difference in binding path:
<Style TargetType="FrameworkElement" x:Key="FieldValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=FieldValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="NumberValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NumberValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="TextValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TextValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="DateTimeValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DateTimeValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="DateValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DateValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="TimeValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TimeValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
We can find the only difference between the styles is the {Binding Path=xyz} in DataTrigger, can I remove the duplication with only XMAL markup? I know we can create custom styles as in this question but the setters are hard coded - can we only extend DataTrigger?
You can use a single style with a MultiBinding DataTrigger and an OR Converter in it.
<Window.Resources>
<local:AtleastOneEmptyConverter x:Key="AtleastOneEmptyConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource AtleastOneEmptyConverter}">
<Binding Path="FieldValidationError"/>
<Binding Path="NumberValidationError"/>
<Binding Path="TextValidationError"/>
...
<Binding Path="DateTimeValidationError"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
Code Behind:
public class AtleastOneEmptyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return values.Cast<string>().Any(p => string.IsNullOrEmpty(p));
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I do suggest, to skip applying styles to all framework elements in your application. It can make your application GUI slow if it grows complex in the future and seek an alternate approach such as Validation Model of WPF.
If you still want to apply style to all types of framework elemetns in your application then you can alternately explore App.xaml way to override styles of all framework elements in your application...
Seems like you want to create a template for a DataTrigger. Since you can't style DataTriggers, you have to resort to creating your own:
public class CollapsingDataTrigger : DataTrigger
{
public CollapsingDataTrigger()
{
base.Setters.Add(new Setter(FrameworkElement.VisibilityProperty, Visibility.Collapsed));
}
}
Then update your XAML to use this CollapsingDataTrigger instead of the regular one(you will have to reference the namespace this is created in).

How to Use DataTrigger to set a property defined in the ViewModel in WPF

I am writing a XAML file which use DataTrigger to set a property in the ViewModel. The ViewModel class defined as:
public class ShellModel : INotifyPropertyChanged
{
public Brush ForegroundBrush
{
get; set;
}
....................
}
I want to use DataTrigger in the View.xaml to set the property ForegroundBrush. The XAML I wrote is:
<StatusBar Name="statusBar" Grid.Row="3">
<StatusBarItem>
<StatusBarItem.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding HasError}" Value="True">
<Setter Property="ForegroundBrush" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HasError}" Value="False">
<Setter Property="ForegroundBrush" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock Name="statusBarMessage" Foreground="{Binding ForegroundBrush}" Text="{Binding StatusMessage}"></TextBlock>
</StatusBarItem>
........................
This does not compile. When I changed the
<Setter Property="ForegroundBrush" Value="Black" />
to
<Setter Property="ShellModel.ForegroundBrush" Value="Black" />
it gives me error:
Dependency property field missing ....
How shall I write this so that the DataTrigger can set the property ForegroundBrush in the ViewModel?
Setters in your DataTriggers should change properties of your UI elements only (and also they only work with DependencyProperties).
Set the Foregound Property of your StatusBarItem directly and set the TargetType of the Style. That should help.
<Style TargetType="{x:Type StatusBarItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasError}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HasError}" Value="False">
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
Having information about the visual representation in your ViewModel is usually not a good idea anyway.

Resources