WPF Databinding and Styles with MVVM - wpf

I have a problem with databinding on a style in WPF.
The basic setup looks like this:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Style.Triggers>
<DataTrigger Binding="{Binding Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The style is applied to all objects of type Player. These objects have a property of type Teams (Enum having values A, B and C). Depending on which team the player is in the template applied to visualize the player differs.
The problem that now occurs is that the whole thing is used in a MVVM application and that somehow the DataContext of the Player object gets set to the ViewModel of the topmost View. I used the new diagnostics options (TraceLevel) to find out something about the problem and got this:
System.Windows.Data Warning: 66 : BindingExpression (hash=30607723): Found data context element: Player (hash=35170261) (OK)
System.Windows.Data Warning: 74 : BindingExpression (hash=30607723): Activate with root item ToolboxViewModel (hash=61398511)
System.Windows.Data Warning: 104 : BindingExpression (hash=30607723): At level 0 - for ToolboxViewModel.Team found accessor <null>
So basically the Player object is found as a data context element (whatever that means) but still the ToolboxViewModel is used as DataContext. How can I fix this? How can I refer to the styled object in the binding expression?

I don't know why I didn't think of this earlier:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
It works perfectly fine with {RelativeSource Self}

You cant' style anything with a trigger that you haven't styled with your style already. You'll need to do this:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Setter Property="Template" Value="{StaticResource TeamBTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Seems like your style should work after this. Those binding warnings are confusing though.

Related

XAML Style Trigger - Change Style ONLY for Object of with a specific Name

I am new XAML however I am given the task to override some styles for certain elements within an existing application.
In my custom Theme, I am attempting to override the style of a BORDER control.
From what I can tell (using Snoop) to inspect the application, the element I want to change is just a plain border.
The border also seems to have a Name of "SubMenuBorder". Please see the image below.
Here is the latest iteration of my style snippet in which I am trying to set the border control's Background, BorderBrush and BorderThickness BUT ONLY if the control has a name of "SubMenuBorder"
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<Trigger Property="Name" Value="SubMenuBorder">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Unfortunately the above does NOT work.
The style trigger does not seem to fire/apply to the intended control.
If I simplify things further and just style ALL borders with the following snippet, then it seems to work and the border control I want to change, is styled, but so is every other border control in the application.
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</Style>
Further Findings
I attempted to use a DataTrigger... which unfortunately doesn't work either.
Snoop shows below that the data trigger is being satisfied, however on the second image below you can see that the property of the background and borderbrush are still from the parenttemplate.
Any ideas please?
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Name}" Value="SubMenuBorder">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You cannot use triggers to modify a Border that is defined in a ControlTemplate, with the exception of using an implicit Style that applies to all elements of the type specified by the TargetType property of the implicit Style.
You will either have to modify the ControlTemplate itself, or programmatically find the Border element in the visual tree and then change its runtime property values. The first approach, i.e. modifying or creating a custom template, is the recommended approach.
The name "SubMenuBorder" is only known and applicable within that Border element's namescope.

Get ValidationError in specific xaml element using a template

i have the following code snippet:
<ContentControl Height="16">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtDistanceH, Path=(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding ElementName=txtDistanceH, Path=(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Now i want to put the style in a separate file instead of inline. However i would like to be able to specify which element it should get the Validation.Errors from, so i can use a single template for several different controls.
Is there any way to tell the template where it should get the Validation.Errors from, OTHER than binding to an element by name?
I tried setting the ContentControls DataContext to the element txtDistanceH, but then i just get a binding error saying that the property cannot be found on the root-element.
thanks for taking the time to answer my question. I've tried it and it works!
However i do have a comment and another related question.
The code i have now is:
<!-- Set content of contentcontrol to the ValidationError of a control stored in Tag, if there is one -->
<Style x:Key="ShowValidationError" TargetType="ContentControl">
<Style.Resources>
<x:Static x:Key="EmptyString" Member="System:String.Empty" />
</Style.Resources>
<Setter Property="Content" Value="{StaticResource EmptyString}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors).CurrentItem}" />
</DataTrigger>
</Style.Triggers>
</Style>
(Validation.Errors).CurrentItem is better than (Validation.Errors)[0], because the latter gives an out of range exception in the debug window when the error is resolved, see This Link for more information. The empty string ensures the control has the same size when its empty as when it has an error.
However even though it compiles and works, i still get some errors during design time. The code responsible is (Validation.HasError) and (Validation.Errors), respectively, in the above snippet.
Property 'Errors' is not attachable to elements of type 'Object'.
The property 'HasError' was not found in type 'Validation'.
Is there any way to fix / suppress these errors?
Bind the Tag property of the ContentControl to the target element using the element name binding and then update the style to use relative source self bindings to the tag to get at the validation errors.
Somewhere in Resources:
<Style x:Key=“ValidationStyle” TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
And use it thusly:
<ContentControl Style=“{StaticResource ValidationStyle}” Tag=“{Binding ElementName=txtDistanceH}” />

WPF re-evaluate style triggers

My App supports Localization in English and Spanish. I have a textlabel that depending on the property Age it applies different styles.
For example, if Localization is set to English:
If Age < 18 -> Text = Under age [in colour red]
If Age > 18 -> Text = Over age [in colour green]
If Age == 18 -> Text = On eighteen [in colour blue]
This is working fine with this code:
<TextBlock Margin="5,0,0,0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Age,
Converter={StaticResource CuttoffConverter}, ConverterParameter=18}"
Value="False">
<Setter Property="Text" Value="{Loc strUnderAge}"/>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Age,
Converter={StaticResource CuttoffConverter}, ConverterParameter=18}"
Value="True">
<Setter Property="Text" Value="{Loc strOverAge}"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Age}" Value="18">
<Setter Property="Text" Value="{Loc strOnEighteen}"/>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The localization part, is covered with {Loc XXXXXX} tag and it works fine except for this: if the language is changed "on-air", this DataTrigger Setter properties are not re-evaluated, so the labels are still displayed in English.
Is there any way to force to be re-evaluated?
UPDATE:
I have updated my Localization library to this one: WPF Localization Advanced which now supports Styles.
However, now it breakes when compiling the above XAML code. The error says:
"LocExtension is not valid for Setter.Value. the only supported
MarkupExtension types are DynamicResoruceExtension and BindingBase or
derived types."
Is there any way to accomplish the same purpose I did with trigger but with any of the suported methods by LocExtension?
define strUnderAge as a property within a class that Implements the INotifyPropertyChanged Interface.
bind the trigger to the strUnderAge property.
when you change the language raise the PropertyChanged event.
Otherwise the trigger can not know that the language has changed.

Why WPF Triggers must be declared into a style (even in-line)?

I don't understand why WPF allows me to write both
<Grid>
<Grid.Triggers>
<DataTrigger Binding="{Binding HasNeverBeenSeen}" Value="true">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Grid.Triggers>
</Grid>
and
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasNeverBeenSeen}" Value="true">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
but only the second seems to work. Why is there a Triggers tag to Grid element if we must use a Style?
Thanks
Short answer to your question is because this is how it is designed by WPF team.
FrameworkElement.Triggers can only have EventTriggers although property is collection of TriggerBase. It's also clearly stated on MSDN page:
Note that the collection of triggers established on an element only
supports EventTrigger, not property triggers (Trigger). If you require
property triggers, you must place these within a style or template and
then assign that style or template to the element either directly
through the Style property, or indirectly through an implicit style
reference.

WPF: Bind to element in DataTemplate in ResourceDictionary

I'd like to bind to a element's property (a ListBox's SelectedItems.Count in my specific case) that's dynamically inserted into my window from a DataTemplate located in a ResourceDictionary. I'd like to enable/disable a button when the count reaches a certain number of ListBoxItems are selected. I thought this would work:
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource myResourceKey}, Path=myListBox.SelectedItems.Count}" Value="25">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
But I'm getting the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'myListBox' property not found on 'object' ''DataTemplate' (HashCode=50217655)'. BindingExpression:Path=aoiListBox.SelectedItems.Count; DataItem='DataTemplate' (HashCode=50217655); target element is 'Button' (Name='myBtn'); target property is 'NoTarget' (type 'Object')
How can I achieve this binding? Thanks in advance.
Well you can write a workaround, but I strongly recomment not to implement it that way. Consider, that a style in a ResourceDictionary is an empty resource, which should be decoupled from any specific instance (in your case myListBox) in your application. Problem is, that you cannot use this malformed style on another Button. So you don't need to, better you shouldn't, declare it as resource.
I definitely recomment to declare this Style directly in the Button. E.g.
<ListBox x:Name="myListBox" />
<Button>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListBox,
Path=SelectedItems.Count}" Value="25">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Additionally, I would use a Binding via the ElementName property.

Resources