WPF datagrid: converter and StringFormat
.net-3.5, c#, wpf
Solution
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;
}
}
Problem
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.