Problem Statement
I want to have a dynamically generated datagrid that updates cell colors in a 'Grade' column if that column contains a grade of 'D' or 'F'. I want it to adapt to new columns being inserted so that the 'Grade' column is not in a fixed position because my columns are sometimes inserted at runtime and are fully dynamic.
Basic Requirements
- Need to update a cell's background based on the cell's value and location in the grid
- No code-behind is used
- The update should be targeted to only the cells that you care about
Libraries Used
For MVVM in C#, I use Prism. I'm using 6.3 here but nothing I'm using is very new so older versions should work. You can get Prism through nuget...just get Prism.Core.
Note that like usual, this is not a full Prism app and is not taking advantage of everything it provides. This is very targeted to the problem here and is not structured in the way you'd structure a real Prism application.
Since this is a DataGridCell and it's referencing itself, Content.Text returns the text contained in the cell and Column.Header returns the name of the column containing the cell. In the converter itself, we put the logic for converting those two fields into a background:
Preview of Result
Targeting Only the Cells with the Grades
I'll walk through two basic ways to do this:
- using converters
- using datatriggers
I prefer the converter approach and will explain why after showing both. In both cases, I will do it by overriding the Datagrid's CellStyle.
Converters
One way to do this is to pass relevant values to a converter. There are a lot of options and the two most obvious to me in this case are:
- Make a multi-value converter and pass in the content of the cell and the name of its column header
- Use a single-value converter, pass in the cell itself, and parse it in the converter code
I've gone with #1 here.
Our xaml here looks like the following:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Setters>
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource GradeToCellBackgroundConverter}">
<Binding Path="Content.Text" RelativeSource="{RelativeSource Self}" />
<Binding Path="Column.Header" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Setters>
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource GradeToCellBackgroundConverter}">
<Binding Path="Content.Text" RelativeSource="{RelativeSource Self}" />
<Binding Path="Column.Header" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</DataGrid.CellStyle>
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
if(value[0].GetType() == typeof(string))
{
if (((string)value[1] == "Grade") && (((string)value[0] == "D") || ((string)value[0] == "F")))
{
return (SolidColorBrush)(new BrushConverter().ConvertFrom("#EEAAAA")); ;
}
}
return null;
}
{
if(value[0].GetType() == typeof(string))
{
if (((string)value[1] == "Grade") && (((string)value[0] == "D") || ((string)value[0] == "F")))
{
return (SolidColorBrush)(new BrushConverter().ConvertFrom("#EEAAAA")); ;
}
}
return null;
}
Basically...if the field is text, it's value is 'D' or 'F', and the column it belongs to is 'Grade', then return red for the background.
Triggers
You can do something very similar with triggers here. Basically...if column name is 'Grade' and content is 'D' or 'F', set the background to red:
There's again multiple options here but this was the most obvious to me. As I noted, I prefer the converter one because it's easier to debug to me, and is cleaner to me when you have a lot of conditions. There's probably also a performance difference, but it shouldn't really matter either way here with a properly virtualized grid.
Other options
As I mentioned, you could do this with a single-value converter. Another obvious way to do it is bind the background color to a ViewModel property that you create for it, but I dislike that method and won't go into it here.
Summary
Using any of the methods described above, you can meet all requirements listed. If you want to confirm that it adapts to dynamic columns, try un-commenting the 'Garbage' property in the model and see that it colors the values in the 'Grades' column. You can also try removing the 'Grade' property to confirm that no fields have a red background.
You can download the source here
Triggers
You can do something very similar with triggers here. Basically...if column name is 'Grade' and content is 'D' or 'F', set the background to red:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Content.Text, RelativeSource={RelativeSource Self}}" Value="D"/>
<Condition Binding="{Binding Path=Column.Header, RelativeSource={RelativeSource Self}}" Value="Grade" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#EEAAAA" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Content.Text, RelativeSource={RelativeSource Self}}" Value="F"/>
<Condition Binding="{Binding Path=Column.Header, RelativeSource={RelativeSource Self}}" Value="Grade" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#EEAAAA" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Content.Text, RelativeSource={RelativeSource Self}}" Value="D"/>
<Condition Binding="{Binding Path=Column.Header, RelativeSource={RelativeSource Self}}" Value="Grade" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#EEAAAA" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Content.Text, RelativeSource={RelativeSource Self}}" Value="F"/>
<Condition Binding="{Binding Path=Column.Header, RelativeSource={RelativeSource Self}}" Value="Grade" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#EEAAAA" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
There's again multiple options here but this was the most obvious to me. As I noted, I prefer the converter one because it's easier to debug to me, and is cleaner to me when you have a lot of conditions. There's probably also a performance difference, but it shouldn't really matter either way here with a properly virtualized grid.
Other options
As I mentioned, you could do this with a single-value converter. Another obvious way to do it is bind the background color to a ViewModel property that you create for it, but I dislike that method and won't go into it here.
Summary
Using any of the methods described above, you can meet all requirements listed. If you want to confirm that it adapts to dynamic columns, try un-commenting the 'Garbage' property in the model and see that it colors the values in the 'Grades' column. You can also try removing the 'Grade' property to confirm that no fields have a red background.
You can download the source here
I'm rusty with WPF and this was exactly what I was looking for. Thank you!
ReplyDeleteสูตรสล็อต เป็นสูตรที่เหล่าเซียนพนันใช้ในการเล่นสล็อตออนไลน์ เพื่อที่จะกอบโกยกำไรในการเดิมพันได้ง่ายขึ้น ซึ่งเป็น สูตรสล็อตที่ดีที่สุด ทำเงินที่เหล่านักพนันมืออาชีพ และเซียนพนันทั้งหลายต่าง เลือกให้เป็นสูตรนี้ในการทำกำไรให้ทุกท่านกันอย่างแพร่หลาย และเป็นช่องทางในการสานต่อการทำเงินก้อนได้อย่างไม่ยาก ถึงแม้ว่าคุณอาจจะมีทุนไม่เยอะมาก
ReplyDelete