Conditional CellTemplete controls at Runtime in Silverlight DataGrid


Most business applications implements a DataGrid in Silverlight and each scenario comes with its own set of complications that sometimes looks funny but challenging. Well I am writing this article after being stuck with a similar situation. In this article we will discuss approaches available to bind various controls, to different cells of a particular column inside a DataGrid, using IValueconvertor and with a bit of common-sense applied.

The Issue/Scenario

Our application required a few records to be displayed in a grid ... the simplest thing we can do, even while sleeping. But the challenge lies in displaying a different set of controls based on a business rule. Let me be clear with an example.
 
We have a set of Customer lists that need to be displayed in a Grid. The Customer object holds a property called Mood; the mood can either 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 feed back. The prototype for a particular control can be seen below:

SNAGHTMLcefdd2_thumb1.png

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 various approaches and some basic concepts of grid customization.

Addressing the Issue And Some Basics of DataGrid

The DataGrid comes with varieties of Column types, which can be either:
  1. DataGridTextColumn : For plain Text Values
  2. DataGridCheckBoxColumn :For Boolean Values
  3. DataGridTemplateColumn : For Integrated Controls (Bound Controls)
A brief explanation of these column types and their scenario for use is described very nicely 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 can define our columns and assign properties as binding. The figure bellow shows a DataGrid with custom columns.

image_thumb2.png

The DataGridTemplateColumn is handy to show custom column content with bound control for editing. By default it support 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.

SNAGHTML3846440_thumb3.png

Here in this article we will concentrate on displaying the data so our focus will be on CellTemplete.

While looking for solution for the issue mentioned above we thought of various options such as Dynamic DataTemplete (DataTemplateSelector) which WPF support but not Silverlight.DataTemplateSelector allows to use specific template based on the data object logic.Then thought of modifying XAML at runtime by creating Datatemplete in code behind and attach it to a 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 a ContentControl inside DataTemplete and assign the required custom control at runtime using IvalueConvertor. We will look into the implementation over 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 bellow

namespace GridInSilverlight
{
public enum Mood
{
NA,
Satisfied,
Normal,
UnSatisfied,
UA
}
public class Customer
{
public string Name{get;set;}
public string Place { getset; }
public string Phone{get;set;}
public bool IsCorporate{get;set;}
public Mood CustomerMood { getset; }
}
}
 

and the Grid definition for above entity is follows as below

<sdk:DataGrid AutoGenerateColumns="False"
Height="226"
Margin="12,45,12,0"
Name="dgCustomers"
VerticalAlignment="Top"
>
<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 }}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>

Consider the last column
SNAGHTML3846440_thumb4.png

The Convertor here is added as a resource in the control and that 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. Convertors are methods that allow you to modify the data as the control goes through the binding process.The convertor Method inherits from IValueConvertor and Implement Convert and ConvertBack method for modifying Source to Target and vice versa. More details on this topic can be found from 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 generates Dummy customer data , for your case it can be data from from your service call .On compiling the project the Grid exactly what we wanted ,

image_thumb_5.png

Last Few words 
 
The above work out is an example of DataGrid customization. The CellTemplete along with DataGridTemplateColumn can be used for further customization. Hope this article will help you understand the Grid concepts and customization concepts. The flexibility does Silverlight offer is amazing and XAML certainly is eligible for centre of Microsoft Focus. Let me know your views Be right back.

Source code and Live Link

Download -: DynamicControl_DataGrid.zip
Live Link -: http://manaspatnaik.com/app/GridInSilverlightTestPage.aspx


Similar Articles