WPF DataTrigger on ViewModel Property Change - wpf

I'm trying to fire a trigger when a property in my ViewModel changes. I can't seem to get the trigger to fire no matter what I try. My XAML looks like this:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Padding" Value="0 1" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanding}" Value="True">
<Setter Property="Control.Template" Value="{StaticResource ResourceKey=loadingTreeViewItem}" />
</DataTrigger >
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
The IsExpanded and IsSelected bindings are working fine, however I can't get the DataTrigger to fire when IsExpanding is true. These properties are in the same ViewModel. I have tried adding different variations for RelativeSource but am not having any luck. Any feedback is appreciated.

Turns out the trigger was working. The problem was that the IsExpanding property and the call to get the data for the TreeView were both happening on the UI thread. I threaded the call to get the data and everything is working as expected

Related

How to change a control's appearance based on whether its (dynamic) context menu has items, using a reusable Style?

I have a button with a dynamic context menu (i.e. fed from an ItemsSource). I would like to use its ContextMenu's .HasItems property as a trigger to disable it when the context menu is empty. The following does not seem to work, even though the debugger shows no issues with the binding:
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.HasItems}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Used like this:
<Button Style="{StaticResource ContextMenuButtonStyle}" Content="Items" Click="ShowContext">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding MyItems}" Placement="Top" VerticalOffset="-1" />
</Button.ContextMenu>
</Button>
This always just behaves as if there were no items, i.e. the button stays disabled. However, if I comment out that trigger I can immediately see that the context menu clearly contains items at that point.
Interestingly, I have a second, similar trigger, based on ContextMenu.IsOpen that is working fine:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.IsOpen}" Value="True">
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
So maybe the issue isn't actually the binding in the trigger but the querying/updating of the ItemsSource that is somehow impacted by the disabled state? Any other ideas or hints on how to resolve this?
Update: I have by now found out that the Items collection is apparently not populated from ItemsSource until the context menu is actually shown, so that certainly explains why my HasItems approach doesn't work. So, is there maybe a way to have the trigger react to the contents of the referenced ItemsSource - but without explicitly referencing that source in the Style so that it could be reused for other buttons with different items sources?
OK, I just realised the obvious answer as I was posting my update to the question:
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.ItemsSource.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Have you thought about doing this instead?
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyItems.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>

Unchecking a CheckBox from a Style Setter?

I have multiple datatriggers of the form:
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PrimaryInsuranceCompany, Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
The IsEnabled property on the CheckBox is set correctly. However, the IsChecked property does not get unchecked if it had been manually checked in the first place.
Can IsChecked be unchecked from a Setter?
Edit#1
To complicate matters a bit more, the checkbox is bound to a property in my viewmodel as:
<CheckBox
Style="{StaticResource VerifyCheckBox}"
IsChecked="{Binding PrimaryInsurance.SelectedInsurance.Verify}"
Content="Verify" HorizontalAlignment="Left" Margin="789,92,0,676" Width="46" />
And there are multiple DataTrigger s in the Style.Triggers each checking a different element on the UI. In short, the UI has all the data validation. For instance, the element named above, PrimaryInsuranceCompany is:
<wc:AutoFilteredComboBox
Name="PrimaryInsuranceCompany"
IsEnabled="{Binding PrimaryInsurance.Enabled}"
ItemsSource="{Binding PrimaryInsurance.AllCompanies}"
ItemTemplate="{StaticResource CompanyTemplate}"
IsEditable="True"
IsCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="Companyname"
Text="{Binding PrimaryInsurance.NewCompanyName, UpdateSourceTrigger=PropertyChanged}"
theFilter="{Binding PrimaryInsurance.TheFilter}"
SelectedItem="{Binding PrimaryInsurance.SelectedInsurance.Company}"
h:ComboBoxRegexValidator.RegexText="{x:Static h:RegexLibrary.NonEmptyRegex}"
HorizontalAlignment="Left" Margin="590,138,0,0" VerticalAlignment="Top" Width="363" />
So with the combobox element in particular, I was trying to avoid duplicating the validation process in the view model since it is already being done directly in the view. Can this be done?
This quite often causes a confusion. You might want to read this: https://msdn.microsoft.com/en-us/library/vstudio/ms743230%28v=vs.100%29.aspx.
Basically, dependency properties in wpf might be set in different places: locally (IsChecked=True), in style, in trigger, and so on. When property is set in multiple places, specific resolution order is used to resolve the conflict. Here is an example:
<Window.Resources>
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Style.Triggers>
<DataTrigger Binding="{Binding TestValue}" Value="True">
<Setter Property="IsChecked" Value="False" />
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<CheckBox Content="Hey there" IsChecked="True" Style="{StaticResource VerifyCheckBox}" />
Here we set IsChecked in two places: locally (IsChecked=True) and in trigger. Local value has higher priority and because of that, when TestValue will become true checkbox will not be unchecked. However we did not set IsEnabled locally, so it will have it's value from trigger. Note that if we did set IsEnabled=True locally, trigger would have no effect at all.
Now let's try like this:
<Window.Resources>
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Setter Property="IsChecked" Value="True"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding TestValue}" Value="True">
<Setter Property="IsChecked" Value="False" />
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<CheckBox Content="Hey there" Style="{StaticResource VerifyCheckBox}" />
We moved IsChecked initial setter in style and now it works as expected, because trigger has priority over values in style setter.
Update: user clicking does not change behavior above. So, if you have local IsChecked=True, then user clicked twice, then TestValue becomes true - it will not be unchecked. And if you do the same but initialize IsChecked=True in style setter - it will be unchecked.

Why is this DataTrigger not working after a PropertyChanged?

The following triggers work almost as expected:
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>
After loading the view, the colors are correct.
Then I execute an async taks en when it has finished I give the propertychanged on the object that has the binding to my datagrid-row.
But why is the DataTrigger not fired (I have to refresh the view to see the effect)?
EDIT:
My problem is that I don't now which property I have to give the PropertyChanged.
Some details about the datagrid (Projects is an ObservableCollection):
DataGrid SelectedItem="{Binding Project}" ItemsSource="{Binding Projects}">
The property of object Project that the binding must use is:
Project.Variants[0].InUse
I tried also the triggers:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<DataTrigger Value="True" Binding="{Binding Path=., Converter={StaticResource InUseConverter}}">
In the view model I have tried after Project.Variants[0].InUse = null;:
Project.OnPropertyChanged("InUse");
Project.Variants[0].OnPropertyChanged("InUse");
raisePropertyChanged("Project.Variants[0].InUse");
raisePropertyChanged("Variants[0].InUse");
raisePropertyChanged("Projects");
raisePropertyChanged("Project");
raisePropertyChanged("InUse");
At last it works using:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
Project.Variants[0].OnPropertyChanged("InUse");
Try to set Foreground property in your style to change it dynamically at runtime
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>

Enabling or disabling validation upon context

Introduction
I have two TextBox in my view, each binded to some properties in my view-model (Property1, Property2).
TextBox are alternatively enabled upon some boolean, and properties, are validated using IDataErrorInfo in the view-model + some styling in the view.
Problem
I would like to disable validation style when items are disabled.
NB1: Currently, solution I found is to change validation scheme directly in the view-model, but this requires to notify for property changes in order to force the view to re-read IDataErrorInfo (while properties haven't really changed, only selector ...)
NB2: My problem is really close to this one, but the description and the solutions are far too complex for me to really get the point.
Pseudo-Code
<UserControl
<UserControl.Resources>
<Style TargetType="{x:Type Control}" x:Key="ControlValidationStyle">
...
</Style>
</UserControl.Resources>
...
<TextBox
Text="{Binding Property1,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsMode1}"
Style="{StaticResource ControlValidationStyle}"
/>
<TextBox
Text="{Binding Property2,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsMode1,
Converter={StaticResource BoolInverse}}"
Style="{StaticResource ControlValidationStyle}"
/>
</UserControl>
ControlValidationStyle
<Style TargetType="{x:Type Control}" x:Key="ControlValidationStyle">
<Style.Resources>
<Style TargetType="ToolTip">
<Setter Property="Background" Value="Tomato" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Foreground" Value="white" />
</Style>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}" />
<Setter Property="Background" Value="Bisque"/>
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
Why wont you use MultiTrigger instead of Trigger:
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Validation.HasError" Value="true" />
<Condition Property="IsEnabled" Value="true" />
</MultiTrigger.Conditions>
<Setter .../>
</MultiTrigger>

WPF - Enable/Disable tabstop based on whether property has data

I need to set the tabstop of a textbox based on whether or not the bound property has data or not. The properties are nearly all strings - I want to disable the tabstop if the property is null or empty.
I am using a style for these textboxes.
Here is the style I am currently using:
<Style TargetType="TextBox" x:Key="FauxLabel">
<Setter Property="Background" Value="Transparent" />
<Setter Property="IsTabStop" Value="True" />
<Setter Property="IsReadOnly" Value="True" />
<!-- rest of setters truncated -->
</Style>
And here is an example of my usage:
<TextBox
Name="Account"
Style="{StaticResource ResourceKey=FauxLabel}"
Text="{Binding
Path=SelectedItem.AccountNumber,
ElementName=CrfResults}"/>
In this exmple, if the AccountNumber property is null or empty, I want to disable the tabstop. I am using Visual Studio 2010 and .Net 4.0. Can anyone help me out?
Update:
Thanks to Rachel for her answer. I was able to update the style to handle all textboxes using that style using by adding the trigger below which binds to the text property, rather than the underlying bound property:
<Style.Triggers>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="IsTabStop" Value="False">
</Setter>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="IsTabStop" Value="False">
</Setter>
</Trigger>
</Style.Triggers>
Use a DataTrigger which checks if the value is {x:Null}
<Style TargetType="TextBox" x:Key="FauxLabel">
<Setter Property="IsTabStop" Value="True" />
<Style.Triggers>
<DataTrigger Property="{Binding ElementName=CrfResults, Path=SelectedItem.AccountNumber}" Value="{x:Null}">
<Setter Property="IsTabStop" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
You could probably do it with a regular Trigger instead of a DataTrigger too

Resources