programming4us
programming4us
WEBSITE

Silverlight Recipes : Controls - Creating Custom Column Types for a DataGrid

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
4/29/2013 7:45:09 PM

1. Problem

You want to create a custom column type for a DataGrid to enable specific functionality to handle a particular data type or data item.

2. Solution

Extend the DataGridBoundColumn class, and add functionality to handle the view and edit modes for the intended data type or data item.

3. How It Works

The framework ships with a few prebuilt DataGrid column types for handling some of the standard data types. DataGridTextColumn is one of the most useful ones and can be used to view and edit any data that can be converted to a meaningful text representation. DataGridCheckBoxColumn is another one that can be used to view and edit a Boolean value as a CheckBox and with the current value mapped to its checked state.

In some situations, you want to implement custom logic to handle a specific data type or a program data item bound to a DataGrid column. One way to achieve that is through the use of the DataGridTemplateColumn column type and the use of CellTemplate and CellEditingTemplate. Yet another way is to create a new column type that encapsulates the custom logic, much like the ones that the framework ships with. The logic encapsulation in creating this custom column offers you the advantage of not having to rely on the consumers (UI layer developers) of your data to supply appropriate data templates, as well as the ability to standardize and lock down how a specific data type or item gets treated inside a DataGrid.

In this second approach, you start by creating a new class extending the System.Windows.Controls.DataGridBoundColumn class in the System.Windows.Controls.Data assembly, which is also the base class for the framework-provided column types mentioned earlier. The DataGridBoundColumn exposes an API of abstract methods that allows you to easily control the UI and data-binding logic of cells in the custom column as they are switched between view and edit modes. The methods in this API that you will override most often are GenerateElement(), GenerateEditingElement(), PrepareCellForEdit(), and CancelCellEdit(). All of these methods, except CancelCellEdit(), are abstract methods; therefore it is mandatory that you provide an appropriate implementation in your custom column code.

3.1. The GenerateElement() Method

This method is expected to create the UI that would be used by the DataGrid to display the bound value in every cell of that column. The created UI is returned in the form of a FrameworkElement from GenerateElement(). By overriding this method and supplying your custom logic, you can change the UI a bound cell uses to display its content. You are also expected to create and set appropriate data bindings for your newly created UI in this method so that data items are appropriately displayed in every cell. To do that, you can obtain the data binding set by the user in the XAML for the column through the DataGridBoundColumn.Binding property. You can then use the SetBinding() method to apply that binding to the appropriate parts of the UI you create before returning the UI as a FrameworkElement.

Also note that this method accepts two parameters passed in by the containing DataGrid: a cell of type DataGridCell and a data item of type object. The first parameter contains a reference to the instance of the DataGridCell that is currently being generated, and the second parameter refers to the data item bound to the current row. You don't have to use these parameters to successfully implement this method. One interesting use of these parameters is in a computed column scenario. Since the dataItem parameter contains the entire item bound to that row, you can easily compute a value based on parts of the data item and use that as the cell value bound to the UI you return from this method. We leave it to you to experiment further with these parameters.

3.2. The GenerateEditingElement() Method

This method is somewhat similar to GenerateElement() in purpose but is used for edit mode rather than view mode. When the user switches a cell to edit mode—for example, by clicking it—the DataGrid calls this method on the column type to generate the UI for the editing experience. The generated UI is again returned as a FrameworkElement. You can override this method in your custom column type to create a custom edit UI for your data type or item. The same requirements for applying the appropriate data bindings before you return the generated UI, as discussed for GenerateElement() earlier, apply here. Also note that this method accepts the same parameter set as GenerateElement().

3.3. The PrepareCellForEdit() Method

This method is called by the DataGrid to obtain the unedited value from the bound cell before entering edit mode. The unedited value is retained by the DataGrid and made available to you in CancelCellEdit() so that edits made to the cell can be undone should a user choose to cancel an edit operation. The FrameworkElement type you created for the edit mode UI in GenerateEditingElement() is made available to you as the editingElement parameter. You can use that to obtain the current unedited value for the cell. The second parameter to this method, editingEventArgs, is of type RoutedEventArgs. It contains information about the user gesture that caused the cell to move to edit mode. For keyboard-based input, it can be cast to KeyEventArgs; for mouse input gestures, it can be cast to MouseButtonEventArgs. You should check the result of your cast to verify that it is non-null before using the parameter. This parameter can be used to implement additional logic, such as different editing behaviors if a specific key is pressed.

3.4. The CancelCellEdit() Method

This method is called if a user cancels an edit operation. The unedited value bound to the cell, prior to any changes made by the user in edit mode, is made available to you via the uneditedValue parameter, as is the FrameworkElement representing the edit UI through the editingElement parameter. You can undo the changes made by resetting the editingElement using the uneditedValue. The uneditedValue parameter is of type object, and consequently you will need to cast it to the appropriate type based on the bound data before you use it to reset the edit changes.

4. The Code

The code sample in this recipe creates a custom column type named DataGridDateColumn for editing DateTime types using the DatePicker control. Listing 1 shows the code for DataGridDateColumn.

Listing 1. DataGridDateColumn Class
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Recipe5_7
{
  public class DataGridDateColumn : DataGridBoundColumn
  {
    [TypeConverter(typeof(DataGridDateTimeConverter))]
    public DateTime DisplayDateStart { get; set; }

    public Binding DisplayDateEndBinding { get; set; }

    protected override void CancelCellEdit(FrameworkElement editingElement,
      object uneditedValue)

{
      //get the DatePicker
      DatePicker datepicker = (editingElement as Border).Child as DatePicker;
      if (datepicker != null)
      {
        //rest the relevant properties on the DatePicker to the original value
        //to reflect cancellation and undo changes made
        datepicker.SelectedDate = (DateTime)uneditedValue;
        datepicker.DisplayDate = (DateTime)uneditedValue;
      }
    }

    //edit mode
    protected override FrameworkElement GenerateEditingElement(
DataGridCell cell, object dataItem)
    {
      //create an outside Border
      Border border = new Border();
      border.BorderBrush = new SolidColorBrush(Colors.Blue);
      border.BorderThickness = new Thickness(1);
      border.HorizontalAlignment = HorizontalAlignment.Stretch;
      border.VerticalAlignment = VerticalAlignment.Stretch;
      //create the new DatePicker
      DatePicker datepicker = new DatePicker();
      //bind the DisplayDate to the bound data item
      datepicker.SetBinding(DatePicker.DisplayDateProperty,
        base.Binding);
      //bind the SelectedDate to the same
      datepicker.SetBinding(DatePicker.SelectedDateProperty,
        base.Binding);
      //bind the DisplayDate range
      //start value is provided directly through a property
      datepicker.DisplayDateStart = this.DisplayDateStart;
      //end value is another binding allowing developer to bind
      datepicker.SetBinding(DatePicker.DisplayDateEndProperty,
        this.DisplayDateEndBinding);
      border.Child = datepicker;
      //return the new control
      return border;
    }

    //view mode
    protected override FrameworkElement GenerateElement(DataGridCell cell,
object dataItem)
    {

					  

//create a TextBlock
      TextBlock block = new TextBlock();
      //bind the displayed text to the bound data item
      block.SetBinding(TextBlock.TextProperty, base.Binding);
      //return the new control
      return block;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement,
      RoutedEventArgs editingEventArgs)
    {
      //get the datepicker
      DatePicker datepicker = (editingElement as Border).Child as DatePicker;
      //return the initially displayed date, which is the
      //same as the unchanged data item value
      return datepicker.DisplayDate;
    }
  }
}

					  

In GenerateElement(), you create a TextBlock as your control of choice to display the bound data. You then set the binding on the TextBlock.Text property to the Binding property on the column so that the date is displayed inside the TextBlock. In GenerateEditingElement(), you instead create a Border and nest a DatePicker control in it for date editing. Once the DatePicker control is created, you set both the DisplayDate (the date displayed in the editable text portion of the DatePicker) and the SelectedDate (the date value selected in the drop-down portion of the DatePicker) initially to the Binding property on the column. You also set a couple of other bindings that will be explained later in the recipe before you return the Border. In PrepareCellForEdit(), you return the currently displayed date to the DataGrid for retention in case of a cancellation, and in CancelCellEdit(), you reset the appropriate values on the DatePicker instance to the unedited value saved earlier through PrepareCellForEdit().

Listing 2 shows the XAML declaration of a DataGrid using the DataGridDateColumn type. Again, you use the AdventureWorks WCF service as a data source.

Listing 2. XAML for the MainPage Demonstrating Custom DataGrid Column
<UserControl x:Class="Recipe5_7.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:data=
  "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
  xmlns:local="clr-namespace:Recipe5_7"
  Width="800" Height="400"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">
  <UserControl.Resources>

					  

</UserControl.Resources>
  <Grid x:Name="LayoutRoot" Background="White">
  <data:DataGrid x:Name="dgProducts" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
      <data:DataGridTextColumn
        Binding="{Binding ProductID}" Header="ID" />
      <data:DataGridTextColumn
        Binding="{Binding Name}" Header="Name" />
      <local:DataGridDateColumn
        Binding="{Binding SellStartDate}"
        DisplayDateStart="01/01/2000"
        DisplayDateEndBinding="{Binding DisplayDateEnd}"
        Header="Available From" />
    </data:DataGrid.Columns>
  </data:DataGrid>
  </Grid>
</UserControl>

One of the challenges of this approach is that the developer using the DataGridDateColumn may want to control some behavior of the internal DatePicker instance. For example, the DatePicker control exposes DisplayDateStart and DisplayDateEnd properties that determine the date range that the DatePicker drop-down is limited to; however, the developer may want to specify this range when using the DataGridDateColumn. Unfortunately, since the DatePicker control instance is not visible outside the DataGridDateColumn code, there is no direct way for the developer to do so.

One way to allow developers to control these properties is to create corresponding properties on DataGridDateColumn so that they can be set in XAML, and those values can be used in the code to set the DatePicker properties. Referring to the DataGridDateColumn class in Listing 1, you can see the DisplayDateStart property of type DateTime; note that it is being set to a date string in the XAML in Listing 2. The value of this property is then used inside GenerateEditingElement() to set the similarly named property on the DatePicker instance.

Since the date string set in XAML needs to be converted to a DateTime type for the code to work correctly, you need a type conversion mechanism. The framework contains the TypeConverter class, which you can extend to create a type converter of your own. Listing 3 shows a type converter that converts from String to DateTime.

Listing 3. DataGridDateTimeConverter Class
using System;
using System.ComponentModel;
using System.Globalization;

namespace Recipe5_7
{
  public class DataGridDateTimeConverter : TypeConverter
  {
    public override bool CanConvertFrom(ITypeDescriptorContext context,
      Type sourceType)
    {

return (typeof(string) == sourceType);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context,
      Type destinationType)
    {
      return (typeof(DateTime) == destinationType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context,
      CultureInfo culture, object value)
    {
      DateTime target;
      target = DateTime.ParseExact(value as string, "d",
        CultureInfo.CurrentUICulture);
      return target;
    }
  }
}

The TypeConverterAttribute can be used to attach this type converter to your DataGridDateColumn.DisplayDateStart property, as shown in Listing 1. Note that in overriding the ConvertFrom() method in the TypeConverter implementation, the system passes in a CultureInfo instance as the second parameter. The CultureInfo parameter allows you to inspect the current culture. If you need to implement any additional conversion logic based on the locale, you can check this parameter and take the needed action in your code. In your case, you do not use the value, but just pass in CultureInfo.CurrentUICulture in your call to Datetime.ParseExact() to allow the DateTime value type to handle the rest of the conversion logic.

You might want to have such a property set as a binding instead of a direct value setting, much like the Binding property on any DataGrid column. This allows the developer to associate a data binding with the property and lets its value be derived at runtime from the source it is bound to, as opposed to being hard-coded.

As an example, say you want to expose a property named DisplayDateEndBinding on the DataGridDateColumn and use that to drive the value of the DisplayDateEnd property of the DatePicker instance. You can see the declaration of this property in Listing 1, and it is bound to a property named DisplayDateEnd on the data source in the XAML in Listing 2. It can then be used to attach the same binding to the DatePicker.DisplayDateEnd property, as shown in the GenerateEditingElement() method in Listing 1. There is not much code to discuss, beyond a call to the AdventureWorks WCF service; we encourage the user to refer to the sample code for the book.

Figure 1 shows the DataGridDateColumn in action.

Figure 1. DataGridDateColumn in edit mode
Other  
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us
programming4us
 
 
programming4us