I tried several ways to factor out the format of a cell in a WPF DataGrid (the one provided with .NET 4):
I will describe my attempts as it might help someone else, and I hope someone can give me advice to improve on these solutions. Note that I am fairly new to WPF...
The expected behaviour is that the cell string is formatted to something specific like "1,234,567" for display, but it should be formatted as "1234567" (its default formatting) when editing the cell. When I tried to use a data converter, I did not find a way to use the default formatting when editing, so I focused my energy on styles and templates.
With a style, the definition of the DataGridTextColumn goes like this:
<DataGridTextColumn Header="Price (Style)" SortMemberPath="BILL_PRICE">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Path=BILL_PRICE, StringFormat={}{0:N0}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Path=BILL_PRICE}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
The behaviour is exactly what is expected. However, I cannot factor out this style and use it several times, because of the binding. To solve the factoring problem, I used a DataGridTemplateColumn and data templates. Here is my DataGridTemplateColumn definition:
<DataGridTemplateColumn Header="Price (Template)" SortMemberPath="BILL_PRICE">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding BILL_PRICE}" Template="{StaticResource CurrencyCellControlTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ContentControl Content="{Binding BILL_PRICE, Mode=TwoWay}" Template="{StaticResource CurrencyCellEditingControlTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
And the ControlTemplate definition:
<ControlTemplate x:Key="CurrencyCellControlTemplate" TargetType="ContentControl">
<TextBlock Margin="2,0,2,0" Padding="0" TextAlignment="Right">
<TextBlock.Text>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" StringFormat="{}{0:N0}"/>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
<ControlTemplate x:Key="CurrencyCellEditingControlTemplate" TargetType="ContentControl">
<TextBox Padding="0" BorderThickness="0" TextAlignment="Right">
<TextBox.Text>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content"/>
</TextBox.Text>
</TextBox>
</ControlTemplate>
Using data templates solves the initial problem of factoring out DataGrid cell formatting, but using a control template brings up ergonomic and visual problems. For instance, the double-tab navigation caused by the control template (discussed in many other places), and the look of the editing text box (which I try to fix with border thickness, padding and the other property settings.)
Specific questions related to this issue are :
Make your own custom DataGridTextColumn and creates bindings to assign to the Element and the EditingElement (one of them with a converter one of them without).
The converter formats the string to look like a decimal with the commas.
public class MyDataGridTextColumn : DataGridTextColumn
{
Binding formattedBinding;
Binding unformattedBinding;
FormatConverter formatConverter = new FormatConverter();
protected override FrameworkElement
GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
element.SetBinding(TextBlock.TextProperty, GetFormattedTextBinding());
return element;
}
protected override FrameworkElement
GenerateEditingElement (DataGridCell cell, object dataItem)
{
var element = base.GenerateEditingElement(cell, dataItem) as TextBox;
element.SetBinding(TextBox.TextProperty, GetTextBinding());
return element;
}
Binding
GetTextBinding()
{
var binding = (Binding)Binding;
if (binding == null) return new Binding();
unformattedBinding = new Binding
{
Path = binding.Path,
Mode=BindingMode.TwoWay
};
return unformattedBinding;
}
Binding
GetFormattedTextBinding()
{
var binding = (Binding)Binding;
if (binding == null) return new Binding();
formattedBinding = new Binding
{
Path = binding.Path,
Converter = Formatter,
};
return formattedBinding;
}
public FormatConverter
Formatter
{
get { return formatConverter; }
set { formatConverter = value; }
}
}
public class FormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string textvalue = value as string;
if (!string.IsNullOrEmpty(textvalue))
{
decimal decimalvalue = decimal.Parse(textvalue);
var test = String.Format("{0:0,0.00}", decimalvalue);
return test;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("FormatConverter can only be used OneWay.");
}
}
I didn't understand how the formatting string you posted works so I made a simple one, if you want more decimal places just sort it out in the converter.
All you need to do now is place a reference to the namespace in your xaml and then create a column in the datagrid.
Then all should be well
u_u
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