Introduction
Most of the business applications implement Datagrid
in Silverlight and each of the scenarios comes with its own complication that sometimes looks funny but challenging. I am writing this article after getting stuck with a similar situation. In this post, we will discuss about approaches available to bind various controls, to different cells of a particular column inside Datagrid
, using IValueconvertor
and with a bit of common-sense applied.
The Issue/Scenario
Our application required few records to be displayed over grid... simplest thing we can do even while sleeping. But the challenge lies in displaying different set of controls based on business rules. Let me clarify with an example.
We have a set of Customer
lists that need to be displayed over Grid
. The Customer
object holds a property called Mood
, either the mood can be Happy
, Neutral
or Angry
. Well, it doesn't stop here. If the Mood
is not available, the Cell must display "NA" and if the property is null
, then it should display a HyperLinkbutton
which in turn will send a request to the particular customer seeking for feedback. The prototype for a particular control can be seen below:
So the grid must be smart enough either to use Image control or Simple Text Block control with NA or a Button control with its own code behind logic. The binding data of each row will decide the control for a particular column at run time. My next section will focus on different approaches and some basic concepts of grid customization.
Addressing the Issue and Some Basics of DataGrid
The Datagrid
comes up with a variety of Column types, which can be one of:
DataGridTextColumn
: for plain Text Values
DataGridCheckBoxColumn
: for Boolean Values
DataGridTemplateColumn
: for Integrated Controls (Bound Controls)
A brief about these column types and their scenarios of use is nicely described over here. Once we choose our custom ColumnType
, we can use any control as a cell template that will hold the binding data. To use custom columns, we should force Datagrid
to stop auto generating columns, instead we can define our columns and assign properties as binding. The figure below shows a datagrid
with custom columns.
The DataGridTemplateColumn is handy to show custom column content with bound control for editing. By default, it supports use of a uniform control across column cells. That means once defined, all the cells of that column will be uniform. As I mentioned earlier, CellTemplete property of DataGrid
allows us to display data while that cell is not in editing mode.
Here in this post, we will concentrate on displaying the data so our focus will be on CellTemplete
.
While looking for a solution for the issue mentioned above, we thought of various options such as Dynamic DataTemplate (DataTemplateSelector) which WPF support but not Silverlight. DataTemplateSelector
allows to use specific template based on the data object logic. Then, I thought of modifying XAML at runtime by creating Datatemplate
at code behind and attaching it to cell template although we kept it as the last choice.We were looking for a control which can hold any control defined at runtime and the choice of control can be decided using Ivalueconvertor
based on the particular business object.
What about ContentControl? Basically ContentControl
is a control whose Control
property can be any of the Silverlight UIElement
.
So the best appeared solution for the given scenario is to use ContentControl
inside DataTemplate
and assign the required custom control at runtime using IvalueConvertor
. We will look into the implementation over the next section.
Setting up the Grid and Binding
The page contains a Grid
which will show the Customer
data.The Customer
business entity object is somehow defined as mentioned below:
namespace GridInSilverlight
{
public enum Mood
{
NA,
Satisfied,
Normal,
UnSatisfied,
UA
}
public class Customer
{
public string Name{get;set;}
public string Place { get; set; }
public string Phone{get;set;}
public bool IsCorporate{get;set;}
public Mood CustomerMood { get; set; }
}
}
and the Grid
definition for the above entity is as follows:
<sdk:DataGrid AutoGenerateColumns="False"
Height="253"
Margin="12,45,0,0"
Name="dgCustomers"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="462">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto"
Binding="{Binding Name}" Header="Name"/>
<sdk:DataGridTextColumn CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto"
Binding="{Binding Place}"
Header="Country" />
<sdk:DataGridTextColumn CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto"
Binding="{Binding Phone}"
Header="Phone" />
<sdk:DataGridCheckBoxColumn
Width="Auto"
Binding="{Binding IsCorporate}"
Header="Is Corporate" />
<sdk:DataGridTemplateColumn CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto" Header="Mood">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content=
"{Binding Converter={StaticResource MoodConv },
ConverterParameter='CUS'}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto"
Header="Company Mood">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Converter=
{StaticResource MoodConv },ConverterParameter='COM'}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Consider the last column:
The Convertor
here is added as a resource in the control and that is going to define the UIElement
that will in turn will be used to display the values. The next step is to create a convertor for Mood
display. Convertor
s are methods that allows you to modify the data as the control goes through the binding process.The convertor
method inherits from IValueConvertor and implements Convert
and ConvertBack
methods for modifying Source to Target and vice versa. More details on this topic can be found here.
The Convertor
code for this particular project is:
public class MoodConvertor:IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Customer cusObj = value as Customer;
Image img = new Image();
switch (cusObj.CustomerMood)
{
case Mood.Normal:
img.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Normal.png", UriKind.Relative));
return img;
case Mood.Satisfied:
img.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("Satisfied.png", UriKind.Relative));
return img;
case Mood.UnSatisfied:
img.Source = new System.Windows.Media.Imaging.BitmapImage
(new Uri("UnSatisfied.png", UriKind.Relative));
return img;
case Mood.UA:
HyperlinkButton btn = new HyperlinkButton();
btn.Content = "Invite Suggestion";
return btn;
default :
TextBlock tbU = new TextBlock();
tbU.Text = "-";
return tbU;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Few lines of code generate Dummy customer data, for your case it can be data from your service call. On compiling the project, the Grid
is exactly what we needed:
Last Few Words…
The above work is one of the examples of DataGrid
customization. The CellTemplete
along with DataGridTemplateColumn
can be used for further customization. Hope this post will help you in understanding the Grid
concepts and customization concepts. The flexibility that Silverlight offers is amazing and XAML is certainly eligible for centre of Microsoft Focus. Let me know your views .