Data template switching based on trigger - wpf

I'm having the same problem as described here :
ContentTemplateSelector is only called one time showing always the same datatemplate
I've attempted the same solution as Simon Weaver suggests (although his answer is somewhat abbreviated so i'm guessing it looks like the following):
<ContentControl >
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="true">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentControl.ContentTemplate"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="false">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentControl.ContentTemplate">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
However when i run this, i just get 'System.Windows.Style' displayed in my content control.
As an aside, i've (sort of) got it working using an overridden DataTemplateSelector class, but the problem there is that the selector only gets evaluated on start up. I need this switching behaviour based on the data bound IsSelected property - which i was hoping the above snippet achieves. BTW the actual data templates themselves just contain UI stuff - no data triggers etc so i'm not including them in my post.

Your ContentControl is setting it's Content to the Style since you are missing your ContentControl.Style tag.
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentTemplate" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentContro.Style>
</ContentControl>

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>

Validation.ErrorTemplate that accesses properties on the AdornedElementPlaceholder

I would like to have my error template look different depending on some property values on the adorned control.
Setting the TargetType like below results in a runtime exception: 'TextBox' ControlTemplate TargetType does not match templated type 'Control'. Thus, it appears that the ErrorTemplate must use a targetType of 'Control'.
<ControlTemplate x:Key="ValidationErrorTemplate" TargetType={x:Type TextBox}>
<Grid>
<AdornedElementPlaceholder HorizontalAlignment="Left" Name="placeholder"/>
<Grid Background="Yellow">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding IsReadOnly}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Grid>
</ControlTemplate>
I removed the targetType and then tried this:
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
And then this, which yielded no exceptions but also no effect:
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
and this, which yielded no exceptions but also no effect:
<DataTrigger Binding="{Binding (TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
and finally this, which yielded "BindingExpression path error: 'IsReadOnly' property not found on 'object' ''AdornedElementPlaceholder'":
<DataTrigger Binding="{Binding IsReadOnly, ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
Does anyone have any other ideas on how to reference dependency properties in the ErrorTemplate?
The correct answer was:
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
While this was one of my failed attempts early on, my test setup was flawed. I was setting the default background property on the grid instead of setting it in the style. Due to Dependency Property precedence, the value set directly on the object will always trump any value set in a style (specifically, in my triggers).
Here is a working setup:
<ControlTemplate x:Key="ValidationErrorTemplate">
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="Yellow"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<AdornedElementPlaceholder Name="placeholder"/>
</Grid>
</ControlTemplate>
One key here is that AdornedElement is always of type Control, so you must do the appropriate qualification (or cast?) to access properties that are not exposed on Control. This is done via the parenthesis around the class name and property. Another example is: AdornedElement.(CheckBox.IsChecked). Since IsChecked is not on Control, you must qualify it by explicitly stating the class type that owns the property.

Bind DataGridCell style directly to its contents

I have a StatusCell style for DataGridCell that I would like to use in several place in my application. I would like to externalize the Style tag so that I can reuse it easily without having to duplicate the code in my XAML everywhere.
Every other source I've found has required me to bind the trigger off the property from my ViewModel. But across the application, the column might be bound to MyStatusProperty or SubObject.MyStatusProperty, etc, so I want to do this to allow me to have one style that will apply to all of these without having to specify where it's binding from.
I am able to do this with a TextBlock with the following style. This lets me bind the TextBlock to whatever I want and the style binding doesn't matter where it's coming from.
<Style x:Key="StatusLabel" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
and when I create a textblock that I want to use this styling, all I have to do is
<TextBlock Style="{StaticResource StatusLabel}" Text="{Binding Whatever}" />
But with a DataGridCell it doesn't let me do this
<Style x:Key="StatusCell" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="Content" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
<DataGridTextColumn Header="Status" Binding="{Binding MyStatusProperty}"
CellStyle="{StaticResource StatusCell}" />
I also tried setting up the trigger like this:
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}}" Value="Reviewed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</DataTrigger>
But neither of these work. I have also tried swapping out "Content" in the last example for "Binding" and "Text"
Is there another property I can bind to in the DataGridCell that will let bind the style trigger to the contents of the cell without knowing the binding path?
As usual, I found a workaround shortly after asking. Since it's working with TextBlocks, I just have to use TemplateColumns instead of TextColumns, although I'd still prefer to be able to use TextColumns since they'd use 6 less lines of XAML.
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty}" Style="{StaticResource StatusCellTextBlock}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Using the style:
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>

Same setters for different triggers

I have several elements and all of them should look the same if "X". The problem is, "X" is different for each element, so I can't easily use a shared style for them. But in all cases, the setters are the same, so I would like to have them all in one place. How can I achieve that?
More concretely, I have this code:
<TextBlock Name="text1">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedMonth, Converter={conv:IsNullConverter}}"
Value="False">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters -->
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Name="text2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters, same ones as above -->
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Here, I would like to put the two Setters to some common place, so that I wouldn't have to always change them one by one. Is there some way to do that?
I tried putting a collection of Setter together and then using something like DataTrigger.Setters="{StaticResource SharedSetters}". This doesn't work, but if it did, it would be exactly what I want.
First of all, you could have a common style which would be the base (BasedOn attribute) for all the other styles. This style would contain a normal Trigger which is controlled by a custom boolean attached property, and the setters you want to reuse. Then, in the other styles, which are based on the base style, your DataTriggers would only set the attached property, and let the base style set all the other properties.
Assuming you have created the attached property IsSelected on the type Foo, the base style would look something like:
<Style x:Key="IsSelectedStyle" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="local:Foo.IsSelected" Value="True">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters -->
</Trigger>
</Style.Triggers>
</Style>
And then you would use it like this:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource IsSelectedStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="local:Foo.IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Move value of your setters to static resources and set key in setters.
<SolidColorBrush x:Key="keyName" Color="RoyalBlue" />
and
<Setter Property="Background" Value="{StaticResource keyName}" />
in your code.
Now you can change brush of "keyName" and it affects all setters.

How can I set a datatrigger for a list view depending on a cell value?

Hi I used to use a datagrid but it was soooo slow when working with tabs, so I switched to ListView but I don't know how to style it, (it's a lot more complicated than DataGrid in my opinion).
So I was wondering how to do this for ListView:
<Style x:Key="gridCell" TargetType="DataGridCell">
<Setter Property="BorderBrush"
Value="{StaticResource lightBlueBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Status}" Value="Otpisano">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
And then in the View
CellStyle="{StaticResource gridCell}"
This is how you do the Styling:
Answer

Resources