To paraphrase Edison, I feel like I’ve discovered a 101 ways not to change the XamDataGrid background colours on individual cells.
So this all started when I wanted to set the background colours of different cells, some based upon row data (i.e. when view model caused the row to not be editable or because the data was externally provided and needed to be highlighted) and some I wanted to change based upon the column (i.e. I had a column which displayed a “reference” value and thus all cells in that column should be coloured differently to the rest of the grid). I went down quite a few routes to solve this and, ofcourse, ultimately it was no where near as difficult as it seemed (at least whilst trying to find a solution). In the process I learned a few different things about how to change the styling/background colours which I think are valid for other styling also.
Hence, I’m going to share what I found as both a reminder to myself and anyone else trying to solve a similar problem.
Setting the scene
All of the styles discussed use the BackgroundConverter which initially looks like this
public class BackgroundConverter : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return Binding.DoNothing;
}
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
We’re only going to be changing the Convert method going forward, so I will only show changes for that in subsequent code blocks.
Now let’s take a look at the sample view model I’m using
public class MyViewModel
{
public double Factor { get; set; }
public string Reference { get; set; }
public int Count { get; set; }
public bool ReadOnly { get; set; }
}
I’ve not bothered including an implementation of INotifyPropertyChanged as this is sufficient to demonstrate the code.
To give us a starting point to demonstrate things, our initial XamDataGrid XAML looks like this
<igDP:XamDataGrid DataSource="{Binding}">
<igDP:XamDataGrid.FieldLayoutSettings>
<igDP:FieldLayoutSettings RecordSelectorLocation="None" AutoGenerateFields="False" />
</igDP:XamDataGrid.FieldLayoutSettings>
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
<igDP:FieldLayout.Fields>
<igDP:Field Name="Factor" Label="Factor">
</igDP:Field>
<igDP:Field Name="Reference" Label="Reference">
</igDP:Field>
<igDP:Field Name="Count" Label="Count">
</igDP:Field>
</igDP:FieldLayout.Fields>
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>
Finally, here’s the code-behind for the Window class constructor which hosts the above XamDataGrid
DataContext = new List<MyViewModel>
{
new MyViewModel
{
Factor = 1.1, Reference = "A", Count = 1
},
new MyViewModel
{
Factor = 2.1, Reference = "B", Count = 2
},
new MyViewModel
{
Factor = 3.1, Reference = "A", Count = 3, ReadOnly = true
}
};
Ultimately what we want to end up with is, the Reference column to have a red background and the row with ReadOnly set to true to have a green background. Just so they stand out *those are definitely not the colours being used in my app).
Let’s see how my attempts to solve this went…
Attempt 1 – The DataRecordCellAreaStyle
So as the name alludes to, this style will ultimately be passed a DataRecordCellArea object.
Spoiler alert: this will not fulfill my requirements, but it’s all about learning so let’s see what we can do with it
If we assume we have the following style
<Style TargetType="{x:Type igDP:DataRecordCellArea}" x:Key="DataRecordCellAreaStyle">
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource BackgroundConverter}}" />
</Style>
We might prefer to use a data trigger instead of the BackgroundConverter to decide which data to apply the background to, but we’re going to go the route of letting the BackgroundConverter make the decisions here.
Our Convert method within the BackgroundConverter now looks like this
var dataRecordCellArea = value as DataRecordCellArea;
if (dataRecordCellArea != null)
{
var vm = dataRecordCellArea.Record.DataItem as MyViewModel;
if (vm != null)
{
if (vm.ReadOnly)
return Brushes.Green;
}
}
return Binding.DoNothing;
Finally for our XamDataGrid XAML we’ve added the style
<igDP:FieldLayoutSettings
RecordSelectorLocation="None"
AutoGenerateFields="False"
DataRecordCellAreaStyle="{StaticResource DataRecordCellAreaStyle}"/>
So we run this code we’ll find that the ReadOnly row is correctly shown with a lovely green background, but unfortunately it’s not (that I can see) possibly to also handle the column background colouring here.
Oh well, onto our next candidate…
Attempt 2 – The DataRecordPresenterStyle
Spoiler alert: As the name suggests, this is similar to the DataRecordCellAreaStyle in that it’s record based, so again is not going to solve my specific requirement
Let’s see what the style looks like
<Style TargetType="{x:Type igDP:DataRecordPresenter}" x:Key="DataRecordPresenterStyle">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource BackgroundConverter}}" />
</Style>
and now the BackgroundConverter Convert method
var dataRecordPresenter = value as DataRecordPresenter;
if (dataRecordPresenter != null)
{
var dataRecord = dataRecordPresenter.Record as DataRecord;
if (dataRecord != null)
{
var vm = dataRecord.DataItem as MyViewModel;
if (vm != null)
{
if (vm.ReadOnly)
return Brushes.Green;
}
}
}
return Binding.DoNothing;
The main difference here is that the Record returned from the DataRecordPresenter is a Record object not a DataRecord (I’m sure there are further differences but I didn’t bother checking them out). Other than that we have the same code as the DataRecordCellAreaStyle implementation.
Now let’s see how we use this in the XamDataGrid XAML
<igDP:FieldLayoutSettings
RecordSelectorLocation="None"
AutoGenerateFields="False"
DataRecordPresenterStyle="{StaticResource DataRecordPresenterStyle}"/>
The solution, CellValuePresenterStyle
The name of this gives us cause for optimism.
Return the FieldLayoutSettings to the following
<igDP:FieldLayoutSettings RecordSelectorLocation="None" AutoGenerateFields="False" />
We now create the style as follows
<Style TargetType="{x:Type igDP:CellValuePresenter}" x:Key="CellValuePresenterStyle">
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource BackgroundConverter}}" />
</Style>
Now to use the CellValuePresenterStyle we need to apply the style to each of our fields, so within the igDP:Field element add the following child elements
<igDP:Field.Settings>
<igDP:FieldSettings
CellValuePresenterStyle="{StaticResource CellValuePresenterStyle}"/>
</igDP:Field.Settings>
So last of all we need to change the BackgroundConverter’s Convert method to the following
var cellValuePresenter = value as CellValuePresenter;
if (cellValuePresenter != null)
{
var dataRecord = cellValuePresenter.Record;
if (dataRecord != null)
{
var vm = dataRecord.DataItem as MyViewModel;
if (vm != null)
{
if (vm.ReadOnly)
return Brushes.Green;
}
}
if (cellValuePresenter.Field.Name == "Reference")
return Brushes.Orange;
}
return Binding.DoNothing;
As can be seen, with the CellValuePresenter we can get at the cell itself and from this it’s easy to get the row/DataRecord and the field/Column.
Summing things up
Woo hoo, it now works. Having now written this post, ofcourse it seems obvious that the first two attempts were doomed to failure, but getting to the solution took a fair bit of time trying out different scenarios. But along the way I learned a fair bit about the XamDataGrid – so I suspect a few more posts will appear on this subject very soon !