Create Custom Column Editor
This tutorial will guide you through the common task of creating a custom column in RadGridView. More precisely, you will learn to create a UserControl with TextBox and RadComboBox, as well as a custom bound column that uses it as an edit element.
- In the beginning you need RadGridView populated with sample data. Below are the business object definition (Example 1), as well as RadGridView declaration (Example 2).
Example 1: Business object definition
public class Club : INotifyPropertyChanged
{
private string name;
private DateTime established;
private int stadiumCapacity;
private Captain captain;
public Club(string name, DateTime established, int stadiumCapacity, Captain captain)
{
this.name = name;
this.established = established;
this.stadiumCapacity = stadiumCapacity;
this.captain = captain;
}
public String Name
{
get { return this.name; }
set
{
if (this.name != value)
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
}
public DateTime Established
{
get
{
return this.established;
}
set
{
if (this.established != value)
{
this.established = value;
this.OnPropertyChanged("Established");
}
}
}
public int StadiumCapacity
{
get { return this.stadiumCapacity; }
set
{
if (this.stadiumCapacity != value)
{
this.stadiumCapacity = value;
this.OnPropertyChanged("StadiumCapacity");
}
}
}
public Captain Captain
{
get
{
return this.captain;
}
set
{
if (this.captain != value)
{
this.captain = value;
this.OnPropertyChanged("Captain");
}
}
}
public static ObservableCollection<Club> GetClubs()
{
ObservableCollection<Club> clubs = new ObservableCollection<Club>();
clubs.Add(new Club("Liverpool", new DateTime(1892, 1, 1, 13, 35, 15), 45362, new Captain("Steven Gerrard", Position.MF)));
clubs.Add(new Club("Manchester Utd.", new DateTime(1878, 1, 1, 18, 45, 25), 76212, new Captain("Wayne Rooney", Position.FW)));
clubs.Add(new Club("Chelsea", new DateTime(1905, 1, 1, 23, 45, 35), 42055, new Captain("John Terry", Position.DF)));
clubs.Add(new Club("Arsenal", new DateTime(1886, 1, 1, 4, 55, 45), 60355, new Captain("Mikel Arteta", Position.MF)));
return clubs;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, args);
}
}
private void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Public Class Club
Implements INotifyPropertyChanged
Private _name As String
Private _established As Date
Private _stadiumCapacity As Integer
Private _captain As Captain
Public Sub New(ByVal name As String, ByVal established As Date, ByVal stadiumCapacity As Integer, ByVal captain As Captain)
Me._name = name
Me._established = established
Me._stadiumCapacity = stadiumCapacity
Me._captain = captain
End Sub
Public Property Name() As String
Get
Return Me._name
End Get
Set(ByVal value As String)
If Me._name <> value Then
Me._name = value
Me.OnPropertyChanged("Name")
End If
End Set
End Property
Public Property Established() As Date
Get
Return Me._established
End Get
Set(ByVal value As Date)
If Me._established <> value Then
Me._established = value
Me.OnPropertyChanged("Established")
End If
End Set
End Property
Public Property StadiumCapacity() As Integer
Get
Return Me._stadiumCapacity
End Get
Set(ByVal value As Integer)
If Me._stadiumCapacity <> value Then
Me._stadiumCapacity = value
Me.OnPropertyChanged("StadiumCapacity")
End If
End Set
End Property
Public Property Captain() As Captain
Get
Return Me._captain
End Get
Set(ByVal value As Captain)
If Me._captain IsNot value Then
Me._captain = value
Me.OnPropertyChanged("Captain")
End If
End Set
End Property
Public Shared Function GetClubs() As ObservableCollection(Of Club)
Dim clubs As New ObservableCollection(Of Club)()
clubs.Add(New Club("Liverpool", New Date(1892, 1, 1, 13, 35, 15), 45362, New Captain("Steven Gerrard", Position.MF)))
clubs.Add(New Club("Manchester Utd.", New Date(1878, 1, 1, 18, 45, 25), 76212, New Captain("Wayne Rooney", Position.FW)))
clubs.Add(New Club("Chelsea", New Date(1905, 1, 1, 23, 45, 35), 42055, New Captain("John Terry", Position.DF)))
clubs.Add(New Club("Arsenal", New Date(1886, 1, 1, 4, 55, 45), 60355, New Captain("Mikel Arteta", Position.MF)))
Return clubs
End Function
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal args As PropertyChangedEventArgs)
Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
If handler IsNot Nothing Then
handler(Me, args)
End If
End Sub
Private Sub OnPropertyChanged(ByVal propertyName As String)
Me.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Note, that the Club object has a Captain property. The Captain object itself, has two properties — Name, which is of type string, and Position, which is an Enum.
Example 2: Initial declaration of RadGridView
<telerik:RadGridView x:Name="radGridView" AutoGenerateColumns="False" ItemsSource="{Binding Clubs}">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Name}" Header="Name" />
<telerik:GridViewDataColumn DataFormatString="{}{0:N0}"
DataMemberBinding="{Binding StadiumCapacity}"
Header="Stadium" />
<local:CustomColumn DataMemberBinding="{Binding Captain}" FilterMemberPath="Position"/>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
Example 3: Populating RadGridView
this.radGridView.ItemsSource = Club.GetClubs();
Me.radGridView.ItemsSource = Club.GetClubs()
- The next step is to create a UserControl with TextBox and RadComboBox. Create a new UserControl named CustomCaptainEditor (Example 4 ).
Example 4: Declaration of CustomCaptainEditor UserControl
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding CaptainName, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=my:CustomCaptainEditor}}"/>
<telerik:RadComboBox Grid.Column="1" SelectedValue="{Binding CaptainPosition, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=my:CustomCaptainEditor}}"
ItemsSource="{Binding Positions, RelativeSource={RelativeSource AncestorType=my:CustomCaptainEditor}}"
DisplayMemberPath="Name" SelectedValuePath="Value"/>
</Grid>
Example 5: Code-behind definition of the CustomCaptainEditor UserControl
public partial class CustomCaptainEditor : UserControl
{
public static readonly DependencyProperty CaptainNameProperty =
DependencyProperty.Register("CaptainName", typeof(String), typeof(CustomCaptainEditor), new PropertyMetadata(null));
public static readonly DependencyProperty CaptainPositionProperty =
DependencyProperty.Register("CaptainPosition", typeof(Position), typeof(CustomCaptainEditor), new PropertyMetadata(null));
public CustomCaptainEditor()
{
InitializeComponent();
}
public String CaptainName
{
get
{
return (String)this.GetValue(CaptainNameProperty);
}
set
{
this.SetValue(CaptainNameProperty, value);
}
}
public Position CaptainPosition
{
get
{
return (Position)this.GetValue(CaptainPositionProperty);
}
set
{
this.SetValue(CaptainPositionProperty, value);
}
}
private IEnumerable<EnumMemberViewModel> positions;
public IEnumerable<EnumMemberViewModel> Positions
{
get
{
if (this.positions == null)
{
this.positions = EnumDataSource.FromType(typeof(Position));
}
return this.positions;
}
}
}
Partial Public Class CustomCaptainEditor
Inherits UserControl
Public Shared ReadOnly CaptainNameProperty As DependencyProperty = DependencyProperty.Register("CaptainName", GetType(String), GetType(CustomCaptainEditor), New PropertyMetadata(Nothing))
Public Shared ReadOnly CaptainPositionProperty As DependencyProperty = DependencyProperty.Register("CaptainPosition", GetType(Position), GetType(CustomCaptainEditor), New PropertyMetadata(Nothing))
Public Sub New()
InitializeComponent()
End Sub
Public Property CaptainName() As String
Get
Return CStr(Me.GetValue(CaptainNameProperty))
End Get
Set(ByVal value As String)
Me.SetValue(CaptainNameProperty, value)
End Set
End Property
Public Property CaptainPosition() As Position
Get
Return CType(Me.GetValue(CaptainPositionProperty), Position)
End Get
Set(ByVal value As Position)
Me.SetValue(CaptainPositionProperty, value)
End Set
End Property
Private _positions As IEnumerable(Of EnumMemberViewModel)
Public ReadOnly Property Positions() As IEnumerable(Of EnumMemberViewModel)
Get
If Me._positions Is Nothing Then
Me._positions = EnumDataSource.FromType(GetType(Position))
End If
Return Me._positions
End Get
End Property
End Class
Take a look at the code-behind of the control. Two additional dependency properties are created in order to enable binding to the Name and Position properties of the business model.
- Create a new class named CustomColumn, which derives from GridViewBoundColumnBase (Example 6).
Example 6: Definition of CustomColumn class
public class CustomColumn : GridViewBoundColumnBase
{
public override FrameworkElement CreateCellElement(GridViewCell cell, object dataItem)
{
TextBlock tb = cell.Content as TextBlock;
if (tb == null)
{
tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding(this.DataMemberBinding.Path.Path) { Converter = new MyConverter() });
}
return tb;
}
public override FrameworkElement CreateCellEditElement(GridViewCell cell, object dataItem)
{
var editor = new CustomCaptainEditor();
editor.SetBinding(CustomCaptainEditor.CaptainNameProperty,
CreateBinding(string.Format("{0}.Name", this.DataMemberBinding.Path.Path)));
editor.SetBinding(CustomCaptainEditor.CaptainPositionProperty,
CreateBinding(string.Format("{0}.Position", this.DataMemberBinding.Path.Path)));
return editor;
}
private Binding CreateBinding(string property)
{
Binding binding = new Binding(property);
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
return binding;
}
}
Public Class CustomColumn
Inherits GridViewBoundColumnBase
Public Overrides Function CreateCellElement(ByVal cell As GridViewCell, ByVal dataItem As Object) As FrameworkElement
Dim tb As TextBlock = TryCast(cell.Content, TextBlock)
If tb Is Nothing Then
tb = New TextBlock()
tb.SetBinding(TextBlock.TextProperty, New Binding(Me.DataMemberBinding.Path.Path) With {.Converter = New MyConverter()})
End If
Return tb
End Function
Public Overrides Function CreateCellEditElement(ByVal cell As GridViewCell, ByVal dataItem As Object) As FrameworkElement
Dim editor = New CustomCaptainEditor()
editor.SetBinding(CustomCaptainEditor.CaptainNameProperty, CreateBinding(String.Format("{0}.Name", Me.DataMemberBinding.Path.Path)))
editor.SetBinding(CustomCaptainEditor.CaptainPositionProperty, CreateBinding(String.Format("{0}.Position", Me.DataMemberBinding.Path.Path)))
Return editor
End Function
Private Function CreateBinding(ByVal [property] As String) As Binding
Dim binding As New Binding([property])
binding.Mode = BindingMode.TwoWay
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
Return binding
End Function
End Class
In a scenario when there is a column.CellEditTemplate defined, the new value of the editor is not available in the arguments of the CellEditEnded event raised when commiting an edit. To get the right value in e.NewValue, you should override the column's GetNewValueFromEditor method.
Here is the code of the custom converter we have used:
Example 7: The MyConverter class
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var captain = value as Captain;
if (String.IsNullOrEmpty(captain.Name))
{
return String.Format("{0}", captain.Position);
}
return String.Format("{0}, {1}", captain.Name, captain.Position);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var captain = value as Captain;
if (String.IsNullOrEmpty(captain.Name))
{
return String.Format("{0}", captain.Position);
}
return String.Format("{0}, {1}", captain.Name, captain.Position);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
- Run your demo and try to edit a cell from the new custom column. The result should be similar to the snapshot in Figure 1.
Figure 1: Snapshot of the created CustomColumn
You can download a runnable project of the previous example from the online SDK repository CustomColumnEditor.
You can also check the SDK Samples Browser that provides a more convenient approach to exploring and executing the examples in the Telerik XAML SDK repository.