Generic style with specific trigger values - c#

I have several TextBlocks in my UserControl that I want to change to become Bold and have a Red font when a Property is triggered. The thing is that each of them are changed by different properties. I saw a solution here with Tags, but couldn't quite get it to work for me. I apologize if this is a duplicate, but I couldn't find any solution that solved my problem.
My Style looks like this:
<Style x:Key="TextBlockTrigger" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="true">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
And this is the TextBlock I'm trying to use it on:
<TextBlock Name="TextBlock1" x:Uid="TextBlock1" Text="This text should become bold and Red"
Style="{StaticResource TextBlockTrigger}" Tag="{Binding Path=TriggerProperty, UpdateSourceTrigger=PropertyChanged}"/>
I added a button with a codebehind function that reads the Tag, and a breakpoint shows that Tag is set to true, but the text is still regular black.
TriggerProperty is set by a function call in the View Constructor, after InitializeComponent:
public MyWindow()
{
InitializeComponent();
UpdateServerProperties();
}
public void UpdateServerProperties()
{
//other code
if(ServerValue == true)
{
TriggerProperty = true;
OnPropertyChanged("TriggerProperty");
}
}
It's a bit simplified, but the actual code is overly complicated, but results in the same thing. ServerValue gets a value, and I have confirmed TriggerProperty does get updated to true.

The Tag property has an object type. Xaml has no way of knowing that true represents a bool value, so it just assumes you meant it to be a string. Assuming you are setting Tag to a boolean value, your Trigger is evaluating Equals(true, "true"), so the condition fails.
Try using {x:Static} to point to some constant boolean value. I keep a KnownBoxes class around for this sort of thing:
public static class KnownBoxes
{
public static readonly object True = true;
public static readonly object False = false;
// ... more common values ...
}
These values are easily referenced from Xaml, e.g., {x:Static ns:KnownBoxes.True}.
Alternatively, you could use the element syntax:
<Trigger Property="Tag">
<Trigger.Value>
<s:Boolean xmlns:s="clr-namespace:System;assembly=mscorlib">True</s:Boolean>
</Trigger.Value>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
Or you could set Tag to the string "true", though that may cause some confusion when someone else works on your code :).

If you set TriggerProperty to true dynamically at runtime, the class where this property is defined should implement the INotifyPropertyChanged interface and raise the PropertyChanged event for the trigger to trigger.
If you set the Tag property to a hard-coded value of true, your sample markup should work as expected:
<TextBlock Name="TextBlock1" x:Uid="TextBlock1" Text="This text should become bold and Red"
Style="{StaticResource TextBlockTrigger}" Tag="true"/>
You should also set the Value property to a typed bool value in your Style:
<Style x:Key="TextBlockTrigger" TargetType="{x:Type TextBlock}"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<Style.Triggers>
<Trigger Property="Tag">
<Trigger.Value>
<s:Boolean>True</s:Boolean>
</Trigger.Value>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>

Related

C# WPF UI is not updated by datatrigger when I change a property in the listview item

I want to make a chess game in WPF, I decided to use listview to display my fields. I have a class named Field with properties. I have a list containing 64 fields and i changed to style of my listview in xaml so its look like a chess field. However, i have problem with the datatriggers. I need to change the picture (that will show the chess piece) when a property in that element in my list is changed.Nothing happens when the 'hasPiece' property is changed. I use INotifyPropertyChanged so i think i have no problem with the UI refresh, because i can update any displayed data in labels and testboxs. That is why i know i successfully changed the property when i selected 1 item, because i binded to a label. So when i select a field(an item) the 'hasPiece' property is updated to hidden, but the image still remains visible. I dont know much about datatriggers and I dont know what i did wrong.
<ListView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Fields}" Width="410" SelectedItem="{Binding selectedField,Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Height" Value="50"></Setter>
<Setter Property="Width" Value="50"></Setter>
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="Border" BorderThickness="4,4,4,4" Background="{Binding Brush}" BorderBrush="{Binding Brush}">
<Grid Name="Grid" Background="{Binding Brush}">
<Image Name="PieceImage" Height="40" Width="40" Source="Images/something.jpg" >
</Image>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Grid" Property="Background" Value="{Binding MouseOverBrush}"/>
<Setter TargetName="Border" Property="BorderBrush" Value="{Binding MouseOverBrush}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red"></Setter>
</Trigger>
<DataTrigger Binding="{Binding hasPiece}" Value="Hidden">
<Setter TargetName="PieceImage" Property="Visibility" Value="Hidden">
</Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
This is what i currently do when an item is selected:
private Field selected { get; set; }
public Field selectedField
{
get { return selected; }
set
{
selected = value;
// fieldId = selected.ID.ToString();
removeImage(selected.ID);
}
}
public void removeImage(int id)
{
foreach (Field item in Fields)
{
if (item.ID == id)
{
item.hasPiece = System.Windows.Visibility.Hidden;
fieldId = item.hasPiece.ToString();
}
}
}
I currently just want to test everything that needed, i know later i have to change things, but now i just want to know how i can change something in the style with datatriggers properly.
I solved the problem!
I implemented the INotifyPropertyChanged interface to my ViewModel, so the UI got updated when a variable which was created there is changed or when I added or removed items to my Observable collection which was also created there.
However if I want to edit my collection I have to implement this interface to my model.
My problem was not related to DataTrigger. My problem was the lack of INotifypropertyChanged in my model.

WPF DataGridColumn style depending on tag

following the post in here, I've managed to create Tag property to the columns of my datagrid. Now, I would like to have different colors of columns depending on the value in tag. The goal is to have a table with days in columns, and today's current day would be highlighted (the date corresponding to column is stored in it's tag). Is it achievable? I found the solution based on coloring cells programmatically, but it doesn't suit my needs (multithreading issue - sometimes cell is painted before it stops to fill with data and then painting doesn't work).
Thanks in advance.
Edit:
Ok, I've followed the answer given by Nayeem Mansoori and came to this:
Style:
<Style x:Key="CellStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="{TemplateBinding local:DataGridCellThemeProps.Brush}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#B2DCDCDC" />
<Setter Property="BorderBrush" Value="#B2DCDCDC" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="#7FFFFFFF" />
<Setter Property="BorderBrush" Value="#7FFFFFFF" />
</Trigger>
</Style.Triggers>
</Style>
DataGrid column:
<DataGridTextColumn local:DataGridCellThemeProps.Brush="Thistle"
Header="{x:Static props:Resources.Monday}"
Binding="{Binding Monday, Converter={StaticResource ZeroConverter}, Mode=TwoWay}"
CellStyle="{StaticResource CellStyle}" Width="75"/>
Class:
public static class DataGridCellThemeProps {
public static SolidColorBrush GetBrush(DependencyObject obj) {
return (SolidColorBrush)obj.GetValue(BrushProperty);
}
public static void SetBrush(DependencyObject obj, SolidColorBrush value) {
obj.SetValue(BrushProperty, value);
}
public static readonly DependencyProperty BrushProperty =
DependencyProperty.RegisterAttached(
"Brush",
typeof(SolidColorBrush),
typeof(DataGridCellThemeProps),
new FrameworkPropertyMetadata(Brushes.Black));
}
When I run my app, I get exception:
Exception: {"'Set property 'System.Windows.Setter.Value' threw an exception.' Line number '25' and line position '14'."}
Inner Exception: {"Expression type is not a valid Style value."}
I've double checked namespace of the class, it's correct. Any ideas?

Get Valdiation error in Tooltip if there is an error, else show explanatory Tooltip (using TextBox Style)

Update: Found a Solution, see below!
I'm try to implement the following behavior:
There are several TextBoxes in a UserControl. Each TextBox-ToolTip should show a specific string, which is located in a Resource.resx-file.
If the Valdiation of this TextBox returns an error, the returned error string will be shown in the ToolTip.
This should be done using a Style. My current status is, that I can show the specific Validation.Errors AND a default ToolTip, which is the same for every TextBox which uses the Style.
So my Style is :
<Style TargetType="{x:Type TextBox}">
<Setter Property="ToolTip" Value="ExampleToolTip"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
With this Style, i get the behavior described above.
Now I want the Style part
<Style TargetType="{x:Type TextBox}">
<Setter Property="ToolTip" Value="ExampleToolTip"/>
...
</Style>
to be TextBox-Specific.
I tried to write an Attatched Property for my TextBoxes, so that I can define a second string, which should be used as the standard ToolTip.
The Attached Property Code ist shown below:
public class TextBox2 : DependencyObject
{
public static void SetToolTipStandard(TextBox target, string value)
{
target.SetValue(ToolTipStandardProperty, value);
}
public static string GetToolTipStandard(TextBox target)
{
return (string)target.GetValue(ToolTipStandardProperty);
}
public static DependencyProperty ToolTipStandardProperty = DependencyProperty.RegisterAttached(
"ToolTipStandard",
typeof(string),
typeof(TextBox),
new PropertyMetadata());
}
Now I want to set a TextBox2.ToolTipStandard property on my TextBoxes in XAML, and the TextBox-Style should take this Property to set the default ToolTip-Text. I tried several combinations of Bindings but with no success. Is there a way to achieve this behavior?
An idea, you can put a hidden border in the ControlTemplate with a BorderThickness of 0 around the input control which provides the error tooltip. If there is an error you set the visibility to visible and the error tooltip is shown.
It's just an idea, but it may work.
I managed to get the desired behaviour by naming all my TextBoxes and adding multiple Triggers to my TextBox-Style. Code follows:
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<!--Here are the static resource strings for the normal-state ToolTip-->
<Trigger Property="Name" Value="TextBox1">
<Setter Property="ToolTip" Value="{x:Static properties:UIStrings.TextBox1_ToolTip_String}"/>
</Trigger>
<Trigger Property="Name" Value="TextBox2">
<Setter Property="ToolTip" Value="{x:Static properties:UIStrings.TextBox2_ToolTip_String}"/>
</Trigger> ...
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>

DataTrigger doesn't work

I have this in XAML in my style:
<DataTrigger Binding="{Binding Path=ButtonStyle}" Value="CutLeft">
<DataTrigger.Setters>
<Setter Property="CornerRadius" TargetName="border" Value="0 11 11 0" />
<Setter Property="CornerRadius" TargetName="border1" Value="0 10 10 0" />
</DataTrigger.Setters>
</DataTrigger>
And this XAML in my Window where i have the button:
<gui:MyCustomButton ButtonStyle="CutLeft"/>
And in MyCustomButton code:
public enum ButtonStyles {
CutLeft, CutRight, Circular, Normal
}
public partial class MyCustomButton
{
[DefaultValue(ButtonStyles.Normal)]
public ButtonStyles ButtonStyle { get; set; }
}
But it doesn't work! Any tips?
I think there are a couple of problems here.
The first is that you're not raising property change notifications for ButtonStyle. Either change ButtonStyle to a dependency property, or implement INotifyPropertyChanged on MyCustomButton, and raise the PropertyChanged event from the ButtonStyle setter. In this case, the dependency property approach is probably better, because at some point someone is going to want to set ButtonStyle through a Style, and only DPs can be styled.
The second is that your data trigger appears to be looking at the data context, which is probably not the control. (If you look in the Output window, you'll probably be seeing binding errors about not being able to find the 'ButtonStyle' property on some data object.) You can get around this by adding RelativeSource={RelativeSource Self} to your DataTrigger. However, if you change ButtonStyle to a DP as suggested, you should just be able to use a plain old Trigger, which automatically works against the properties of the control being styled:
<Style.Triggers>
<Trigger Property="ButtonStyle" Value="CutLeft">
<Setter Property="CornerRadius" TargetName="border" Value="0 11 11 0" />
</Trigger>
</Style.Triggers>
(Note also that you don't need to specify the Trigger.Setters or DataTrigger.Setters element. The XAML reader will fill that in for you.)

How do I know whether to use a style or override a control template?

This question is inspired by this recent question and other situations I've encountered in my WPF development. How do I know whether it is enough to set a style on a control to override some default behavior vs creating a new control template?
More concretely, in the question above, the author wants to change the look of a ListBoxItem when it is selected. (See code reprinted below). Everything works, except the Background property. How is one supposed to know that they should override the Control Template for this?
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
As to whether to use a style or template Ray provided a great response.
As to how to solve your problem without creating a template, maybe I can help.
The background color is being set by the SystemColors. Using Blend and creating a template you can see the exact xaml.
So if NO TEMPLATES! is a requirement you can always change what that resource is.
Example :
<ListBox>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Yellow" />
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBoxItem>Test 1</ListBoxItem>
<ListBoxItem>Test 2</ListBoxItem>
<ListBoxItem>Test 3</ListBoxItem>
</ListBox>
That will give you the background color for that given ListBox and not screw up anything else in the app.
Styles can be thought of very closely to CSS styles in HTML. If all you want to do is change the basic properties of a control such as Background, Foreground or whatever properties it exposes then a Style is exactly what you need. Styles also allow you to apply triggers so for animations, a style is also sufficient.
If you're finding you want to change the intrinsice behaviours / inner workings on a control then a control template is what you want. For example, if you want to change how a button is laid out by adding some sort of grid behaviour, then using a control template is the way forward.
Unfortunately, for your specific example, you don't know unless you try it. Basically you first try it with a Style....and if that doesn't work for whatever reason, then you write a ControlTemplate. You usually only end up writing ControlTemplates for the reasons Ray mentioned.
My guess is that the trigger you're trying to set has also been hardcoded in the ControlTemplate...which is bad design imo because it prevents the Style from overriding it.
By "Background" I take it to mean the "blue" rectangle that surrounds the ListBoxItem when it is selected?
This is actually the FocusVisualStyle property, which is a style that describes what the item should look like when it is focused. The Control explicitly sets this property (described here), so in order to override it, you will have to redefine the Control Template, making sure to use a default Style setter to set it to {x:Null}.

Categories