Sunday, January 30, 2011

DataGrid Validation using IDataErrorInfo in MVVM - WPF

In this post I will demo how you can apply validation rules on WPF's DataGrid using IDataErrorInfo interface. This example uses popular MVVM pattern but if you are not using MVVM you can still grasp the main idea about using IDataErrorInfo for validation in WPF. I will create a DataGrid in my application which will have two columns Name and Age. I will apply two validation rules on my DataGrid. 1) Name cannot be empty. 2) Age must be greater than 20. I will notify user about the validation error using tooltip displaying corresponding error and a red border around the cell which contains invalid value. You can download complete ready to run demo from here.

I have defined a class Person which consist of two fields Name and Age. I will bind item source of my DataGrid with collection of Person class to display list of persons in my DataGrid.

   1:      /// <summary>
   2:      /// Derived from Base, Implements IDataErrorInfo
   3:      /// </summary>
   4:      public class Person : Base, IDataErrorInfo
   5:      {
   6:          string m_Name;
   7:          int m_Age;
   8:   
   9:          public Person()
  10:          {
  11:              m_Name = string.Empty;
  12:              m_Age = 0;
  13:          }
  14:   
  15:          public string Name
  16:          {
  17:              get
  18:              {
  19:                  return m_Name;
  20:              }
  21:              
  22:              set
  23:              {
  24:                  if(value != m_Name)
  25:                  {
  26:                      m_Name = value;
  27:                      //notify the binding that my value has been changed
  28:                      OnPropertyChanged("Name");
  29:                  }
  30:              }
  31:          }
  32:   
  33:          public int Age
  34:          {
  35:              get
  36:              {
  37:                  return m_Age;
  38:              }
  39:   
  40:              set
  41:              {
  42:                  if (value != m_Age)
  43:                  {
  44:                      m_Age = value;
  45:                      //notify the binding that my value has been changed
  46:                      OnPropertyChanged("Age");
  47:                  }
  48:              }
  49:          }
  50:   
  51:          public string Error
  52:          {
  53:              get { throw new NotImplementedException(); }
  54:          }
  55:   
  56:          //IDataErrorInfo Property
  57:          public string this[string columnName]
  58:          {
  59:              get 
  60:              { 
  61:                  //this get will be invoked everytime user change the Name or Age filed in Datagird
  62:                  //columnName contains the property name which is modified by user.
  63:                  string error = string.Empty;
  64:                  switch(columnName)
  65:                  {
  66:                      case "Name":
  67:                          //if user changes the name field, I check if the new value is empty or not
  68:                          //if it is empty I set the error message accordingly.
  69:                          if(string.IsNullOrEmpty(m_Name))
  70:                              error = "Name cannot be empty";
  71:                      break;
  72:   
  73:                      case "Age":
  74:                          //if user change the Age, I verify that Age is greater than 20,
  75:                          //if not I set the error message.
  76:                          if(m_Age < 20)
  77:                              error = "Age must be greater than 20";
  78:                      break;
  79:                  }
  80:                  //just return the error or empty string if there is no error
  81:                  return error;
  82:              }
  83:           
  84:          }
  85:      }
Note that Person class has been derived from Base which is implementing INotifyPropertyChanged interface to tell the binding instantly that a proprty.

I am using the Person class created above in MainWindowData which is the main class whose object is set as DataContext of MainWindow i.e. our view. This is done in MainWindow.xaml.cs. MainWindowData has a PersonCollection which is an ObservableCollection of  Person class and this collection will be set as an item source of DataGrid.

I have defined application's view/UI in XAML. It consists of a style for TextBlock and a DataGrid consisting of two template columns, first for Name second for Age. Inside window's resources, I have defined a style for TextBlock type. In this style I am defining that whenever Validation Error occurs, display the error message in a tooltip. I will be using this style in my CellTemplate for both the columns of my DataGrid.

   1:  <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
   2:       <Style.Triggers>
   3:            <Trigger Property="Validation.HasError" Value="true">
   4:                 <Setter Property="ToolTip"
   5:                 Value="{Binding RelativeSource={RelativeSource Self},
   6:                 Path=(Validation.Errors)[0].ErrorContent}"/>
   7:            </Trigger>
   8:       </Style.Triggers>
   9:  </Style>
I have binded ItemSource of my DataGrid with PersonCollection. DataGrid consists of two template columns Name and Age. I have set TextBlock as CellTemplate and TextBox as CellEditingTemplate for both the columns. I have applied the style on CellTemplate which is defined in window resources to display the error in tooltip.

   1:  <DataGrid ItemsSource="{Binding PersonCollection}" AlternatingRowBackground="Wheat" AutoGenerateColumns="False">
   2:              <DataGrid.Columns>
   3:                  <DataGridTemplateColumn  Header="Name" MinWidth="150">
   4:                      <DataGridTemplateColumn.CellTemplate>
   5:                          <DataTemplate>
   6:                              <TextBlock Style="{StaticResource ResourceKey=TextBlockStyle}" Text="{Binding Path=Name,  UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
   7:                          </DataTemplate>
   8:                      </DataGridTemplateColumn.CellTemplate>
   9:   
  10:                      <DataGridTemplateColumn.CellEditingTemplate>
  11:                          <DataTemplate>
  12:                              <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
  13:                          </DataTemplate>
  14:                      </DataGridTemplateColumn.CellEditingTemplate>
  15:                  </DataGridTemplateColumn>
  16:   
  17:                  <DataGridTemplateColumn Header="Age" MinWidth="150">
  18:                      <DataGridTemplateColumn.CellTemplate>
  19:                          <DataTemplate>
  20:                              <TextBlock Style="{StaticResource ResourceKey=TextBlockStyle}" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"/>
  21:                          </DataTemplate>
  22:                 </DataGridTemplateColumn.CellTemplate>
  23:   
  24:                 <DataGridTemplateColumn.CellEditingTemplate>
  25:                      <DataTemplate>
  26:                           <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/>
  27:                      </DataTemplate>
  28:                 </DataGridTemplateColumn.CellEditingTemplate>
  29:            </DataGridTemplateColumn>
  30:       </DataGrid.Columns>  
  31:  </DataGrid>
These were the main parts of complete example. If user enters an empty string in the Name column, he/she will get error like this

You can download the complete ready to run sample from here and play with it.



Sunday, January 23, 2011

Create DataGrid in WPF using code

In this post I will explain a way to create DataGrid in WPF through code. I will use a DataTable with two columns as a DataSource for the DataGrid. I will display one column as TextBox while the second column will be displayed as ComboBox. You can download complete sample from here. My main Window contains single Grid inside which I will display my DataGrid. I will use MainGrid_Initialized event to create DataGrid.


 Inside MainGrid_Initialized event I am creating a DataGrid and adding two columns inside it. One for the TextBox and other for the ComboBox. Then I am setting a DataTable as DataSource for my DataGrid which contains two columns "TextBoxColumn" and "ComboBoxColumn". At the end, I am placing my DataGrid in the Main Window's Grid.
   1:  DataGrid dataGrid = new DataGrid();
   2:  dataGrid.AutoGenerateColumns = false;
   3:   
   4:  dataGrid.Columns.Add(CreateTextBoxColumn());
   5:  dataGrid.Columns.Add(CreateComboBoxColumn());
   6:  //set DataTable as item source of dataGrid
   7:  dataGrid.ItemsSource = GetDataTable().AsDataView();
   8:   
   9:  //place DataGrid inside main Grid
  10:  Grid.SetColumn(dataGrid,0);
  11:  Grid.SetRow(dataGrid,0);
  12:  MainGrid.Children.Add(dataGrid);
In CreateTextBoxColumn method I am defining my TextBox based column. I am creating two templates one for viewing(TextBlock) and other for editing (TextBox). I am also creating bindings between DataTable's "TextBoxColumn" with the TextBlock and the TextBox templates.

   1:  private DataGridTemplateColumn CreateTextBoxColumn()
   2:          {
   3:              //create a template column
   4:              DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
   5:              //set title of column
   6:              templateColumn.Header = "TextBoxColumn";
   7:              //non editing cell template.. will be used for viweing data
   8:              DataTemplate textBlockTemplate = new DataTemplate();
   9:              FrameworkElementFactory textBlockElement = new FrameworkElementFactory(typeof(TextBlock));
  10:              Binding textBlockBinding = new Binding("TextBoxColumn");
  11:              textBlockElement.SetBinding(TextBlock.TextProperty, textBlockBinding);
  12:              textBlockTemplate.VisualTree = textBlockElement;
  13:              templateColumn.CellTemplate = textBlockTemplate;
  14:   
  15:              //editing cell template ... will be used when user will edit the data
  16:              DataTemplate textBoxTemplate = new DataTemplate();
  17:              FrameworkElementFactory textboxElement = new FrameworkElementFactory(typeof(TextBox));
  18:              Binding textboxBinding = new Binding("TextBoxColumn");
  19:              textboxElement.SetBinding(TextBox.TextProperty, textboxBinding);
  20:              textBoxTemplate.VisualTree = textboxElement;
  21:              templateColumn.CellEditingTemplate = textBoxTemplate;
  22:              return templateColumn;
  23:          }
Similarly, I am creating a column for ComboBox

   1:   private DataGridTemplateColumn CreateComboBoxColumn()
   2:          {
   3:              //create a template column
   4:              DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
   5:              //set title of column
   6:              templateColumn.Header = "ComboBoxColumn";
   7:              //non editing cell template.. will be used for viweing data
   8:              DataTemplate textBlockTemplate = new DataTemplate();
   9:              FrameworkElementFactory textBlockElement = new FrameworkElementFactory(typeof(TextBlock));
  10:              Binding textBlockBinding = new Binding("ComboBoxColumn");
  11:              textBlockElement.SetBinding(TextBlock.TextProperty, textBlockBinding);
  12:              textBlockTemplate.VisualTree = textBlockElement;
  13:              templateColumn.CellTemplate = textBlockTemplate;
  14:   
  15:              //editing cell template ... will be used when user will edit the data
  16:              DataTemplate comboboxTemplate = new DataTemplate();
  17:              FrameworkElementFactory comboboxElement = new FrameworkElementFactory(typeof(ComboBox));
  18:              Binding comboboxBinding = new Binding("ComboBoxColumn");
  19:              comboboxElement.SetBinding(ComboBox.TextProperty, comboboxBinding);
  20:   
  21:              //combo box will show these options to select from
  22:              comboboxElement.SetValue(ComboBox.ItemsSourceProperty, new List<string> { "Value1", "Value2" ,"Value3", "Value4" });
  23:              comboboxTemplate.VisualTree = comboboxElement;
  24:              templateColumn.CellEditingTemplate = comboboxTemplate;
  25:              return templateColumn;
  26:          }
You can download complete sample from here and play with it

Saturday, January 22, 2011

WPF’s DataGrid doesn’t show blank/empty row

Yesterday, I was playing with WPF’s DataGrid and DataBindings. ItemSource of my DataGrid was bound with the ObservableCollection. DataGrid was displaying all the items correctly which were present in my collection but I was facing one issue. The DataGrid was not displaying a blank empty row through which user can add a new record. I had set CanUserAddRows=”True” in my XAML. After spending sometime on the problem I found out that in order to show a blank row for adding a new record, DataGrid needs a constructor which takes no parameter. So I added a constructor of MyCustomObject which takes no parameter as a result DataGrid started displaying a blank row at the end.

ComboBox in WPF’s DataGrid through XAML

One thing I did recently was to allow user select value of a cell in WPF’s DataGrid from a ComboBox. I was using MVVM and the DataGrid was in my view so I had to define the columns and their bindings through XAML. After reading some articles on internet and some experimentation I was able to achieve the goal and I am sharing my solution here.

   1:  <DataGrid ItemsSource=”{Binding Path=CustomObjectCollection}” 
   2:   AutoGenerateColumns=”False>
   3:   <DataGrid.Columns>
   4:    <DataGridTemplateColumn>
   5:     <DataGridTemplateColumn.CellTemplate>
   6:      <DataTemplate>
   7:       <TextBlock Text=”{Binding Path=CustomObjectStringMember}”/>
   8:      </DataTemplate>
   9:     </DataGridTemplateColumn.CellTemplate>
  10:     <DataGridTemplateColumn.CellEditingTemplate >
  11:      <DataTemplate>
  12:       <ComboBox ItemsSource=”{Binding Path=CustomObjectListMember}” 
  13:       Text=”{Binding Path=CustomObjectStringMember}”/>
  14:      </DataTemplate>
  15:     </DataGridTemplateColumn.CellEditingTemplate>
  16:    </DataGridTemplateColumn>
  17:   </DataGrid.Columns>
  18:  </DataGrid>
In the code snippet above, CustomObjectCollection is an ObservableCollection where CustomObject is custom class I have written. Each instance of CustomObject in my ObservableCollection represents one row in my DataGrid. My CustomObject has two members ‘CustomObjectStringMember’ which is a String and ‘CustomObjectListMember’ which is List. The ComboBox in DataGrid will display the items in ‘CustomObjectListMember’ as possible options for selection and the selected value will be stored in ‘CustomObjectStringMember’ variable of my ‘CustomObject’ instance.

I have defined two templates inside XAML of my column. First one i.e. DataGridTemplateColumn.CellTemplate displays the selected value as TextBlock when the user is only viewing the the data in DataGrid. Second i.e. DataGridTemplateColumn.CellEditingTemplate will be activated when user will click on particular cell to edit its value. This template will update the cell from TextBlock to ComboBox and will allow user to select any value from the ComboBox. As soon as the cell will lose focus the CellTemplate will again replace the CellEditingTemplate

Creating shortcut of ‘Favorites’ in Windows 7

I frequently use ‘Favorites’ folder for speedy navigation to different locations on my system(Windows 7). When I tried to create a desktop shortcut for ‘Favorites’ folder I couldn’t find any way to do it through the standard window’s menu options. If you are facing same problem here is one way to do it.

Right click on your desktop and create a new shortcut.
Inside ‘Type the location of Item’ write C:\Windows\explorer.exe shell:::{323CA680-C24D-4099-B94D-446DD2D7249E} and click next.
Give your shortcut the name you want and click finish.
That’s all… Click the newly created shortcut it will open the Favorites folder.