WPF binding to parent content in datatrigger - wpf

I have a custom control with a style. The control is bound to a property in my viewmodel
<controls:PromoAlarmBox Content="{Binding Controller.IOGRP1W.Value}"/>
I want to create a datatrigger which changes the color of the control depending on the bound value and this works
<Style TargetType="{x:Type local:PromoAlarmBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Rectangle x:Name="PART_rectangle" VerticalAlignment="Stretch" Fill="Yellow" Stroke="Black" Height="20" Width="20"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Controller.IOGRP1W.Value, UpdateSourceTrigger=PropertyChanged}" Value="1">
<Setter Property="Fill" TargetName="PART_rectangle" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So far so good. I can make it work if I refer to the same property in the datatrigger as the control is bound to. My problem is that I have multiple instances of the same control bound to different values and I don't want to create a new style for each one of them so my question is how can I bind to the bound value of the control in the datatrigger.

Found It. Love WPF but it seems that you can spend weeks just studying different binding expressions
<DataTrigger Binding="{Binding Path=Content, RelativeSource={RelativeSource Mode=Self}}" Value="1">

Maybe you could try this:
<ControlTemplate.Triggers>
<Trigger Property="Content" Value="1">
<Setter Property="Fill" TargetName="PART_rectangle" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
This way the Trigger should bind to the Content property of the PromoAlarmBox.

You should use PriorityBinding to achieve the target behavior

Related

In WPF style how to correct bind parent checkbox property to a child?

I'm new to WPF styling and I cannot make next thing to work:
I've got a custom checkbox style with another checkbox in it. I need parent "IsChecked" value to change as "contolCheckBox" child in ControlTemplate changes.
Tryed different triggers but can't get to parent property.
For now I've got next xaml code:
<Style x:Key="CustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="27" />
<Setter Property="Foreground" Value="#FFBABAC7" />
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<CheckBox x:Name="contolCheckBox" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsChecked" Value="{Binding IsChecked, ElementName=contolCheckBox}"/>
<!---->
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Foreground" Value="#FF29E200"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Foreground" Value="Firebrick"/>
</Trigger>
</Style.Triggers>
</Style>
"IsChecked" value of control doesn`t changes.
What am I doing wrong?
Binding ElementName=contolCheckBox, Path=IsChecked
not working too.
I don't think controlCheckBox is within the name scope since it's declared within the ControlTemplate. So your binding isn't working. Instead, invert your binding and bind from the bottom up. What you're looking for in a sitation like this is template binding. Try this in yourControlTemplate ..
<CheckBox x:Name="contolCheckBox" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked, Mode=TwoWay}" />
Note the RelativeSoucebinding. This indicates that I want to bind to the parent of the template. This is the common way to bind underlying ControlTemplate controls to parent properties.

WPF - When TextBox inside DateTemplate is focused change datatemplate child property

I have a listview with a Border wrapping a textbox (and other elements not shown in sample code). I want when the textbox is keyboard focused to change a property of the border that wraps it.
<ListView ItemsSource="{Binding activeLists}">
<ListView.ItemTemplate>
<DataTemplate>
<Border x:Name="border">
<TextBox Text="something">
<TextBox.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBox.IsFocused" Value="True">
<Setter TargetName="border" Property="TextBox.Background" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If using inside the trigger TargetName="border" the item is not found.
If possible to do this from XAML only.
When I tried running this, I also got the error "TargetName property cannot be set on a Style Setter". Which indicates that you can't set a property of the Border control inside a style setter for the TextBox control (which doesn't honestly surprise me.)
What you can do instead is set it in the style of the border control itself, using a DataTrigger to bind to the IsFocused property of the textbox:
<Border>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsFocused, ElementName=textBox}" Value="true">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBox Name="textBox" Text="something"/>
</Border>

Binding to Style Target using RelativeSource in Setter.Value

I want to create a Trigger to be applied to all TextBox on Validation.HasError to show the Validation.Error in a custom ToolTip.
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip">
<Setter.Value>
<StackPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource XXX}, Path=(Validation.Error)[0].ErrorContent}"/>
</StackPanel>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
What should I put in the XXX?
My problem is that I don't really understand how RelativeSource works in this context and I can't get the correct code for binding to the TextBox.
I am guessing Self would refer to the TextBlock and FindAncestor x:Type TextBox will fail becuase it will traverse from TextBlock > StackPanel > Setter.Value > Setter > etc.. instead.
How can I refer to the Style Target instead?
Since ToolTip is not part of the visual tree, it's a bit cumbersome to get the behavior you want.
You can use its PlacementTarget property to find the element it is attached to, and set its DataContext to that element. In your case that will be a TextBox.
Now you can bind directly to the Validation.Errors property, and it will find the validation errors on a given TextBox.
You can use the following code to get it working:
<Window.Resources>
<ToolTip x:Key="errorTooltip"
DataContext="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent}" />
</StackPanel>
</ToolTip>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip" Value="{StaticResource errorTooltip}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>

TextBox trigger to clear Text using a style

First, let me say I've been working with WPF for about a week. I want to style a TextBox so that when it is disable, it is cleared. This article explained how to do it, however I'm confused on how to set the generic style as a resource so that every TextBox can bind to a different property without repeating the style for each TextBox.
<Window.Resources>
<Style TargetType="{x:Type TextBox}" x:Key="style1">
<Setter Property="Text" Value="{What do I really put here?}" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Text" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
....
<TextBox Style="{StaticResource style1}" Text="{Binding SomeProperty}"/>
Thanks!
You won't be able to use the Text property like that. Setting the Text property explicitly on any TextBox that has that style will override the Text setter in the trigger (like you noticed).
If you only need the TextBox to be cleared and not the property it is binding to, then a workaround is to use an attached property (or Tag) for the text which you bind Text to in the Style.
Example..
<Style TargetType="{x:Type TextBox}" x:Key="style1">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Self},
Path=Tag}"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Text" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
Then a TextBox can use this Style like
<TextBox Style="{StaticResource style1}" Tag="{Binding SomeProperty}" />

Styling DataGridCell correctly

This is a question following my previous problem, you can find it right there
So. Now I defined a DataGrid with a specific ElementStyle for each column (which just defines the TextBlocks inside in bold & white -- will come over this problem later)
So now I have two questions
First question (solved)
When I happen to set a background to my cell, it overrides the default style, and the background stays the same when the cell is highlighted.
One example of a style:
<!-- Green template for market-related -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
I'd naturally say that this is "normal" because I set the Grid's background to Green. I therefore tried it this way:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF3774FF" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
This won't work either. As you can see I put a DebugConverter so I can check that the trigger is actually called, which is the case, but... Background does not change (and Snoop confirms this...)
Third try:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Background" Value="LightGreen" />
</Style>
</ControlTemplate.Resources>
<Grid>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
And... No background will be displayed (stays transparent)
So I think I am working in the wrong way here and I was wondering what should I do to JUST define the "not selected" template.
I would say that I may need to define a style BasedOn the "classic" style but, how would I do that? I tried to add TemplateBindings with no success
** EDIT: Solution**
As H B suggested in his answer, problem was coming from DependencyProperty Precedence, here's the solution:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF316AC5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="False">
<Setter Property="Grid.Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
Second question
Now, let's speak Triggers.
Basically, what I want to do is to define specific Triggers to my ElementStyle so the font color is white if the cell's background is Red or Green (the only aim of this is to have a better readability as Red and Green are kinda dark, black font on dark background results in a nice fail :p )
Edit Seems like I'm not clear enough: the following style is the style applied to each item of the datagrid, through the property DataGridTextColumn.ElementStyle. Here is the code handling that:
void VolatilityDataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridTextColumn column = e.Column as DataGridTextColumn;
column.ElementStyle = s_boldCellStyle;
// Other stuff here...
}
Here is what I do:
<!-- Cell style for colored matrix-->
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}}"
Value="Red">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}"
Value="Green">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
And... It doesn't work. Strangely, what goes through converter is ONLY transparent background colors. I am definitely missing something here!
BTW, I also tried with classic triggers, no success either, I use DataTriggers here so I can debug the binding values!
Now I've been stuck for more than three days on this and I'm starting to freak out... Hopefully the Stackoverflow community will save me :)
Thanks!
Edit
Okay, update.
I understood why my Trigger does not work. The Background actually set is on the Grid and NOT on the DataGridCell. It is therefore normal that I don't get any color set there.
However, I ran some tests and found out that when the binding is set, the TextBlock does not have any parent yet (Parent = null). Binding to a RelativeSource of type Grid will bind me to... The whole DataGrid items presenter.
I'm not sure what to do now, since it seems like that from the actual TextBlock style I can't reach the parent Grid and therefore cannot resolve what color should I display according to the background.
Also, I can't change the Font color in my ControlTemplate because the DataGrid wants a Style for each column, which overrides the template's style by default (see my previous question and its answer)
So... Stuck again I am!
Dependency Property Value Precedence
This:
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Needs to be:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="LightGreen"/>
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Not sure about your second question as of now, possibly a related problem, i would suggest setting TextElement.Foreground instead of Foreground for starters. Getting Transparent as value is not very helpful, what control template do you use for the DataGridCell? If it is custom, is the Background hooked up properly via a TemplateBinding?
This works as long as the Background property is used, so if you have a ControlTemplate which sets things internally you need to externalize that. A normal DataGrid example:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Red">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Green">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
So if the CellStyle sets the ControlTemplate the properties need to be hooked up via TemplateBinding. e.g.
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
Do not do the triggering inside the template or it will get messy.

Resources