WPF datagrid: converter and StringFormat

I have a standard (WPF toolkit) data grid. Some columns (which are explicitly defined) should appear as percentages. Some columns should be shown in red if the values ​​are below 0. (Two sets of columns do not match). I tried to implement these requirements using 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 corresponding 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 all this is pretty standard, but the problem is that the converter gets a Text value after it has passed through StringFormat , and at that moment it is difficult for it to parse it correctly (since in fact not all columns have the same format). If I StringFormats , the converter works fine and the text is displayed in red. Am I missing something? Is there an easy way around this? The only thing I can think about right now is to move the formatting to another converter, and I'm not sure if this will work.

+7
source share
3 answers

We had a similar situation when we needed another Path property for Binding , but otherwise similar to CellStyle for each DataGridColumn . We solved this with a custom MarkupExtension . In your case, it will look like

 <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 a Style for the DataGridCell depending on the 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; } } 

In addition, if you have other Setters , etc. that you would like to use, they can be included with another parameter in 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 will 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; } } 
+5
source

Specify the 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 change 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; } } 

enter image description here

+2
source

The easiest way I understood is to associate your full element instead of item / content.text only with your converter. Then you can do what you want to do with your cells, with the need to worry about the values ​​of the elements and parameters.

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; } 
+1
source

All Articles