Same setters for different triggers - wpf

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.

Related

One DataTrigger for multiple properties

I have a listview bound to an Observable collection, the listview has properties that most of them will use the same trigger.
is it possible to define the trigger once in the resource section and just refer to it once needed by the properties ?
so far i come to this :
<Style TargetType="TextBlock" x:Key="Pstyle">
<Setter Property="Text" Value="Testing"/>
<Style.Triggers>
<DataTrigger Binding="{Binding P1}" Value="Testing">
<Setter Property="Foreground" Value="DarkGreen"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
and in the listview member i just apply the defined style to the propertie P1
Style="{DynamicResource Pstyle}"
but how to apply the same defined trigger for let say P2, P3, P4...
Since you want to put your trigger logic on the content of the TextBlock, in my opinion you should use a Trigger targeting Text property, instead of a DataTrigger.
Check out this sample code:
<Style TargetType="TextBlock" x:Key="Pstyle">
<Style.Triggers>
<Trigger Property="Text" Value="Testing">
<Setter Property="Foreground" Value="DarkGreen"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
<TextBlock Name="MyTextBlock1" Text="MyTextBlock1Text" Style="{StaticResource Pstyle}"/>
<TextBlock Name="MyTextBlock2" Text="MyTextBlock2Text" Style="{StaticResource Pstyle}"/>
<TextBlock Name="MyTextBlock3" Text="MyTextBlock3Text" Style="{StaticResource Pstyle}"/>
as you can see there is only one single style applied to different Textblocks.
Then you should adapt this to your listview.

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>

Data template switching based on trigger

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>

Best way to have a TextBox show a message based on a bool in the ModelView

I have a TextBox that needs to have its border change color and then display a message below the text box. This message should be displayed/hidden based on a bool value in the model. What is the best way to achieve this?
There are a ton of different ways of doing this. If you're only going to do this once, the simplest way is to add the TextBlock to the layout and use a style to hide it, e.g.:
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Lightgray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Text="This is the message displayed if IsValid is false.">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
If this is something you want to be able to repeat, you'll want to make this into a template or a user control.
Also, note that changing the visibility from collapsed to visible will change the overall layout, which could have all kinds of undesirable effects. Depending on your design, you might make the visibility default to hidden.
You can use a DataTrigger to set the text, visibility, and appearance of the textbox based on the value in the ViewModel. This seems like the simplest solution.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TheBoolean}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
...
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Another option is to create an IValueConverter to convert the bool to get the text, visibility, and color.

Styling both Hyperlink and TextBlock with a single style?

I have two types of text that need to follow similar coloring rules based on an enumeration:
public enum Modes
{
A,
B,
C
}
A Style with DataTrigger markup is used to colorize:
<Style TargetType="SEE BELOW" x:Key="Coloring">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=.}" Value="A">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=.}" Value="B">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=.}" Value="C">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
One use scenario is a System.Windows.Documents.Hyperlink with a nested System.Windows.Controls.TextBlock:
<Hyperlink><TextBlock/></Hyperlink>
and the other is just a simple TextBlock:
<TextBlock Style="{StaticResource Coloring}" Text="yada"/>
I can, of course, style both TextBlock elements:
<TextBlock Style="{StaticResource Coloring}" Text="yada"/>
<Hyperlink><TextBlock Style="{StaticResource Coloring}"/></Hyperlink>
but that fails to style the underline of the Hyperlink case.
If I try to style across both types:
<TextBlock Style="{StaticResource Coloring}" Text="yada"/>
<Hyperlink Style="{StaticResource Coloring}"><TextBlock/></Hyperlink>
Then the styling fails, as there is (apparently) no common ancestor type for use in the TargetType attribute of the Style.
Since this is suppposed to ultimately be a configurable thing, the goal is to have an XAML document that defines a mode to color mapping for these text blocks. Thus I am reluctant to have two redundant styles (one for Hyperlink and one for TextBlock) that define the same mapping.
So...the question: How can I consistently style both cases without redundant Style XAML blocks?
You can force Hyperlinks to have the same foreground colour as their parent TextBlocks by binding them within the style itself, like this:
<Style TargetType="TextBlock" x:Key="Coloring">
<Style.Resources>
<Style TargetType="Hyperlink">
<Setter Property="Foreground" Value="{Binding Foreground,RelativeSource={RelativeSource FindAncestor,AncestorType=TextBlock}}"/>
</Style>
</Style.Resources>
<Setter Property="Foreground" Value="Orange"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=.}" Value="A">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=.}" Value="B">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=.}" Value="C">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
In this example I've added a setter to make the default foreground Orange, just for testing purposes.
After posting, I realized another approach. I was forcing the Hyperlink with nested TextBlock scenario. If I were to wrap the the Hyperlink in a TextBlock:
<TextBlock Style="{StaticResource Coloring}"><Hyperlink><TextBlock/></HyperLink></TextBlock>
Then both my cases collapse to a TextBlock styling. (In combination with the solution above)

Resources