Excel accounting style column for WPF DataGrid

For our WPF applications we had a requirement that the WPF DataGrid must be able to show amounts just like the Excel accounting format. In short this means: currency symbol aligned to the left and the amount aligned to the right within a column. Default such column is not available but with some effort it is relative simple to create this kind of a column.

First we create a control which shows an amount like the Excel accounting style.

<Style TargetType="{x:Type local:AccountingCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:AccountingCell}">
                <Grid Margin="2,0,2,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="{x:Static local:DataGridHelper.CurrencySymbol}" VerticalAlignment="Center"></TextBlock>
                    <TextBlock Grid.Column="1" Text="{TemplateBinding Amount}" TextAlignment="Right" VerticalAlignment="Center"></TextBlock>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
public class AccountingCell : Control
{
    static AccountingCell()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(AccountingCell), new FrameworkPropertyMetadata(typeof(AccountingCell)));
    }

    public string Amount
    {
        get { return (string)GetValue(AmountProperty); }
        set { SetValue(AmountProperty, value); }
    }

    public static readonly DependencyProperty AmountProperty =
        DependencyProperty.Register("Amount", typeof(string), typeof(AccountingCell), new UIPropertyMetadata(null));        
}

The AccountingCell control derives from Control and has one extra dependency property of type string which is named Amount. The DataGridHelper class (line 10) is just a helper class which reads the currency symbol from the regional settings (Current culture).

The next step is creating a DataGridAccountingColumn class which derives from the DataGridTextColumn. A dependency property is added for setting the number of decimal digits used for formatting the amount. We override the GenerateElement method which is responsible for creating the elements when the cell is not in edit mode.

public class DataGridAccountingColumn : DataGridTextColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        AccountingCell accountingCell = new AccountingCell();

        Binding newBinding = new Binding(((Binding)Binding).Path.Path);
        
        string decimalPattern = new String('0', DecimalDigits);
        string accountingFormat = "{0:#,##0." + decimalPattern + "}";
        newBinding.StringFormat = accountingFormat;
        // Ensure the current culture passed into bindings is the OS culture.
        // By default, WPF uses en-US as the culture, regardless of the system settings.
        newBinding.ConverterCulture = System.Threading.Thread.CurrentThread.CurrentCulture;

        accountingCell.SetBinding(AccountingCell.AmountProperty, newBinding);

        return accountingCell;
    }
        
    public int DecimalDigits
    {
        get { return (int)GetValue(DecimalDigitsProperty); }
        set { SetValue(DecimalDigitsProperty, value); }
    }

    public static readonly DependencyProperty DecimalDigitsProperty =
        DependencyProperty.Register("DecimalDigits", typeof(int), typeof(DataGridAccountingColumn), new UIPropertyMetadata(2));        

}

In the GenerateElement method we create an AccountingCell object and create a new binding based on the orginal column binding because we cannot change the orginal binding. On the new binding we set the StringFormat property so the amount is properly formatted.

Now we can use the accounting column as follows:

<DataGrid ItemsSource="{Binding }" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <local:DataGridAccountingColumn Header="Accounting style" Binding="{Binding Amount}"></local:DataGridAccountingColumn>
     </DataGrid.Columns>
</DataGrid>

As you can see the result is a column which shows the amount just like the Excel accounting format.

Source code can be downloaded from here.

Advertisements

Alignment of WPF DataGrid header

In the previous post I described how you can wrap the text in the column header of the WPF DataGrid. In this post we are going to extend the style so is also possible to align the header text. The default alignment of the header text is left.

First we create 3 styles, one for each alignment possibility (left, center and right alignment).

<Style x:Key="LeftAlignmentColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource WrappedColumnHeaderStyle}">
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>

<Style x:Key="CenterAlignmentColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource WrappedColumnHeaderStyle}">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>

<Style x:Key="RightAlignmentColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource WrappedColumnHeaderStyle}">
    <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>

In the styles above we use the BasedOn property so the style is based on the WrappedColumnHeaderStyle we created in the previous post. The property HorizontalContentAligment is used for setting the alignment because in the default control template of the DataGridColumnHeader this property is used for aligning the content of the header.

In the column definitions we can set the HeaderStyle to a specific alignment style.

<DataGrid ItemsSource="{Binding }" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Long header text (alignment = left)" HeaderStyle="{StaticResource LeftAlignmentColumnHeaderStyle}" Width="50" Binding="{Binding Name}"/>
        <DataGridTextColumn Header="Long header text (alignment = center)" HeaderStyle="{StaticResource CenterAlignmentColumnHeaderStyle}" Width="50" Binding="{Binding Name}"/>
        <DataGridTextColumn Header="Long header text (alignment = right)" HeaderStyle="{StaticResource RightAlignmentColumnHeaderStyle}" Width="50" Binding="{Binding Name}"/>
     </DataGrid.Columns>
</DataGrid> 

As you can see in the picture below the header text is aligned.

There is one issue we have to fix. For wrapping the text we make use of a TextBlock. This TextBlock is correctly aligned but when the text is wrapped the text lines in the TextBlock are always aligned to the left within the TextBlock. To solve this we extend the WrappedColumnHeaderStyle with some data triggers which set the TextAlignment property of the TextBlock to the correct value.

<Style x:Key="WrappedColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <TextBlock x:Name="TextBlock" TextWrapping="Wrap" Text="{Binding}"></TextBlock>
                <DataTemplate.Triggers>
                     <DataTrigger Binding="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}}" Value="Center">
                        <Setter TargetName="TextBlock" Property="TextAlignment" Value="Center"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}}" Value="Left">
                        <Setter TargetName="TextBlock" Property="TextAlignment" Value="Left"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}}" Value="Right">
                        <Setter TargetName="TextBlock" Property="TextAlignment" Value="Right"/>
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see in the picture below also in case the text is wrapped the alignment for all text lines is correct.

Text wrapping in WPF DataGrid column header

The WPF DataGrid does not support out of the box wrapping of the text in the column headers. With a small style adjustment it’s possible to wrap the text in the header.

First we create a new style for the DataGridColumnHeader class. In this style we only set the property ContentTemplate so the orginal style is not touched. The ContentTemplate is set to a DataTemplate which contains a TextBlock. The TextWrapping property of this TextBlock is set Wrap. This way we have a column header with the header text wrapped.

<Style x:Key="WrappedColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <TextBlock TextWrapping="Wrap" Text="{Binding}"></TextBlock>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

In the columns of the DataGrid we set the HeaderStyle property to the new style.

<DataGrid ItemsSource="{Binding }" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn HeaderStyle="{StaticResource WrappedColumnHeaderStyle}" Header="Long header text" Width="50" Binding="{Binding Name}"/>
    </DataGrid.Columns>
</DataGrid> 

As you can see in the picture below the header text is wrapped when the text is too long for the column width.