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));
}
}
}
}
<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();
}
}
}
<local:DecimalConverter x:Key="DecimalConverter"/>
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 });
<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>
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>
No comments:
Post a Comment