Using a DataTrigger in my Style - wpf

I am trying to change the fill of a rectangle based upon a value in my ViewModel but despite having tried all the suggestions I have found online, it still isn't working.
The IsMouseOver trigger works fine but the DataTrigger is ignored despite the fact there is always either a 4 or 5 in my ViewModel property.
Could somebody show me where I may be going wrong?
Thanks.
This is my style:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
EDIT: So it seems that my Binding was incorrect due to my incorrect assumption that the DataContext of the UserControl is where I should direct the binding.
The Rectangle is in the template for a ListBox and has "Items" as it's DataContext so by changing the "Binding" it now works.
Many thanks for all assistance though:
SOLUTION:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="4">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="5">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Change your TargetType="Rectangle"
Here is the code I used to test
<Style x:Key="FavouriteRectangleStyle" TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Theme}" Value="4">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Theme}" Value="5">
<Setter Property="Fill" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
And the implementation
<Rectangle Style="{StaticResource FavouriteRectangleStyle}"/>

Inside the constructor of your window or usercontrol what you haveused, you should set this.DataContext = this; Otherwise binding not properly worked for the corresponding window or usercontrol.

That probably because you miss to implement the INotifyPropertyChanged interface, update you Theme property to this:
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
and make sure your codebehind or your viewmodel is implementing the interface
public partial class MainWindow : Window,INotifyPropertyChanged
{
here a full working sample:
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<SolidColorBrush x:Key="content__star__hex646464__shadow" Color="Red"/>
<SolidColorBrush x:Key="content__star__favorit__hexebebeb__shadow" Color="Green"/>
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Rectangle Style="{StaticResource FavouriteRectangleStyle}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Height="100"></Rectangle>
<Button Content="Change Theme" Click="ButtonBase_OnClick"></Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window,INotifyPropertyChanged
{
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
public MainWindow()
{
this.InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Theme = Theme == 5 ? 4 : 5;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Answer in EDIT part of original question. Thanks for all assistance

Related

MultiDataTrigger Not respecting all conditions

I have an "On" "Off" switch that I've created and I cannot get the MultiDataTrigger to respect all Conditions.
<UserControl.Resources>
<Style TargetType="Button" BasedOn="{StaticResource NoDevExpressStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Height="30" Width="81" CornerRadius="15" BorderThickness="1" Background="White">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<!-- On Not Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.On}" />
<Condition Binding="{Binding SwitchClicked, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" Value="False"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OnInactiveColorBrush}"></Setter>
</MultiDataTrigger>
<!-- On Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.On}" />
<Condition Binding="{Binding SwitchClicked, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" Value="True"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OnColorBrush}"></Setter>
</MultiDataTrigger>
<!-- OFF Not Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.Off}" />
<Condition Binding="{Binding Path=SwitchClicked}" Value="False"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OffInactiveColorBrush}"></Setter>
</MultiDataTrigger>
<!-- Off and selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.Off}" />
<Condition Binding="{Binding Path=SwitchClicked}" Value="True"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OffColorBrush}"></Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Button x:Name="Button" Click="ButtonBase_OnClick" />
It seems to respecting the "State" Property which is a dependency property that is set once and never changed. However the SwitchClicked is not respecting which is a property inside the control.
private bool _switchClicked;
public bool SwitchClicked
{
get => _switchClicked;
set => _switchClicked = value;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (Button.Command != null)
{
if (Button.Command.CanExecute(Button.CommandParameter))
{
SwitchClicked = !SwitchClicked;
SetValue(IsActiveProperty, !IsActive);
Button.Command.Execute(null);
}
}
}
Can someone please tell me how to insure that all of my conditions are met?
I have tried binding with relitive source to self and user control, however the SwitchClicked property is in the codebehind and there is no ViewModel.
You have to implement INotifyPropertyChanged either in your view model (or I suppose your code-behind), otherwise setting your property will not notify
being changed and the binding in your trigger will not update.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool SwitchClicked
{
get => _switchClicked;
set
{
if (_switchClicked != value)
{
_switchClicked = value;
OnPropertyChanged(nameof(SwitchClicked));
}
}
}

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: IsMouseOver trigger not working for datatemplate

I'm trying to get the IsMouseOver trigger working for datatemplate. For some reason it is not firing. I also tried the http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html but I do not see anything in the trace. Here is the code:
XAML:
<Window x:Class="FirstSImpleDataApp.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="tmptemplate">
<Border x:Name="brd" BorderBrush="Black" BorderThickness="2">
<TextBlock x:Name="txt">my text box</TextBlock>
</Border>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="txt" Property="Background" Value="Red"></Setter>
<Setter TargetName="txt" Property="Foreground" Value="Green"></Setter>
<Setter TargetName="brd" Property="Background" Value="Green"></Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Canvas x:Name="can" Loaded="can_Loaded">
</Canvas>
</Window>
Code behind:
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
}
private void can_Loaded(object sender, RoutedEventArgs e)
{
var tmp = this.TryFindResource("tmptemplate") as DataTemplate;
var obj = (FrameworkElement)tmp.LoadContent();
can.Children.Add(obj);
}
}
Any help is appreciated!
Your triggers actually work just fine. The problem is with how you're instantiating the template and adding it to your Canvas programmatically.
Apply the template directly in your Xaml to see it work:
<Canvas x:Name="can">
<ContentControl ContentTemplate="{StaticResource tmptemplate}" />
</Canvas>
If you want to apply it programmatically, apply the template to a ContentControl or ContentPresenter and drop it in the Canvas instead:
private void can_Loaded(object sender, RoutedEventArgs e)
{
var tmp = this.TryFindResource("tmptemplate") as DataTemplate;
can.Children.Add(new ContentPresenter { ContentTemplate = tmp });
}
Here is an alternative using Style triggers. The border remains black, but with a green margin from the TextBlock.
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="Green"/>
<Setter Property="Margin" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
<Border BorderThickness="1" BorderBrush="Black">
<TextBlock Text="My Text Block"/>
</Border>

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 use IDataErrorInfo.Error in a WPF program?

I have an object like that:
public class Person : IDataErrorInfo
{
public string PersonName{get;set;}
public int Age{get;set;}
string IDataErrorInfo.this[string propertyName]
{
get
{
if(propertyName=="PersonName")
{
if(PersonName.Length>30 || PersonName.Length<1)
{
return "Name is required and less than 30 characters.";
}
}
return null;
}
}
string IDataErrorInfo.Error
{
get
{
if(PersonName=="Tom" && Age!=30)
{
return "Tom must be 30.";
}
return null;
}
}
}
Binding the PersonName and Age properties is easy:
<TextBox Text="{Binding PersonName, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding Age, ValidatesOnDataErrors=True}" />
However, how can I use the Error property and show it appropriately?
You should modify the TextBox style so it shows what's wrong with the property. Here is a simple example that shows the error as tooltip:
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
Just put it inside Application.Resources from your app.xaml file and it will be aplied for every textbox of your application:
<Application.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
Here is an example, adapted from this question, that shows how to display the error in Tooltip:
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Resources