Reputation: 3
Suppose I have a list of visitor object grouped by Nation
Class visitor
{
String Nation;
String Name
}
Nation | Name |
---|---|
USA | Tom |
Italy | Jim |
China | Josh |
UK | Jane |
UK | Kelly |
S. Korea | Betty |
Japan | Cathy |
France | Steve |
France | Alex |
Cuba | Ken |
I want my datagrid to have alternating color only when the Nation is different with the previous row. In that case, the above table should be colored as Colored Row
DataGrid.RowStyle>DataTrigger is the first suggestion came to my mind. However, it judges from the property of the entry (visitor in my case) itself instead of the relationship between entries.
My current workaround is to have a custom List which would append a flag based on the logic I mentioned above. The DataTrigger then reads the flag and colors each row based on the bool value.
class FlaggedVisitor
{
string Nation;
string Name;
//True for ColorA false for ColorB
bool ColorFlag;
}
Ex: List
USA Tom True
Italy Jim False
China Josh True
UK Jane False
UK Kelly False
S.Korea Betty True
However, I'm curious if I can somehow achieve what I want by XAML or some ValueConverter?
Upvotes: 0
Views: 47
Reputation: 29028
First, your data model FlaggedVisitor
must implement INotifyPropertyChanged
even when the properties are not changing. Every binding source object that does not extend DepenedencyObject
(and therefore does not implement its properties as dependency properties) and that participates in a OneWay
or TwoWay
binding must implement INotifyPropertyChanged
.
Next, group the items. Assuming that you have a collection of type FlaggedVisitor
that binds to the DataGrid
somewhere, you group by providing a related PropertyGroupDescrition
to the underlying ICollectionView
that the DataGrid
implicitly operates on:
MainWindow.cs
class MainWindow : Window
{
public ObservableCollection<FlaggedVisitor> FlaggedVisitors { get; }
public MainWindow()
{
this.FlaggedVisitors = new ObservableCollectionFlaggedVisitor>();
// Create the groups based on the FlaggedVisitor.Nation value
var ICollectionView flaggedVisitorsCollectionView = CollectionViewSource.GetDefaultView(this.FlaggedVisitors);
var nationGroupDescription = new PropertyGroupDescription(nameof(FlaggedVisitor.Nation));
flaggedVisitorsCollectionView.GroupDescriptions.Add(nationGroupDescription);
InitializeComponent();
}
}
Then create a converter that convers the group index to a Brush
:
GroupBackgroundConverter.cs
This simple implementation supports different alternation counts, e.g. to alternate between three colors.
[ContentProperty(nameof(AlternationBrushes))]
public class GroupBackgroundConverter : IValueConverter
{
public List<Brush> AlternationBrushes { get; }
public int AlterantionCount { get; set; }
private readonly Brush defaultAlterantionBrush;
public GroupBackgroundConverter()
{
this.AlternationBrushes = new List<Brush>();
this.AlterantionCount = 2;
this.defaultAlterantionBrush = Brushes.LightSteelBlue;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not GroupItem groupItem)
{
return Binding.DoNothing;
}
int alternationIndex = ItemsControl.GetAlternationIndex(groupItem);
return alternationIndex < this.AlternationBrushes.Count
? this.AlternationBrushes[alternationIndex]
: this.AlternationBrushes.FirstOrDefault() ?? this.defaultAlterantionBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
Finally, configure the DataGrid
to show groups and attach the GroupBackgroundConverter
inside the GroupStyle
:
MainWindow.xaml.cs
<Window x:Name="Root">
<Window.Resources>
<GroupBackgroundConverter x:Key="GroupBackgroundConverter"
AlternationCount="2">
<SolidColorBrush Color="Orange" />
<SolidColorBrush Color="Yellow" />
</GroupBackgroundConverter>
</Window.Resources>
<!--
Set the row background to Transparent so that we can enable
the group's background to take precedence.
-->
<DataGrid ItemsSource="{Binding ElementName=Root, Mode=OneTime}"
RowBackground="Transparent">
<!-- Define a headerless group layout -->
<DataGrid.GroupStyle>
<GroupStyle AlternationCount="2">
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<!--
The Border that actually renders the
background brush based on the GroupBackgroundConverter
-->
<Border Background="{Binding RelativeSource={RelativeSource AncestorType=GroupItem}, Converter={StaticResource
GroupBackgroundConverter}}">
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Window>
Upvotes: 1