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

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.

Related

Conditionally create view in XAML?

I have views that may or may not be created in xaml based on a boolean condition in codebehind or a viewmodel.
I'd like to do something like:
<AlwaysVisibleView />
<IfShowSometimesViewBindingOrVariableOrSomething>
<SometimesView AProperty="something"/>
</IfShowSometimesViewBindingOrVariableOrSomething>
I'd like to implement this avoiding codebehind and other such trickery as much as possible, at the last ideally I don't want the view to be instantiated.
Dynamic creation of views can sometimes get a little tricky. Plus they tend to mess up how XAML renders things.
Can you just bind the visibility of the "Sometimes visible view" to a property? You can run that through a Boolean to visibility convertor and just have the code behind toggle the bool to show/hide.
Example thread
You can work with ContentControl and Style.Triggers to change content and visibility based on a property (example: bool ShowMe):
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{x:Null}"/>
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowMe}" Value="True">
<Setter Property="Content">
<Setter.Value>
<SometimesView AProperty="something"/>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Different template on different ListBoxItem value without DataTemplateSelector?

As title, is it possible?
I have seen in TreeView you can defines different HierarchicalDataTemplate for different datatype using DataType attribute, it doens't even need DataTemplateSelector.
So I wonder if is possible to choose a template according to a binded value without using DataTemplateSelector?
In my condition, is very simple, if the data object's Property = 1, then display template1, 2 then template2.
Is it possible to do it without DataTemplateSelector?
Yes, you can use a DataTrigger
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="2">
<Setter Property="Template" Value="{StaticResource Template2}" />
</DataTrigger>
</Style.Triggers>
</Style>
I actually prefer DataTriggers to a DataTemplateSelector because they respond to PropertyChange notifications, and I prefer to see my UI logic in my UI code.

Style Trigger to Apply another Style

I am sure this has been asked before, but I haven't had an easy time figuring out how to phrase the query.
I have this style;
<SolidColorBrush x:Key="SemiTransparentRedBrushKey">#F0FF0000</SolidColorBrush>
<Style x:Key="TextBoxEmptyError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.Length}" Value="0">
<Setter Property="BorderBrush" Value="{StaticResource ResourceKey=SemiTransparentRedBrushKey}"/>
</DataTrigger>
</Style.Triggers>
</Style>
That I can apply to Textboxes to have a red border when they are empty. Its great, I can just add Style="{StaticResource TextBoxEmptyError}" to the Control Tag. But what if I want to apply this style with a trigger, so that the control only used it under certain conditions (like a binding being true)? Something like:
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ApprovedRequired}" Value="True">
<Setter Property="Style" Value="{StaticResource TextBoxEmptyError}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
This code throws an exception though {"Style object is not allowed to affect the Style property of the object to which it applies."}
Can something like this be done?
Edit: If this cannot be done with a Style trigger because it would overwrite itself, is there another way to Conditionally apply a resource style?
Edit: I can change the question title if there is a more proper term for this action.
Styles cannot be set from a Setter within the Style, because then essentially the first Style would never exist at all.
Since you're looking for a Validation style, I would recommend looking into Validation.ErrorTemplate, although if that doesn't work you can change your trigger so it modifies specific properties such as BorderBrush instead of the Style property
i would think of using a Template with a TemplateTrigger and there you can change the style to what ever you like based on what ever condition

Calling an ElementMethod in a DataTrigger

I have a TextBox thats using this Style. I need to add a Focus() method to in this style.
So that when the TextBox is Visible and the ValidParent Property is false then i call the Focus() method on that TextBox
<Style x:Key="ParentTextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ValidParent }" Value="false">
...
</DataTrigger>
<DataTrigger Binding="{Binding Path=ValidParent }" Value="false">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
Is this possible ?? And if it is then if i had multiple textboxes with the same behaviour which one will recieve Focus?? Does the Order of the Controls in my Xaml make a diffrence then ??
Thank you
You cannot call methods via style triggers. Using Interactivity from the Blend SDK you have more options, including method calls but they cannot be easily used in styles.

Why can't I add a DataTrigger to my control's Triggers collection?

Why cant I code like this
<Border Width="130" Height="70">
<Border.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorder}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorderInactive}"/>
</DataTrigger>
</Border.Triggers>
</Border>
I get this error
Failed object initialization (ISupportInitialize.EndInit).
Triggers collection members must be of type EventTrigger.
Error at object '4_T' in markup file
What am I doing wrong plz help.
Abe is correct and explains the limitations well. One thing you might want to consider is:
Instead of having two border styles, and trying to pick between them based on a trigger...
Use a single style on your border, this style's setters represent your 'normal' look.
This style also contains your DataTrigger, and your DataTrigger has a collection of setters which essentially represents your second style (which have higher priority than the standard setters when this trigger evaluates to true!
Edit:
Something like this -
<Style TargetType="Border" x:Key="BorderStyle">
<!-- These setters are the same as your normal style when none of your triggers are true -->
<Setter Property="BorderBrush" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<!-- These setters are the same as your ListBoxItemBorder style -->
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<!-- These setters are the same as your ListBoxItemBorderInactive style -->
<Setter Property="BorderBrush" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
Unfortunately, only EventTriggers can be applied directly to elements. If you want to use a Trigger or DataTrigger, they have to be in a Style, ControlTemplate, or DataTemplate.
From the resource names, it looks like this is a Border inside a ListBoxItem ControlTemplate. You could easily move the triggers into the template's triggers collection.
Here is a way for no limitations triggers.
Example:
<Border Width="130" Height="100" Grid.Row="1">
<ListBox x:Name="lstItems" ItemsSource="{Binding TestItems}">
</ListBox>
<tg:TriggerExtensions.Triggers>
<tg:TriggerCollections>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxRed}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0" IsInvert="True">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxBlue}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
</tg:TriggerCollections>
</tg:TriggerExtensions.Triggers>
</Border>
Link Sample
Link Component Github

Resources