I have a standard (WPF toolkit) data grid. Some of the columns (which are explicitly defined) have to be shown as percentages. Some columns have to be shown in red if the values are below 0. (The two sets of columns are not the same). I tried to implement these requirements using a StringFormat
and Style
, respectively. My XAML:
<Window xmlns:local="clr-namespace:myNamespace"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit">
<Window.Resources>
<local:ValueConverter x:Key="valueToForeground" />
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Foreground"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource valueToForeground}}" />
</Style>
</Window.Resources>
<Grid>
<tk:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Path=myClass/myProperty}">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="A"
Binding="{Binding colA}" />
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}" />
<tk:DataGridTextColumn Header="C"
Binding="{Binding colC, StringFormat=\{0:P\}}" />
<tk:DataGridTextColumn Header="D"
Binding="{Binding colD, StringFormat=\{0:P\}}" />
</tk:DataGrid.Columns>
</tk:DataGrid>
</Grid>
</Window>
And the relevant converter:
namespace myNamespace
{
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Black);
Double doubleValue = 0.0;
if (value != null)
{
if (Double.TryParse(value.ToString(), out doubleValue))
{
if (doubleValue < 0)
brush = new SolidColorBrush(Colors.Red);
}
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I think it's all pretty standard, but the problem is that the converter gets the Text
value after it's gone through the StringFormat
, and at that point it's difficult to parse it correctly (since in reality, not all columns have the same format). If I take out the StringFormats
, the converter works fine and the text shows up in red. Am I missing something obvious? Is there an easy way to work around this? The only thing that I can think of right now is moving the formatting into a different converter, and I'm not convinced that would work.
We had a similar situation where we needed a different Path
Property for the Binding
but otherwise a similar CellStyle
for each DataGridColumn
. We solved this with a custom MarkupExtension
. In your case it would look like this
<tk:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding MyItems}">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="A"
Binding="{Binding colA}" />
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colB}"/>
<tk:DataGridTextColumn Header="C"
Binding="{Binding colC, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colC}"/>
<tk:DataGridTextColumn Header="D"
Binding="{Binding colD, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colD}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
and then ForegroundCellStyleExtension
creates the Style
for DataGridCell
depending on PropertyName
ForegroundCellStyleExtension
public class ForegroundCellStyleExtension : MarkupExtension
{
public ForegroundCellStyleExtension() { }
public ForegroundCellStyleExtension(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
DependencyObject targetObject = service.TargetObject as DependencyObject;
if (targetObject == null)
{
return null;
}
Binding foregroundBinding = new Binding
{
Path = new PropertyPath(PropertyName),
Converter = new ValueConverter()
};
Style foregroundCellStyle = new Style(typeof(DataGridCell));
foregroundCellStyle.Setters.Add(new Setter(DataGridCell.ForegroundProperty, foregroundBinding));
return foregroundCellStyle;
}
}
Also, if you have some other Setters
etc. that you would like to use then they can be included by another parameter to the MarkupExtension
.
<Window.Resources>
<Style x:Key="dataGridCellStyle" TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Background" Value="Blue"/>
</Style>
</Window.Resources>
<!-- ... -->
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle colB, {StaticResource dataGridCellStyle}}"/>
And ForegroundCellStyleExtension
would then use the second parameter as BasedOn
for the DataGridCell
Style
ForegroundCellStyleExtension with BasedOn
public class ForegroundCellStyleExtension : MarkupExtension
{
public ForegroundCellStyleExtension() { }
public ForegroundCellStyleExtension(string propertyName, Style basedOnCellStyle)
{
PropertyName = propertyName;
BasedOnCellStyle = basedOnCellStyle;
}
public string PropertyName
{
get;
set;
}
public Style BasedOnCellStyle
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
DependencyObject targetObject = service.TargetObject as DependencyObject;
if (targetObject == null)
{
return null;
}
Binding foregroundBinding = new Binding
{
Path = new PropertyPath(PropertyName),
Converter = new ValueConverter()
};
Style foregroundCellStyle = new Style(typeof(DataGridCell), BasedOnCellStyle);
foregroundCellStyle.Setters.Add(new Setter(DataGridCell.ForegroundProperty, foregroundBinding));
return foregroundCellStyle;
}
}
Specify a cell style for each column as follows:
<DataGridTextColumn Header="ColA" Binding="{Binding colA, StringFormat=\{0:P\}}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Foreground"
Value="{Binding colA, Converter={StaticResource valueToForeground}}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="ColB" Binding="{Binding colB, StringFormat=\{0:P\}}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Foreground"
Value="{Binding colB, Converter={StaticResource valueToForeground}}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
...
and modify your converter
public class ValueConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double) value < 0) ? Brushes.Red : Brushes.Black;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
The simplest way I figured out is to bind your full item instead of the item/content.text only to your converter. Then you will be able to do what you wanted to do with your cells with need to worry about the item and parameter values.
In your Cell Style:
<Setter Property="Foreground"
Value="{Binding Converter={StaticResource valueToForeground}}" />
and in your Converter code:
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Black);
Double doubleValue = 0.0;
if (value != null)
{
mydatatype data = value as mydatatype;
//your logic goes here and also can play here with your dataitem.
if (Double.TryParse(data.CollD.ToString(), out doubleValue))
{
if (doubleValue < 0)
brush = new SolidColorBrush(Colors.Red);
}
}
return brush;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With