I have a datagrid bound to a list of a custom "model" class.
In one of the cell I have 4 rectangles, representing 4 different string properties of my model class.
The rectangles are supposed to be black if the corresponding property is empty, red otherwise.
I achieve this using a DataTrigger on each rectangle and it works fine but i have to write four time the same datatrigger with only the bound path changing. It seems like it could be more efficient.
Is there a clever way to define a DataTrigger once, and use a different bound path for each element (in my case rectangles) it is applied to ?
Thanks.
This is my model class:
class myModel
{
[...]
public string pr1 { get; set; }
public string pr2 { get; set; }
public string pr3 { get; set; }
public string pr4 { get; set; }
[...]
}
This is the datagrid bound to a List<myModel> :
<DataGrid AutoGenerateColumns="False" Height="200" Name="myDg" ItemsSource="{Binding}">
<DataGrid.Columns>
[...]
<DataGridTemplateColumn Header="prs">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Name="spRow" Orientation="Horizontal">
<Rectangle Height="15" Name="rPr1" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr1}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr2" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr2}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr3" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr3}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr4" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr4}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
There are two ways...
Solution 1:
You can generalize the style at the ancestor level and then specialize the model properties in each rectangle.
The way you can do this is by biding specific pr property to Tag of each Rectangle and then use Tag as a generic data trigger source.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox Margin="5" x:Name="MyTextBox1" Text="pr1" />
<TextBox Margin="5" x:Name="MyTextBox2" Text="pr2" />
<TextBox Margin="5" x:Name="MyTextBox3" Text="pr3" />
<TextBox Margin="5" x:Name="MyTextBox4" Text="pr4" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="20"/>
<Setter Property="Stroke" Value="Black"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}"
Value="">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Rectangle Tag="{Binding Text, ElementName=MyTextBox1, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox2, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox3, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox4, Mode=OneWay}" />
</StackPanel>
</Grid>
So in above example when you clear the individual textbox you get a corresponding red rectangle. Notice that we have used Tag through a DataTrigger but we can also use a normal Trigger...
<Trigger Property="Tag" Value="">
<Setter Property="Fill" Value="Red"/>
</Trigger>
Solution 2:
Use ItemsControl and then 4 (or n) items in your model to hold pr1..pr4 values.
EDIT
....
Change you model to include a list of pr objects. I am using SourceFilter class just to hold the two way editable string value that was held by pr'n' properties... You can use any class that holds a string value through a property.
public List<SourceFilter> pr
{
get;
set;
}
Then I load the four properties as four SourceFilter objects...
pr = new List<SourceFilter>();
pr.Add(new SourceFilter("pr1"));
pr.Add(new SourceFilter("pr2"));
pr.Add(new SourceFilter("pr3"));
pr.Add(new SourceFilter("pr4"));
And then I use ItemsControl's ItemPanel, ItemsTemplate, ItemsSource to achieve exactly same effect from Step 1...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding pr, ElementName=MyWindow2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Margin="5"
Text="{Binding Path=Source,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Row="1" ItemsSource="{Binding pr, ElementName=MyWindow2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="20"/>
<Setter Property="Stroke" Value="Black"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source}" Value="">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<Rectangle />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Advantage is here you do not have to specify specific pr1..pr4 bindings anywhere. Plus its extensible (as pr can hold any n values and will generate same number of rectangles automatically).
Yes, you can create a class that derives from DataTrigger and has the unchanging details pre-set:
public class RedBlackDataTrigger : DataTrigger
{
public RedBlackDataTrigger()
{
Value = string.Empty;
Setters.Add(new Setter(Rectangle.StrokeProperty, new SolidColorBrush(Colors.Black)));
}
}
You can use it in XAML and just add the binding:
<Rectangle Height="15" Width="10">
<Rectangle.Style>
<Style TargetType ="Rectangle">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<Your-Namespace:RedBlackDataTrigger Binding="{Binding Path=pr4}" />
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Since you are also duplicating your Style, you can take it a step further and create a derived style class:
public class RectangleStyle : Style
{
public RectangleStyle()
{
TargetType = typeof (Rectangle);
Setters.Add(new Setter(Rectangle.StrokeProperty, new SolidColorBrush(Colors.Red)));
}
public string ColorTrigger
{
set
{
Triggers.Add(new RedBlackDataTrigger {Binding = new Binding(value) });
}
}
}
Now you've reduced things to:
<Rectangle Height="15" Width="10">
<Rectangle.Style>
<Your-Namespace:RectangleStyle ColorTrigger="pr4" />
</Rectangle.Style>
</Rectangle>
You can even take it one step further and create a derived Rectangle class to do it all.
Related
I have a ListView Control in WPF which displays a list of boxes from which the user can select one from the collection. What I'm currently trying to accomplish is to have Selected Item's Background Property changed to a different color for a proper identification.
I found a Code Snippet which partially solves my problem, however, the Background value of the SelectedItem reverts back to its original when I press anywhere within the module.
What I'd like to happen is for the Background value to persist for the SelectedItem regardless if I press on other controls or even if the Window gets unfocused.
Below is my XAML Code Snippet with the Setters and Triggers I currently have in place.
<ListView Grid.Row="2"
ItemsSource="{Binding FakeDispatchBoxCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<md:Card Margin="0,0,0,10"
Cursor="Hand">
<md:Card.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" Value="true">
<Setter Property="md:Card.Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</md:Card.Style>
<Grid Margin="5">
<!--#region Row & Column Definitions -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.60*" />
<ColumnDefinition Width="0.40*" />
</Grid.ColumnDefinitions>
<!--#endregion-->
<Label Grid.Row="0" Grid.Column="0"
Content="{Binding DispatchBoxName}"
FontWeight="ExtraBlack"
FontSize="15"
FontFamily="Baloo Bhai 2" />
<Label Grid.Row="1" Grid.Column="0"
Content="{Binding DispatchBoxType}"
FontWeight="SemiBold"
FontSize="13"
FontFamily="Baloo Bhai 2" />
<Label Grid.Row="0" Grid.Column="1"
Grid.RowSpan="2"
Content="{Binding UnitCount}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
FontWeight="ExtraBlack"
FontSize="20"
FontFamily="Baloo Bhai 2" />
</Grid>
</md:Card>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the Item's Data Template (ItemTemplate property), you don't have any items that can accept keyboard focus. There are only Labels.
And in the ListViewItem Template Trigger (ItemContainerStyle property), you set the IsSelected property dependency on the keyboard focus inside the ListViewItem.
Also, you shouldn't use a ListView if you don't specify columns. In this case it is better to use ListBox.
An example of a correct implementation:
<Window.Resources>
<!--To simplify the example.-->
<CompositeCollection x:Key="source">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
<sys:String>Fourth</sys:String>
</CompositeCollection>
</Window.Resources>
<ListBox ItemsSource="{DynamicResource source}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label x:Name="label" Content="{Binding}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="true">
<Setter TargetName="label" Property="Background" Value="Coral" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I've created a "DarkMode" theme for my application but the IsSelected binding for the ListViewItem is not working. When I select an item/items they highlight as expected (I.e. the MultiTriggers are applying the correct highlight colour), so the ListView obviously thinks IsSelected == True. But the Setter is never called so the property in my model is never updated.
If I remove the x:Key="RemoteComputerItem" style it works fine (I.e. the Setter is called and my model is updated). Removing all of the triggers in that style make no difference (I obviously don't see the items being highlighted, but the Setter is still not called). So that only leaves the following:
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd">
<GridViewRowPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
</ControlTemplate>
I believe the problem lies within the control template above, but I can't figure out how to fix it. Am I missing something? or do I need to use something else in place of the GridViewRowPresenter?
My XAML is below. I haven't included the C# as I think the problem lies here and don't want to make the question too long, but I'm happy to add it if needed.
View:
<ListView x:Name="ADComputers" Margin="0" Padding="0" Grid.Row="1" Grid.Column="2"
ItemsSource="{Binding SelectedItem.Computers, ElementName=ADTree, Mode=TwoWay}"
SelectionMode="Multiple" ItemContainerStyle="{DynamicResource RemoteComputerItem}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource RemoteComputerItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Style.Triggers>
<DataTrigger Binding="{Binding StatusText}" Value="Offline">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="" Width="20">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="Name" Width="120" DisplayMemberBinding="{Binding Name}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Computer Name" util:SortOrderGlyph.IsSorted="False" util:SortOrderGlyph.SortDirection="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="SortCol">
<cal:Parameter Value="Name" />
<cal:Parameter Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<!-- More columns like above, removed to shorten -->
</GridView>
</ListView.View>
</ListView>
Separate Resource Dictionary for DarkMode theme:
<Style x:Key="RemoteComputerItem" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd">
<GridViewRowPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource ControlMouseOverColor}" />
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource SelectedBackgroundColor}" />
</Setter.Value>
</Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource SelectedBackgroundColor}" />
</Setter.Value>
</Setter>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT:
Here is some more code to give you the full picture of the bindings, but as I mentioned above the bindings work perfectly when I remove the control template for the ListViewItem.
As you can see above, the ListView is bound to SelectedItem.Computers of the element ADTree. Here is the XAML for ADTree:
<TreeView Margin="0, -1, 0, 0" Grid.Row="1" Grid.Column="0" x:Name="ADTree" ItemsSource="{Binding ADRoot.Children}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:OUModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The TreeView is bound to ADRoot.Children:
class ADTreeViewModel : OUModel
{
public OUModel ADRoot { get; set; } = new OUModel(null, false);
}
class OUModel : PropertyChangedBase, ITreeViewItemViewModel
{
// TreeView is bound to this property, which holds a collection of more OUModel objects
public BindableCollection<ITreeViewItemViewModel> Children { get; set; } = new BindableCollection<ITreeViewItemViewModel>();
// The ListView's ItemSource is bound to ADTree.SelectedItem.Computers which is this property
public List<RemoteComputer> Computers
{
get { return computers; }
set { computers = value; NotifyOfPropertyChange(); }
}
// PropertyChangedBase is from Caliburn.Micro
class RemoteComputer : PropertyChangedBase
{
// All other properties in this class are being displayed correctly in the ListView, so the DataContext is correct E.g.:
public string Name { get; set; }
private bool isSelected = false;
// The ListViewItem's IsSelected property is bound to this
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
NotifyOfPropertyChange(); // Caliburn.Micro gets the property name itself
// I've also tried adding the following
NotifyOfPropertyChange(() => Parent.Children);
NotifyOfPropertyChange(() => Parent);
}
}
}
Here is the Live Property Explorer for an item which IS selected:
I've got a ListBox as such:
<ListBox Margin="5" ItemsSource="{Binding NetworkAdapters, Mode=OneWay}" SelectedItem="{Binding SelectedNetworkAdapter}" SelectionChanged="{s:Action SelectedNetworkAdapterChanged}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="15" Height="15" Margin="5">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Gray"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Up}">
<Setter Property="Fill" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Down}">
<Setter Property="Fill" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<StackPanel Margin="5,0,0,0">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Text="{Binding Speed, StringFormat='Speed: {0}'}" FontSize="10"/>
<TextBlock Text="{Binding Status}" FontSize="10"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
NetworkAdapters is a collection of View Models that implement INotifyDataErrorInfo.
With the current XAML, if there is an error in any of the View Models the whole ListBox will be highlighted red, but I would like just the single ListBoxItems that contains errors to be highlighted.
I had a look at similar questions such as:
WPF ListBox ErrorTemplate and
Validating a ListBoxItem rather than a ListBox
But I still can't make this work. Any help would be appreciated.
UPDATE:
As per Krzysztof's advice, I tried wrapping the StackPanel around a border and using a DataTrigger as such:
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1">
<Border.Resources>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<StackPanel> ... </StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
However, what this produces is the following:
Which is slightly different from what I expected. I would like to have the highlight around the whole ListBoxItem not just part of it as per the image.
You need to implement an ItemContainerStyle as below:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
This will enable you to change the border of the ListBoxItem itself, so the whole things as you want.
You can forget about ErrorTemplate and just use DataTrigger to bind to Validation.HasErrors:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type StackPanel}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True"> <!-- change all text to red -->
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
</StackPanel>
If you want a highlight, you can wrap StackPanel with a Border and set its color to red in a style.
I have this TabItem:
<TabItem x:Name="tabTFGWReadingMainHall" Header="Main Hall" DataContext="{Binding Meeting}">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsSelected" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
<Border x:Name="borderBibleReadingMain" BorderThickness="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label Content="Student:"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
DataContext="{Binding TFGW.BibleReadingItem.Main}"
Text="{Binding DataContext.BibleReadingMain, ElementName=oclmEditor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="buttonBibleReadingMain" Grid.Column="1" Background="Transparent"
DataContext="{Binding DataContext, ElementName=oclmEditor}"
Command="{Binding ApplicationCommand}" CommandParameter="BibleReadingMain">
<Image Source="Images/AssignmentTypeBibleReading16.png" Margin="2"/>
</Button>
</Grid>
<Label Content="Study:"/>
<ComboBox DataContext="{Binding DataContext, ElementName=oclmEditor}"
ItemsSource="{Binding ReadingStudyPointsList}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Number}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Border>
</TabItem>
As you can see, it has some DataTriggers implemented. Thus, when you change the comboActiveStudentAssignmentType (over on the right edge of the editor):
This is how the controls on the left change:
Before:
After:
The DataTriggers are working right. The Border is set correctly and the TabItem gets selected. But, if possible, I would like to end up with this:
I have done some research on this and it appears that I need to use BringIntoView to get the desired results. But that needs to be called from the code behind. I added a SelectionChanged handler and did this:
private void comboActiveStudentAssignmentType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
OCLMEditorViewModel vm = this.DataContext as OCLMEditorViewModel;
if(borderBibleReadingMain != null &&
vm.ActiveStudentAssignmentType == Data.StudentInfo.Enums.StudentAssignmentType.BibleReadingMain)
borderBibleReadingMain.BringIntoView();
// TODO: Add others here
}
Whilst it did get fired, it did not seem to make any difference. I still had to scroll the reactangle to bring it into view.
How should I be doing this?
Thank you.
I am trying to implement alternating background Color on my Rows.
I have an ItemsControl with an ItemTemplate in which I use Triggers on the Style Property of the Border.
But I end up with all RoyalBlue Rows instead of alternating with Red.
Can somene assist? Thank you very much!
<Page.Resources>
<DataTemplate x:Key="myTemplate" >
<Grid>
<Border BorderThickness="1" CornerRadius="2" Margin="2" VerticalAlignment="Stretch" Height="20" Width="Auto">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="Red" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="RoyalBlue" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{Binding Name}"/>
</Grid >
</Border>
</Grid>
</DataTemplate>
</Page.Resources>
<ScrollViewer >
<StackPanel >
<ItemsControl ItemsSource="{Binding Path=myElements}" ItemTemplate="{StaticResource myTemplate}" AlternationCount="2"/>
</StackPanel>
</ScrollViewer>
ItemsControl.AlternationIndex will be set against direct child of ItemsControl panel (ContentPresenter) so you need to use DataTrigger with RelativeSource binding and because it's an attached property you need to put in brackets like Path=(ItemsControl.AlternationIndex)
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Background" Value="RoyalBlue" />
</DataTrigger>
</Style.Triggers>
</Style>