Sunday, September 30, 2018

Styling the StringFormat - Summary

Continuing on from my previous blog on using a static resource to enforce a consistent stringformat across an application, I realized I had not covered the Infragistics SummaryDefinition. Infragistics has over-engineered the Summary Definition StringFormat and made our lives more difficult rather than easier.

The summary definition stringformat does not honor the simple string format I used ie: ###,###,##0.00;(###,###,##0.00). This is a problem and probably qualifies as a bug. If you read their documentation you can see they use a non-standard StringFormat. You can simplify their string format to {0:###,##0.00;(###,##0.00)} but no simpler. So we are forced to define a second format string specifically for summaries. The format required by columns is not compatible with the format required by the column's summary. This is bad design.

In the final project from my previous blog, after </igDP:FieldLayout.Fields> add


<igDP:FieldLayout.SummaryDefinitions>
    <igDP:SummaryDefinition SourceFieldName="Amount" Calculator="Sum" StringFormat="{}{0:###,##0.00;(###,##0.00)}" Position="UseSummaryPositionField"/>
</igDP:FieldLayout.SummaryDefinitions>

When you run the project you will see the XamDataGrid has a footer (you may need to increase the grid height).


Now we need to create a new static resource and use it. Add a static resource called SummaryDecimal to Dictionary1 and change the StringFormat to reference it. Note: I copied the StringFormat exactly from the XAML to the dictionary - this is wrong but do it anyway because it illustrates a common error.

Dictionary1.xaml:
<sys:String x:Key="SummaryDecimal">{}{0:###,##0.00;(###,##0.00)}</sys:String>


MainWindow.xaml
<igDP:SummaryDefinition SourceFieldName="Amount" Calculator="Sum" StringFormat="{StaticResource SummaryDecimal}" Position="UseSummaryPositionField"/>


If you run the project now something weird happens. The footer gets a prefix "Sum = " and the format is ignored. It took me a while to figure out what I did wrong.


In the StringFormat the leading {} is used to tell the XAML parser that the following { does not indicate the start of a markup extension - it 'escapes' the following { character, telling the parser to pass the string format (without the leading {}) to the StringFormat setter directly. This isn't needed when setting a static resource.

What I did above causes the leading {} to be sent to the StringFormat setter. It sees the leading {} and assumes I have not set a format so it uses the default format instead. I don't even think it parses anything beyond the leading {}. Infragistics, in their wisdom, has decided the default StringFormat for a summary should be <type> = <result>. 

Remove the leading {} from the static resource in Dictionary1.

<sys:String x:Key="SummaryDecimal">{0:###,##0.00;(###,##0.00)}</sys:String>

Now it works the way we expect.



Friday, September 28, 2018

Styling the StringFormat

I just got a request from my application designers to change the format of all decimal fields displayed throughout a major application. I would estimate there are close to a thousand labels, textblocks, data grids and XamDataGrids where I display a decimal value. I have to find, modify, and test each one. This will take about a week. My great fear is that two weeks from now they will change their minds and make me do it all over again with a slightly different format string.

So I asked myself "How can I style the format string?". It turns out this is either difficult or impossible depending on the control. For controls such as the XAMDataGrid and the Label, where the format is not part of the binding, this can be done although it is not trivial. For controls such as DataGrid and TextBlock, where the StringFormat is part of the binding, this is impossible. You cannot style the StringFormat.

My saving grace is converters. A converter can find a resource and use it to format the decimal. I am presenting a project that uses a variety of techniques to effectively style a string format. I could use converters consistently, but they are less efficient because of the call to TryFindResource. My gut feeling, as an application architect, is to use consistent techniques when available, even when the controls the techniques are applied to are inconsistent.

The format I am implementing has no currency symbol and has thousand separators, nine digits before the decimal place, and two digits after. Negative numbers are displayed in parentheses.

Start by creating a new WPF project in C# called StylingStringFormat.


We will start with the simplest control which is the Label. It has a ContentStringFormat property which is not part of the binding so it can be styled or set to a StaticResource. If you're OK with changing all your TextBlocks to labels this will solve a lot of your problems.


<Window x:Class="StylingStringFormat.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StylingStringFormat"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <ResourceDictionary Source="/Dictionary1.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Row="0" Grid.Column="0" Text="Enter Amount:"/>
        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Amount, UpdateSourceTrigger=PropertyChanged}"/>

        <TextBlock Grid.Row="1" Grid.Column="0" Text="Label"/>
        <Label Grid.Row="1" Grid.Column="1" Content="{Binding Amount}" ContentStringFormat="{StaticResource Decimal}" />
    </Grid>
</Window>

---------------------------------

using System;
using System.ComponentModel;
using System.Windows;

namespace StylingStringFormat
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private Decimal _Amount = 0;
        public Decimal Amount
        {
            get { return _Amount; }
            set
            {
                _Amount = value;
                NotifyPropertyChanged("Amount");
            }
        }

        public MainWindow()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
    }
}

I want all my styles to be defined in a ResourceDictionary that is referenced in my MainWindow. These techniques all work just as well if you reference the ResourceDictionary in the Application. Here's the Resource Dictionary which is named Dictionary1.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:StylingStringFormat"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <sys:String x:Key="Decimal">###,###,##0.00;(###,###,##0.00)</sys:String>
</ResourceDictionary>

If you run the application as it stands you will see that entering a decimal causes the label to display the decimal in the format described above. Try modifying the format string in the resource dictionary.



Now we need to tackle the TextBlock. This needs a converter because you cannot style the StringFormat (this would require an enhancement to the Text Dependency Property and Microsoft has not see fit to do this).

Add another row to the Grid and put the following controls in that row.

<TextBlock Grid.Row="2" Grid.Column="0" Text="TextBlock"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Amount, Converter={StaticResource DecimalConverter}, ConverterParameter='Decimal'}"/>

Add a new class called "Converter" to the project and put this code in it.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;


namespace StylingStringFormat
{
    public class DecimalConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            String StringFormat;
            StringFormat = (String)Application.Current.MainWindow.FindResource((String)parameter);
            return Decimal.Parse(value.ToString()).ToString(StringFormat);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

}

Lastly add an entry in Dictionary1 to create a StaticResource for the converter.
    <local:DecimalConverter x:Key="DecimalConverter"/>

I have chosen to pass the resource name into the converter. You could also hard-code it which would be fine. If you run the application now you will see both the label and text block are using the same resource to control their format.


Now let's consider a DataGrid. We need to enhance our code behind to define an ObservableCollection to bind it to. Note the technique I use to populate the collection is ugly, but this is a post about formatting - not collections.

Add a class called cAmount and a collection of cAmount called Amounts. We will modify the collection in the Amount setter. 

Add this using: using System.Collections.ObjectModel;

Add this inside the MainWindow class.

        public class cAmount
        {
            public Decimal Amount { get; set; }
        }

        private ObservableCollection<cAmount> _Amounts;
        public ObservableCollection<cAmount> Amounts
        {
            get { return _Amounts; }
            set
            {
                _Amounts = value;
                NotifyPropertyChanged("Amounts");
            }
        }

and add this to the Amount property's setter (told you this was ugly).

                if (Amounts == null) Amounts = new ObservableCollection<cAmount>();
                Amounts.Clear();
                Amounts.Add(new cAmount() { Amount = Amount });

Now we have a usable data source, add another row to the Grid and add this DataGrid to the new row. The Amount column will use the same converter we created for the TextBlock.

<TextBlock Grid.Row="3" Grid.Column="0" Text="DataGrid"/>
<DataGrid Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Amounts}" AutoGenerateColumns="False" IsReadOnly="true">
   <DataGrid.Columns>
       <DataGridTextColumn Header="Amount" Binding="{Binding Amount, Converter={StaticResource DecimalConverter}, ConverterParameter='Decimal'}" Width="100"/>
   </DataGrid.Columns>
</DataGrid>

Run the application now and you will see the DataGrid's Amount column uses the same format string as the other controls.


The last control I have to deal with is the XamDataGrid. The NumericField can be styled through the EditorStyle property or we can assign a StaticResource to the Format property. We will do the later.

Add references to the XamDataGrid to our project.


Now add the Infragistics namespace to the XAML, add a row to the Grid and add a XAMDataGrid into the new row. It will bind to the same datasource as the DataGrid above.

        xmlns:igDP="http://infragistics.com/DataPresenter"

        <TextBlock Grid.Row="4" Grid.Column="0" Text="XamDataGrid"/>
        <igDP:XamDataGrid Grid.Row="4" Grid.Column="1" DataSource="{Binding Amounts}" GroupByAreaLocation="None" Height="100">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings AutoGenerateFields="False"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldSettings>
                <igDP:FieldSettings AllowSummaries="False" AllowEdit="False"/>
            </igDP:XamDataGrid.FieldSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout>
                    <igDP:FieldLayout.Fields>
                        <igDP:NumericField Label="Amount" Name="Amount" Width="100" Format="{StaticResource Decimal}"/>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>

The result looks like this.


So to summarize, the Label and XamDataGrid.NumericField can have their formats directly assigned to a StaticResource but the TextBlock and DataGrid have to get to the StaticResource via a converter.