I've looked quite a bit on how to change the background color of a row but all the examples are based on a value in the row. Is there any other way to color specific row numbers? I have a project where only the third row gets a green background color. Another project requires rows 1 through 5 get an orange background color.
My first thought was to add an Index column and populate it and then set my background row color based on that value. Is that the only way to do this?
<DataGrid x:Name="grdRes" ItemsSource="{Binding Path=DvAirports, Mode=OneWay}"
HeadersVisibility="Column" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow"
CanUserAddRows="False" CanUserDeleteRows="False"
Style="{DynamicResource Esri_DataGrid}" Margin="0,0,0,31">
<DataGrid.Columns>
<DataGridTextColumn Header="Origin" Binding="{Binding Origin}"/>
<DataGridTextColumn Header="Proximity" Binding="{Binding Proximity}"/>
<DataGridTextColumn Header="NAME" Binding="{Binding NAME}"/>
<DataGridTextColumn Header="Range" Binding="{Binding Range, StringFormat=N2}"/>
</DataGrid.Columns>
CodePudding user response:
Ok so I ended up adding an Idx field and then keying off that. I also hide that column since its not needed for the analysis. The grid is CanUserSortColumns="false" so the user cant sort the rows. It works just like I wanted it.
<DataGrid x:Name="grdRes" ItemsSource="{Binding Path=DvAirports, Mode=OneWay}"
HeadersVisibility="Column" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="false"
Style="{DynamicResource Esri_DataGrid}" Margin="0,0,0,31">
<DataGrid.Columns>
<DataGridTextColumn Header="Origin" Binding="{Binding Origin}"/>
<DataGridTextColumn Header="Proximity" Binding="{Binding Proximity}"/>
<DataGridTextColumn Header="NAME" Binding="{Binding NAME}"/>
<DataGridTextColumn Header="Range" Binding="{Binding Range, StringFormat=N2}"/>
<DataGridTextColumn Header="Idx" Binding="{Binding Idx}" Visibility="Hidden"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Idx}" Value="2">
<!--if its the third row (Idx=2) then color green-->
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
CodePudding user response:
General
Highlighting any item should be based on data, not the index of any item within an items control. The reason is simply that there is not association between the indices in the view and the data items that can is preserved. When sorting, filtering, grouping or just any operation that shifts the indices and their corresponsding items like removing or inserting items makes an index in the items control refer to a different item.
Ideally, you would expose a property that represents the special case that you can add a trigger for in order to adapt the visual state. This could as simple as a bool
, like IsSpecial
, or even an int
as data index that has a conveys a meaning. As you already showed, this can be done using a style.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSpecial}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Special Case Dirty
Although not advisible and dirty there is a method to use the display indices for row coloring if you do not need alternation indices in your application. Alternation indices are used to color each n rows differently, where n is the alternation count. For this purpose, an attached property AlternationIndex
is provided. Now, if you set the AlternationCount
to the number of of items, the AlternationIndex
property effectively provides the index in the view. Based on this property, you can then add triggers.
<DataGrid ItemsSource="{Binding YourItems}"
AlternationCount="{Binding YourItems.Count}"
...>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="2">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
You will need a collection that exposes a Count
property like ObservableCollection<T>
, List<T>
or an array that exposes the number of items as Length
. Please be aware that this approach has the same issues as stated above, so it is only an option if the indices and corresponding items always stay the same.
Index Ranges
If you want to check ranges or indices with a special pattern, you can create a converter.
public class IsInRangeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int intValue) || !(parameter is string rangeString))
return Binding.DoNothing;
var range = rangeString.Split(",");
if (range.Length != 2 ||
!int.TryParse(range[0], out var lowerLimit) ||
!int.TryParse(range[1], out var upperLimit) ||
lowerLimit < 0 ||
upperLimit < 0 ||
lowerLimit > upperLimit)
return Binding.DoNothing;
return intValue >= lowerLimit && intValue <= upperLimit;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Then you can use the converter in the DataTrigger
and pass the range as parameter.
<DataGrid ItemsSource="{Binding StringItems}"
AlternationCount="{Binding StringItems.Count}"
...>
<DataGrid.Resources>
<local:IsInRangeConverter x:Key="IsInRangeConverter"/>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding (ItemsControl.AlternationIndex), RelativeSource={RelativeSource Self}, Converter={StaticResource IsInRangeConverter}, ConverterParameter='3,5'}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
If you need to bind the range bounds, you would need to create a multi value converter.