Friday, January 30, 2009

WPF data templates

WPF Data Templates.

While displaying data in data grid or ListBox, we may need to display various types of data. For example we may need to display dates in ‘MM/DD/YYYY’ format, float or double values in currency format ($560.00) or in some other format and all numeric data should be right aligned.

In our application we used Xceed data grid control. We can bind data by converting all fields in to string format. Initially what I did is, created a data table dynamically from data .All columns of the data table are of data type string. I used this data table as the Item source to the data grid control. It worked properly but the mistake I did was, I converted all numeric and date fields into string so that sorting on the date and numeric fields is not working. As I defined all the columns of the data table as string data type, the Data grid using string comparison instead of date comparison .

So my headache started …at last I came up with a beautiful and simple solution for the problem .Using Data Template we can solve this problem. While creating data table, I defined various columns with various data types corresponding to the binding data, so that the data table contains various data types of columns. Then sorting working properly but data is not formatted. With data template I solved this problem. Here is the sample code.

[ValueConversion(typeof(double), typeof(string))]

public class CurrencyConverter : IValueConverter

{

public object Convert(object value, Type targetType,object parameter, CultureInfo culture)

{

if ((value != null) && (!object.Equals(string.Empty, value)))

{

try

{

// Convert the string value provided by an editor to a double before formatting.

double tempDouble = System.Convert.ToDouble(value, CultureInfo.CurrentCulture);

return string.Format(CultureInfo.CurrentCulture, "{0:C}", tempDouble);

}

catch (FormatException)

{

}

catch (OverflowException)

{

}

}

return string.Format(CultureInfo.CurrentCulture, "{0}", value);

}

public object ConvertBack(object value, Type targetType,object parameter, CultureInfo culture)

{

return double.Parse(value as string, NumberStyles.Currency,CultureInfo.CurrentCulture);

}

}

[ValueConversion(typeof(DateTime), typeof(string))]

public class DateConverter : IValueConverter

{

public object Convert(object value, Type targetType,object parameter, CultureInfo culture)

{

if ((value != null) && (!object.Equals(string.Empty, value)))

{

try

{

DateTime dateTime = System.Convert.ToDateTime(value);

if (dateTime == DateTime.MinValue)

return String.Empty;

return dateTime.ToString("MM/dd/yyyy");

}

catch (FormatException)

{

}

catch (OverflowException)

{

}

}

return string.Format(CultureInfo.CurrentCulture, "{0}", value);

}

public object ConvertBack(object value, Type targetType,object parameter, CultureInfo culture)

{

return DateTime.Parse(value as string);

}

}

The data binding infrastructure of the WPF is extremely flexible. One of the major contributors to that flexibility is the fact that a custom value converter can be injected between two bound objects (i.e. the data source and target). A value converter can be thought of as a black box into which a value is passed, and another value is emitted.

A value converter is any object which implements the IValueConverter interface. That interface exposes two methods: Convert and ConvertBack. Convert is called when a bound value is being passed from the data source to the target, and ConvertBack is called for the inverse operation. If a value converter decides that it cannot return a meaningful output value based on the input value, it can return Binding.DoNothing, which will inform the data binding engine to not push the output value to the binding operation’s respective target.

Above I defined two Converter classes one for currency and the other for date conversion.

public static void SetDataTemplatesForGridColumns(DataTable dataTable, DataGridControl dataGrid)

{

Binding itemsBinding;

FrameworkElementFactory textBlockFactory;

foreach (DataColumn column in dataTable.Columns)

{

if (column.DataType == typeof(double) || column.DataType == typeof(Int32))

{

if (column.ColumnName == "Amount" || column.ColumnName == "LocalAmount")

{

itemsBinding = new Binding();

CurrencyConverter converter = new CurrencyConverter();

itemsBinding.Converter = converter;

//itemsBinding.ConverterParameter = c.DisplayFormat;

itemsBinding.ConverterCulture = CultureInfo.CreateSpecificCulture("en-US");

textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));

textBlockFactory.Name = "myTextBlockFactory" + column.ColumnName;

textBlockFactory.SetBinding(TextBlock.TextProperty, itemsBinding);

textBlockFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right);

textBlockFactory.SetValue(TextBlock.MarginProperty, new Thickness(0, 0, 5, 0));

dataGrid.Columns[column.ColumnName].CellContentTemplate = new DataTemplate();

dataGrid.Columns[column.ColumnName].CellContentTemplate.VisualTree = textBlockFactory;

}

else

{

itemsBinding = new Binding();

textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));

textBlockFactory.Name = "myTextBlockFactory" + column.ColumnName;

textBlockFactory.SetBinding(TextBlock.TextProperty, itemsBinding);

textBlockFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right);

textBlockFactory.SetValue(TextBlock.MarginProperty, new Thickness(0,0,5,0));

dataGrid.Columns[column.ColumnName].CellContentTemplate = new DataTemplate();

dataGrid.Columns[column.ColumnName].CellContentTemplate.VisualTree = textBlockFactory;

}

}

if(column.DataType == typeof(DateTime))

{

itemsBinding = new Binding();

DateConverter dateConverter = new DateConverter();

itemsBinding.Converter = dateConverter;

itemsBinding.ConverterCulture = CultureInfo.CreateSpecificCulture("en-US");

textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));

textBlockFactory.Name = "myTextBlockFactory1" + column.ColumnName;

textBlockFactory.SetBinding(TextBlock.TextProperty, itemsBinding);

textBlockFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Left);

dataGrid.Columns[column.ColumnName].CellContentTemplate = new DataTemplate();

dataGrid.Columns[column.ColumnName].CellContentTemplate.VisualTree = textBlockFactory;

}

}

}

itemsBinding = new Binding();

CurrencyConverter converter = new CurrencyConverter();

itemsBinding.Converter = converter;

itemsBinding.ConverterCulture = CultureInfo.CreateSpecificCulture("en-US");

the above lines creates a Binding object and sets its Converter property.Here itemsBinding.ConverterCulture set to ‘en-US’ (English USA) to display data in currency format(245.89378 as $245.89). The .NET format strings are useful if you want to convert one of the standard .NET Framework data types to a string that represents that type in some other format. For example, if you have an integer value of 100 that you want to represent to the user as a currency value, you could easily use the ToString method and the currency-format string ("C") to produce a string of "$100.00". Computers that do not have English (United States) specified as the current culture will display whatever currency notation is used by the current culture. The original value contained in the data type is not converted, but a new string is returned that represents the resulting value. This new string cannot be used for calculation until it is converted back to a .NET base data type. The original value, however, can be calculated at any time.

In the following code example, the ToString method displays the value of 100 as a currency-formatted string in the console's output window.

double Amount=100;

string formatedAmount=Amount.ToString("C”);

Console.WriteLine(formatedAmount);

This code displays $100.00 to the console on computers that have English (United States) as the current culture.

In CurrencyConverter class, the line

return string.Format(CultureInfo.CurrentCulture, "{0:C}", tempDouble);

will do the same.

FrameworkElementFactory class is a deprecated way to programmatically create templates, which are subclasses of FrameworkTemplate such as ControlTemplate or DataTemplate;

I created a FrameworkElementFactory object of type TextBlock.Then set its horizontal alignment and margin properties.

dataGrid.Columns[column.ColumnName].CellContentTemplate = new DataTemplate();

dataGrid.Columns[column.ColumnName].CellContentTemplate.VisualTree = textBlockFactory;

the above two lines creates a DataTemplate for DataGrid CellContentTemplate ,set its VisualTree to the FrameworkElementFactory object textBlockFactory. DateConverter also do the same as CurrencyConverter but it displays the data in the given date format,here the format given is "MM/dd/yyyy".

1 comment:

Anonymous said...

Tks! It helped me a lot! =)
Simple and clear!