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:

Wednesday, July 6, 2011

Using SQL SMO to Backup/Restore your SQL Server 2008 DB in .NET 4.0

             Add References From VS 2010 Project to:

    i) Microsoft.SqlServer.ConnectionInfo.dll

ii) Microsoft.SqlServer.Management.Sdk.Sfc.dll

iii) Microsoft.SqlServer.Smo.dll

iv) Microsoft.SqlServer.SqlEnum.dll

v) Microsoft.SqlServer.SmoExtended.dll



I have SQL Server 2008 Express installed on my machine and the above DLLs are located in:
C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies


    Once the references are added, the last step is to:

Add these using directives in your code

using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;



Example:

Below are screenshots from a Windows Forms 4.0 application that enables users to Backup/Restore a database using SQL Server 2008 SMO.


Backing up the Database


Fig. 1 Initially the app shows the Server, the Database and all other databases installed on this Server



 Fig. 2 To Backup the database (in this case my DB is called ShopOrders) clicking the "Backup..." button shows a Folder Browser Dialog wich enables you to choose the location where you want your DB backed up.



 Fig. 3 The user creates a new folder on the C:\ drive where the .bak file will be saved



Fig. 4 The database backup is complete



Fig. 5 The backed up database file ShopOrders.bak



Restoring the Database



Fig. 6 To restore this DB just select the .bak file from the backup location.


The .cs of the Form shown above:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;

namespace ShopOrders
{
    public partial class AdminDB : Form
    {
        Server server;
        Database database;
        ServerConnection serverConnection;

        public AdminDB()
        {
            InitializeComponent();
        }

        //****************************************************
        private void AdminDB_Load(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Maximized;
            GetServerDbData();
        }

        //****************************
        private void GetServerDbData()
        {
            SqlConnection sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["connStr"].ConnectionString);

            this.serverConnection = new ServerConnection(sqlConnection);
            this.server = new Server(serverConnection);
            this.database = new Database(this.server, "ShopOrders");

            this.lblServerName.Text = this.server.Name;
            this.lblDatabaseName.Text = this.database.Name;

            foreach (Database db in this.server.Databases)
            {
                this.listBoxDatabases.Items.Add(db.Name);
            }
        }

        //******************************************************
        private void btnBackup_Click(object sender, EventArgs e)
        {
            this.folderBrowserDialog.RootFolder = Environment.SpecialFolder.MyComputer;

            if (this.folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    this.Cursor = Cursors.WaitCursor;
                    Backup backup = new Backup();
                    this.serverConnection.Connect();

                    backup.Devices.AddDevice(this.folderBrowserDialog.SelectedPath + @"\" + this.database.Name + ".bak", DeviceType.File);

                    backup.Database = this.database.Name;

                    backup.Action = BackupActionType.Database;

                    backup.Initialize = true;

                    backup.PercentCompleteNotification = 5;

                    backup.PercentComplete += new PercentCompleteEventHandler(bkp_PercentComplete);

                    backup.SqlBackup(this.server);

                    this.serverConnection.Disconnect();

                    backup = null;
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
                finally
                {
                    this.Cursor = Cursors.Default;
                }
            }
        }

        //*************************************************************************
        private void bkp_PercentComplete(object sender, PercentCompleteEventArgs e)
        {
            this.lblPercentageComplete.Text = "Backup Completed " + e.Percent.ToString() + "%";
        }

        //******************************************************
        private void btnRestore_Click(object sender, EventArgs e)
        {
            if (this.openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    this.Cursor = Cursors.WaitCursor;
                    Restore restore = new Restore();
                    this.serverConnection.Connect();

                    restore.Devices.AddDevice(this.openFileDialog.FileName, DeviceType.File);
                    this.server.DetachDatabase("ShopOrders", true);
                    restore.Database = "ShopOrders";

                    restore.Action = RestoreActionType.Database;
                    restore.PercentCompleteNotification = 10;
                    restore.ReplaceDatabase = true;
                    restore.PercentComplete += new PercentCompleteEventHandler(restore_PercentComplete);
                    restore.SqlRestore(this.server);
                    this.serverConnection.Disconnect();
                    restore = null;
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
                finally
                {
                    this.Cursor = Cursors.Default;
                }
            }
        }

        //*************************************************************************
        private void restore_PercentComplete(object sender, PercentCompleteEventArgs e)
        {
            this.lblRestorePercentageComplete.Text = "DB restored " + e.Percent.ToString() + "%";
        }
    }
}


NOTE: The name of the database to be backed up/restored is hard coded in the app. You can easily modify it, to enable the user to select the desired database from the ListBox.