Tuesday, July 12, 2011

More complex formatting by implementing IMultiValueConverter in WPF 4

Recently on a WPF project I had to display a date and time on a WPF DataGrid column. The date and the time fields were stored in separate columns in the database, and the requirement was to combine the two in a single field when shown in the DataGrid. The date was to be displayed in the dd.MM.yyyy format and the time was stored in the database as an integer indicating seconds starting from midnight. The only option I had at disposal was implementing the IMultiValueConverter interface.

Since it is not a straightforward formatting, as it is the case with dates or with currencies where only the StringFormat class will suffice, here we need to do some additional work to satisfy our requirements.


Example using only the StringFormat to format your DataGrid’s Date and Currency fields:

<DataGridTextColumn Header="My Date" Width="80"
        Binding="{Binding Path=MyDate, StringFormat={}{0:dd-MMM-yyyy}}">
</DataGridTextColumn>

<DataGridTextColumn Header="Total Amount" Width="80"
        Binding="{Binding Path=totalAmount, StringFormat={}{0:C}}">
</DataGridTextColumn>


To accomplish the aforementioned combination of date and seconds into a single date and time field, first we need to declare a class that implements the IMultiValueConverter interface. I called this class DoubleSecondsToDateTimeConverter. See the class’ detatils below:

using System;
using System.Windows;
using System.Windows.Data;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MultiValueConverterDemo
{
    public class DoubleSecondsToDateTimeConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        //**********************************************************************
        public object Convert(object[] values, Type targetType, object parameter,  
                    System.Globalization.CultureInfo culture)
        {
            DateTime dtDate;
            double dblNumOfSeconds;

            if (values[0] != DependencyProperty.UnsetValue)
            {
                dtDate = (DateTime)(values[0]);
            }
            else
            {
                return null;
            }

            if (values[1] != DependencyProperty.UnsetValue)
            {
                dblNumOfSeconds = System.Convert.ToDouble(values[1]);
            }
            else
            {
                return dtDate;
            }

            DateTime dtNew = dtDate.AddSeconds(dblNumOfSeconds);
            return dtNew;
        }

        //****************************************************************************
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
                                    System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}


I am passing two values to the DoubleSecondsToDateTimeConverter.Convert() from XAML, more precisely from the <Binding> child node of <MultiBinding> (shown in bold):

<DataGrid …>
.
.
.
<DataGridTemplateColumn Header="Data" Width="130" IsReadOnly="True">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Text>                                                   
                     <MultiBinding Converter="{StaticResource dstdConverter}" Mode="OneWay"                    
                                   StringFormat="{}{0:dd.MM.yyyy hh:mm:ss}">
                          <Binding Path="DateRecorded" />
                          <Binding Path="TimestampSec" />
                     </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>                                   
</DataGridTemplateColumn>
.
.
.
</DataGrid>

When each row is bound to the DataGrid, the parameter array “object[] values” in the implemented Convert() method looks like this:

-   values[0] holds the DateRecorded value, e.g. 2011-05-12
      values[1] holds the TimestampSec value, e.g. 23732 (seconds since midnight)

The Convert method combines the previously extracted date and seconds by this statement:

DateTime dtNew = dtDate.AddSeconds(dblNumOfSeconds);

And the DateTime value dtNew is returned.

Once this class is complete, it has to be declared as a resource in the Window.Resources collection:

 <Window.Resources>
     <local:DoubleSecondsToDateTimeConverter x:Key="dstdConverter">
     </local:DoubleSecondsToDateTimeConverter>
 </Window.Resources>


And then use it in your DataGrid like this:

<DataGrid x:Name="dgTileData" AutoGenerateColumns="False" Width="Auto" MaxHeight="500"
          VerticalScrollBarVisibility="Auto" GridLinesVisibility="Horizontal"  
          Canvas.Left="2"
          Canvas.Top="10">
    <DataGrid.Columns>                               
        <DataGridTemplateColumn Header="Date & Time Recorded"
                                Width="130" IsReadOnly="True">
             <DataGridTemplateColumn.CellTemplate>
                  <DataTemplate>
                       <TextBlock>
                          <TextBlock.Text>                                                   
                               <MultiBinding Converter="{StaticResource dstdConverter}" 
                                  Mode="OneWay" StringFormat="{}{0:dd.MM.yyyy hh:mm:ss}">
                                  <Binding Path="DateRecorded" />
                                  <Binding Path="TimestampSec" />
                               </MultiBinding>
                           </TextBlock.Text>
                       </TextBlock>
                  </DataTemplate>
             </DataGridTemplateColumn.CellTemplate>                                   
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Bearing To Strike" Width="70" IsReadOnly="True">
             <DataGridTemplateColumn.CellTemplate>
                  <DataTemplate>
                       <TextBlock Text="{Binding BearingToStrike}"
                                  HorizontalAlignment="Right" />
                  </DataTemplate>
             </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>       
        <DataGridTemplateColumn Header="Strike Type" Width="40" IsReadOnly="True">
             <DataGridTemplateColumn.CellTemplate>
                 <DataTemplate>
                      <TextBlock Text="{Binding StrikeType}"
                                 HorizontalAlignment="Center" />
                 </DataTemplate>
             </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Polarity" Width="65" IsReadOnly="True">
             <DataGridTemplateColumn.CellTemplate>
                 <DataTemplate>
                      <TextBlock Text="{Binding StrikePolarity}"
                                 HorizontalAlignment="Center" />
                 </DataTemplate>
             </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        .
        .
        .
        .  
    </DataGrid.Columns>
</DataGrid>


The result looks like this:

No comments:

Post a Comment