WPF DataTrigger not setting when bound value changed - c#

I have a data template which defines a line to display for every NodePairViewModel drawn inside by a canvas ItemsPanel. The line colour should change when the IsChecked Property of the NodePairViewModel being represented changes as defined in the style (see below) however when this property is set the colour does not change. INotifyPropertyChanged is implemented and I have verified that this event is firing. I cannot see anything obviously wrong and the same setup works for NodeViewModels which are also drawn in the same itemspanel.
<DataTemplate DataType="{x:Type vm:NodePairViewModel}">
<Line Canvas.ZIndex="2" Name="Arc" Stroke="Blue" StrokeThickness="3"
X1="{Binding Path=NodeA.PositionX, Mode=OneWay, Converter={StaticResource NodeLinkXConverter}}"
X2="{Binding Path=NodeB.PositionX, Mode=OneWay, Converter={StaticResource NodeLinkXConverter}}"
Y1="{Binding Path=NodeA.PositionY, Mode=OneWay, Converter={StaticResource NodeLinkYConverter}}"
Y2="{Binding Path=NodeB.PositionY, Mode=OneWay, Converter={StaticResource NodeLinkYConverter}}"
MouseLeftButtonDown="Arc_MouseLeftButtonDown" MouseLeftButtonUp="Arc_MouseLeftButtonUp">
<Line.Style>
<Style TargetType="Line">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked}" Value="True">
<Setter Property="Stroke" Value="Orange"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter Property="Stroke" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsRejected, Mode=OneWay}" Value="True">
<Setter Property="Stroke" Value="LightGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsVisited}" Value="True">
<Setter Property="Stroke" Value="LightGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
</DataTemplate>
<ItemsControl Name="NodeSpace" ItemsSource="{Binding GraphItems}" Margin="5" MouseMove="NodeSpace_MouseMove" MouseDown="NodeSpace_MouseDown" MouseLeftButtonUp="NodeSpace_MouseLeftButtonUp" MouseLeftButtonDown="NodeSpace_MouseLeftButtonDown" MouseRightButtonDown="NodeSpace_MouseRightButtonDown" RenderTransformOrigin="0.5,0.5" Grid.Column="1" Grid.Row="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Name="GraphSurface" Background="#FFFFFE"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<Binding Mode="OneWay" Path="PositionX" Converter="{StaticResource XInverseConverter}"/>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<Binding Mode="OneWay" Path="PositionY" Converter="{StaticResource YInverseConverter}"/>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
And the IsChecked property itself
private bool ArcIsChecked = false;
public bool IsChecked
{
get
{
return ArcIsChecked;
}
set
{
ArcIsChecked = value;
RaisePropertyChanged("IsChecked");
}
}

You need to move default value from Line
<Line ... Stroke="Blue">
into Style as Setter
<Line
Canvas.ZIndex="2"
Name="Arc"
StrokeThickness="3"
X1="{Binding Path=NodeA.PositionX, Mode=OneWay, Converter={StaticResource NodeLinkXConverter}}"
X2="{Binding Path=NodeB.PositionX, Mode=OneWay, Converter={StaticResource NodeLinkXConverter}}"
Y1="{Binding Path=NodeA.PositionY, Mode=OneWay, Converter={StaticResource NodeLinkYConverter}}"
Y2="{Binding Path=NodeB.PositionY, Mode=OneWay, Converter={StaticResource NodeLinkYConverter}}"
MouseLeftButtonDown="Arc_MouseLeftButtonDown"
MouseLeftButtonUp="Arc_MouseLeftButtonUp">
<Line.Style>
<Style TargetType="{x:Type Line}">
<Setter Property="Stroke" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked}" Value="True">
<Setter Property="Stroke" Value="Orange"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter Property="Stroke" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsRejected, Mode=OneWay}" Value="True">
<Setter Property="Stroke" Value="LightGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsVisited}" Value="True">
<Setter Property="Stroke" Value="LightGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
according to Dependency Property Setting Precedence List style trigger is lower in hierarchy then local value so it won't be able to overwrite your default (local) value

Related

Change the Fill Color of a path displayed in a DataGrid column

I have a DataGrid, that shows a Path in the second column. This path is different based on the value in the bound property.
If I select the row, the Stroke color almost disapears in the background color of the selected row. The color of the text in the other columns changes from black to white in the selected row. But I cannot change the Strokeof the Path.
<DataGrid
x:Name="EpicsDataGrid"
AlternationCount="1"
AutoGenerateColumns="false"
IsReadOnly="true"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn
Width="*"
MinWidth="150"
Binding="{Binding EpicName}"
Header="Epic Name" />
<DataGridTemplateColumn
Width="110"
MinWidth="90"
Header="Recurency">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Path x:Name="CalendarIcon"
Height="40"
Stretch="Uniform"
Stroke="Gray"
StrokeThickness=".5">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger
Binding="{Binding RunName}"
Value="Yearly">
<Setter
Property="Data"
Value="{DynamicResource calendar-recuring}" />
</DataTrigger>
<DataTrigger
Binding="{Binding RunName}"
Value="Incidental">
<Setter
Property="Data"
Value="{DynamicResource calendar}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="120"
MinWidth="50"
Binding="{Binding LastRun}"
Header="Last year added" />
</DataGrid.Columns>
</DataGrid>
I can set font attributes on row select but not Path attributes.
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Wite" />
</Trigger>
</Style.Triggers>
</Style>
You could use another DataTrigger that sets the Stroke color to White if the corresponding row is selected. If the row is selected is given by the IsSelected property of the DataGridRow that you can bind using a RelativeSource with AncestorType. You could alternatively bind the DataGridCell, it makes not difference here.
Please also note that the original Stroke value assignment was moved into the style via a setter, because otherwise the local value would take precedence over the setter in the DataTrigger.
<Path x:Name="CalendarIcon"
Height="40"
Stretch="Uniform"
StrokeThickness=".5">
<Path.Style>
<Style TargetType="Path">
<Setter
Property="Stroke"
Value="Gray"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding RunName}"
Value="Yearly">
<Setter
Property="Data"
Value="{DynamicResource calendar-recuring}" />
</DataTrigger>
<DataTrigger
Binding="{Binding RunName}"
Value="Incidental">
<Setter
Property="Data"
Value="{DynamicResource calendar}" />
</DataTrigger>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Value="True">
<Setter Property="Stroke" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>

Dynamic cell control in one column in WPF DataGrid

I want to create WPF DataGrid with dynamic cell control in one column based on value in another column as below image.
XAML
<Window.Resources>
<Style x:Key="DataGridCellStyle1" TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Height="21.96">
<ComboBox x:Name="cbCondition1">
<ComboBoxItem Content="1"/>
<ComboBoxItem Content="2"/>
</ComboBox>
<TextBox x:Name="tbCondition2" Text="text"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding TypeColumn}" Value="ComboBox">
<Setter Value="Visible" TargetName="cbCondition1" Property="Visibility"/>
<Setter Value="Hidden" TargetName="tbCondition2" Property="Visibility"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeColumn}" Value="TextBox">
<Setter Value="Hidden" TargetName="cbCondition1" Property="Visibility"/>
<Setter Value="Visible" TargetName="tbCondition2" Property="Visibility"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static DataGrid.FocusBorderBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<DataGrid HorizontalAlignment="Left" Width="500" Grid.ColumnSpan="2" VerticalAlignment="Top" x:Name="GridWorkers"
ItemsSource="{Binding Workers}" Grid.Row="1" SelectedItem="{Binding SelectedWorker}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Control Type" Binding="{Binding TypeColumn}" Width="90" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Dynamic Control" CellStyle="{StaticResource DataGridCellStyle1}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
I'm able to achieve this with xaml style, but looking for programmatic way to dynamically add controls in DataGrid cell i.e from C# code both (mvvm approach and code-behind approach) is preferred. Other possible way for solution also appreciated.
You could use a ContentControl with a Style that sets the Content property based on the value of the ControlType source property, e.g.:
<DataGrid ... AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Control Type" Binding="{Binding ControlType}" />
<DataGridTemplateColumn Header="Dynamic Control">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ControlType}" Value="ComboBox">
<Setter Property="Content">
<Setter.Value>
<ComboBox />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="TextBox">
<Setter Property="Content">
<Setter.Value>
<TextBox Text="text...." />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

WPF Set label background to image depending on content

I have the following Xaml code and I have label triggers. I want to have a trigger that will place an image in the background for some content value. How do i do this as a trigger?
<Window.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<Label Content="{Binding }" Width="70" Height="70" HorizontalContentAlignment="Center" x:Name="Background">
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="1">
<Setter TargetName="Background" Property="Background" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="5">
<Setter TargetName="Background" Property="Background" Value="Image"/>
</DataTrigger>
<DataTrigger Binding="{Binding }" Value="9">
<Setter TargetName="Background" Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="7">
<Setter TargetName="Background" Property="Background" Value="blue"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
Simply use ImageBrush as background.
First add the brush in the resources.
Example:
<Window.Resources>
<ImageBrush x:Key="MyImageBrush"
ImageSource="C:\Test.png" />
</Window.Resources>
Then simply use StaticResource to set it in the specific trigger.
<DataTemplate x:Key="DataTemplate_Level2">
<Label Content="{Binding }"
Width="70"
Height="70"
HorizontalContentAlignment="Center"
x:Name="Background">
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}"
Value="7">
<Setter TargetName="Background"
Property="Background"
Value="{StaticResource MyImageBrush}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

How to use the same DataTrigger for multiple DataTemplate?

I want to set a DataTrigger for multiple DataTemplate but I am not sure if there is way to do that?
I have view structured like below
<ItemsControl ItemsSource="{Binding Collection}"
AlternationCount="2"
ItemTemplateSelector="{StaticResource RowSelector}" >
<local:HeaderTemplateSelector x:Key="RowSelector"
NormalTemplate="{StaticResource NormalTemplate}"
ExpanderTemplate1="{StaticResource ExpanderTemplate1}"
ExpanderTemplate2="{StaticResource ExpanderTemplate2}"
ExpanderTemplate3="{StaticResource ExpanderTemplate3}" />
<DataTemplate x:Key="NormalTemplate" ...>
<stackpanel x:Name="main"
<DataTemplate x:Key="ExpanderTemplate1" ...>
<Grid x:Name="main" ..>
<DataTemplate x:Key="ExpanderTemplate2" ...>
<Expander x:Name="main"
<DataTemplate x:Key="ExpanderTemplate3" ...>
<Textblock x:Name="main" ...>
I want this data trigger which provides alternative background for rows, the trigger defined below
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Blue" TargetName="main"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="black" TargetName="main"/>
</Trigger>
</DataTemplate.Triggers>
I can add this to the end of each DataTemplate and it works fine but it mean that I have to duplicate the some code 4 times, is there any way to avoid that? (I know I can use datagrid but it is overkill for this simple structure)
You can try to set the trigger with the style:
<Style TargetType="Panel" x:Key="AlternatelyPainted">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
(code thankfully stolen from this answer) and use it in your templates:
<DataTemplate x:Key="NormalTemplate" ...>
<StackPanel Style="{StaticResource AlternatelyPainted}">
...
</StackPanel>
</DataTemplate>
etc.
Edit: if you want the style to be universal, not Panel-specific, you can try the following trick:
<Style x:Key="AlternatelyPainted">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Panel.Background" Value="Red"/>
<Setter Property="Control.Background" Value="Red"/>
<Setter Property="TextBlock.Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Panel.Background" Value="Blue"/>
<Setter Property="Control.Background" Value="Blue"/>
<Setter Property="TextBlock.Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
(removed the TargetType and added explicit types to the property names). Note that TextBlock doesn't inherit its Background, so we need an additional line for it.

StyleTrigger Setter to an other Element

I have the following xaml-Code:
<Border x:Name="border">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding MessageType}" Value="Error">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Rectangle>
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Style.Triggers>
<DataTrigger Binding="{Binding MessageType}" Value="Error">
<Setter Property="Fill" Value="Icon.ico" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Border>
(MessageType is a Enum)
How can i combine this two StyleTrigger into One? Is it possible?
Not sure what your parent object is, containing the border. But i assume a DataTemplate, other objects should be work similar.
<DataTemplate>
<Border x:Name="border">
<Rectangle x:Name="rect"/>
<Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MessageType}" Value="Error">
<Setter TargetName="border" Property="Background" Value="Red" />
<Setter TargetName="rect" Property="Fill" Value="Icon.ico" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Updated for comments:
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="myTemplate">
<!-- above template -->
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource myTemplate}"/>
</Grid>
</UserControl>

Categories