WPF DataGrid structure - changing cells based on value
I have bound an Object to DataGridTextColumn
and would like to reference one of its properties from the corresponding CellStyle. I assumed that each cell of this column would contain an instance MyObject
. However, I can't seem to find an object reference from DataGridCell
(I used a trivial converter to set a breakpoint and have been looking for an object for a long time DataGridCell
).
I am looking for MyObject.IsEnabled property and would like to reference this in the Path-Property marked with ??? in the code below. Any suggestions?
<DataGridTextColumn Binding="{Binding MyObject}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Path="???" Binding="{Binding RelativeSource={RelativeSource Self}, PresentationTraceSources.TraceLevel=High,Converter={StaticResource debugger}}" Value="False">
<!-- some setters -->
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
EDIT:
Since I want to apply this style to all cells in my DataGrid later, it is important to find the object bound to the cell via RelativeSource
instead of adding the hard-coded to MyObject
.
Decision
Thanks to the contribution of antiocol, I was able to find a solution for my case that could possibly be adapted to similar problems.
Since the problem is that we don't have access to Cell values ββor CellModel
from CellStyle
, we use the attached property in DataGridCell
to store the integer CellModel
in there. From there, we can bind any available Property DataGridCell
to any property of ours CellModel
.
code for attached property:
public static class DataGridUtils
{
public static CellModel GetCellModel(DependencyObject obj)
{
return (CellModel)obj.GetValue(CellModelProperty);
}
public static void SetCellModel(DependencyObject obj, CellModel value)
{
obj.SetValue(CellModelProperty, value);
}
public static readonly DependencyProperty CellModelProperty =
DependencyProperty.RegisterAttached("CellModel", typeof(CellModel), typeof(DataGridUtils), new UIPropertyMetadata(null));
We need to set this property in every cell in our DataGrid. I haven't found a good solution for this in XAML, so for now I have installed it in the converter before I get the information. (suggestions for improvement appreciated)
Converter
public class CellToEnabledConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var cell = values[0] as DataGridCell;
DataGridTextColumn column = cell.Column as DataGridTextColumn;
//you should probably check if column is null after casting.
Binding b = column.Binding as Binding;
//Any suggestions on how to create a Binding to the parent object properly?
//I needed this workaround since I bind `MyObject.Value` to the `DataGridTextColumn`,
//but need a reference to `MyObject` here.
Binding b1 = new Binding(b.Path.Path.Split('.')[0]){ Source = cell.DataContext };
cell.SetBinding(DataGridUtils.CellModelProperty, b1);
CellModel c = DataGridUtils.GetCellModel(cell);
return c.IsEnabled;
}
Now we can define a global Style
in XAML and apply it to everything DataGrid
instead of a single column.
<Window.Resources>
<converter:CellToEnabledConverter x:Key="CellToEnabledConverter" />
<Style x:Key="DataGridCellStyle" TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<!--This Converter works only on DataGridTextColumns with this minimal example!-->
<Binding Converter="{StaticResource CellToEnabledConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
</Binding>
</DataTrigger.Binding>
<!--Setters-->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DataGrid CellStyle="{StaticResource DataGridCellStyle}">
<DataGridTextColumn Binding="{Binding MyObject.Value}"/>
</DataGrid>
Since I found several comments on the net that say "styling a cell based on its value is simply not possible with the current one DataGrid
", I hope this workaround helps someone get out.
source to share
I tried another solution for your problem.
<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=., RelativeSource={RelativeSource Self}, Converter={StaticResource DataGridCellToBooleanConverter}, ConverterParameter=IsEnabled}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataGrid CellStyle="{StaticResource DataGridCellStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="MyObject1" Binding="{Binding MyObject1}" />
<DataGridTextColumn Header="MyObject2" Binding="{Binding MyObject2}" />
</DataGrid.Columns>
</DataGrid>
On the other hand, I'm assuming ItemsSource
yours DataGrid
is a collection of objects Element
(or something similar of course)
public class Element
{
public string Name { get; set; }
public MyObject MyObject1 { get; set; }
public MyObject MyObject2 { get; set; }
}
public class MyObject
{
public string Name { get; set; }
public bool IsEnabled { get; set; }
public override string ToString()
{
return Name;
}
}
Finally, the converter:
public class DataGridCellToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string propertyToAppend = parameter as string;
var cell = value as DataGridCell;
var column = cell.Column as DataGridTextColumn;
Binding b = column.Binding as Binding;
Binding b1 = new Binding(string.Join(".", b.Path.Path, propertyToAppend)) { Source = cell.DataContext };
CheckBox dummy = new CheckBox();
dummy.SetBinding(CheckBox.IsCheckedProperty, b1);
return dummy.IsChecked;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The tricky part is using ConverterParameter
to pass the name of the property you want to bind to DataTrigger
.
PS You can use reflection instead of the hacky element CheckBox dummy
, but that works for me.
Hope it helps
source to share
The only thing I can think of is to switch each one DataGridTextColumn
to DataGridTemplateColumn
and inside each CellTemplate
add some ContentControl whose DataContext is bound to a property that goes into that column.
This way you can create a reusable styling for that ContentControl as it DataGridCell
didn't help you: P
<Style x:Key="MyObjectStyle" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- your TextBlock and stuff -->
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}" Value="False">
<!-- some setters -->
</DataTrigger>
</Style.Triggers>
</Style>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding MyObject}"
Style="{StaticResource MyObjectStyle}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
source to share
First, you must correct the syntax of your DataTrigger, something like this:
<DataGridTextColumn Binding="{Binding MyObject}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding MyObject.IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}" Value="False">
<!-- some setters -->
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Notice the Binding DataTrigger property. In this property you need to write Path
in your case IsEnabled
due to the fact that DataContext
in each DataGridCell
will be an instance of an object MyObject
. You don't need to use RelativeSource
because yours Binding
points to MyObject
, as said earlier.
If you want to create this style as a resource, you can do this:
<Style x:Key="cellStyle" TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyObject.IsEnabled, PresentationTraceSources.TraceLevel=High, Converter={StaticResource debugger}}" Value="False">
<!-- some setters -->
</DataTrigger>
</Style.Triggers>
</Style>
source to share