Before I discuss about ItemsControl in Silverlight, I want to mention what exactly I needed to implement that lead me to ItemsControl.
I needed a flexible Custom Control (let’s call it MyItemsControl henceforth) which can display Repeated Items like a Silverlight Data Grid. But it has to be fully customizable in terms of the presentation of the Grid, the presentation of the Items etc.
Important thing which I have to mention is the Data Source. The Data source for this control is any Xml of the following structure.
<Items>
<Item>
<Attribute1></Attribute1>
<Attribute2></Attribute2>
<Attribute3></Attribute3>
. . .
. . .
</Item>
</Items>
Note that the Xml source can have any Element Names.
And the way MyItemsControl has to be used is Drop the Control in a Xaml and configures the Presentation of the Grid in the Xaml. Specify the Source Xml as one of the Attributes on the Control.
So now let’s see how this can be built using ItemsControl in Silverlight.
Since I have mentioned that I want to specify the presentation of the Control as part of the Xaml inside MyItemsControl, I require my control class to have Children property. So MyItemsControl should inherit Grid class (or even Stack Panel & Canvas).
Note that this is one of the reason I didn’t not use UserControl because it does not inherit from Panel class.
public class MyItemsControl : Grid, IDisposable {}
Once I have the ability to define childeren in the control I now add the Silverlight ItemsControl to it. This control has the following skeleton.
Basic MyCustomControl Xaml
<my:MyItemsControl RecordCount=”5″ x:Name=”control_001″ BindingItemsControl=” XML_Template” XMLUrl=”http://XMLSource/datasource.xml”>
<ItemsControl x:Name=”XML_Template”>
<ItemsControl.Resources>
<!–THE BELOW RESOURCE IS MANDATOR AAND IT IS REFERENCED BU EACH DATA ITEM.–>
<my:XMLItemConverter x:Key=”conv” />
</ItemsControl.Resources>
<ItemsControl.Template>
<!–THE CONTAINER FOR OVER ALL LAYOUT INCLUDING HEADER, IF ANY
THIS CAN BE ANY VALID XAML–>
<ControlTemplate TargetType=”ItemsControl”>
<!–INSIDE THE CONTROL TEMPLATE THE ITEM PRESENTER REPRESENT THE ACTUAL DATA ITEM CONAINER–>
<ItemsPresenter/>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!–THIS SECTION DEFINES THE ARRANGEMENT OF ITEMS. SINCE
ALWAYS ITEMS ARE ATACKE, IT WILL CONTAIN EITHER GRID OR STACK PANEL–>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!–THIS SECTION CONTAINS THE XAML FOR THE ITEM. IT CAN CONTAIN ANY INDEPENDENT VALID XAML–>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</my:XMLItemsControl>
Let’s discuss a few points about the above Xaml.
- The parent control is MyCustomControl which has an attribute RecordCount. This basically controls the no.s of Items that are to be shown at a time.
- XMLUrl is the Url of the Data source that the control will be bound to.
- BindingItemsControl specifies the control name of ItemsControl that my control has to bind Xml data to.
- The remaining sections of the ItemsControl are self explanatory.
- I will discuss about XMLItemConverter further below.
Controling the number of Items to Bind at a time.
The RecordCount attribute can be used as per the requirements. Let’s say you want to bind first 5 items to the ItemsControl and then after 20 sec you bind next 5 items and so on. For this you can have a storyboard or a timer to control the interval and then rebind next 5 items every time. You can pull all the items from XML source once for all during the load of the control and then use Linq to bind only 5 subsequent items every time. You might even trigger the Rebinding on occurrence of some user event like €œGet Next Items€.
void MyItemsControl_Loaded(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(BindingItemsControl))
{
_bindingItemsControl = (ItemsControl)((FrameworkElement)this.Parent).FindName(BindingItemsControl);
if (_bindingItemsControl != null)
{
xItemsCache = XMLItemsCache.GetXMLItems(XMLUrl);
if (!xItemsCache.IsFetchComplete)
{ xItemsCache.ItemsLoadComplete += new XMLItemsCache.DlgItemsLoadComplete(ShowItems);
xItemsCache.InitXMLItemsCache();
}
else {
ShowItems();
}
}
}
}
void strBrdLoop_Completed(object sender, EventArgs e)
{
int temp = LoadItems();
if (temp != -1)
strBrdLoop.Begin();
else {
strBrdLoop.Stop();
}
}
private int LoadItems()
{ if (!string.IsNullOrEmpty(XMLUrl))
{
XMLItemsCache xItems = XMLItemsCache.GetXMLItems(XMLUrl);
int itemIndex = GetCurrentItemIndex(PageUrlKey);
List<Dictionary<string, string>> temp = xItemsCache.GetNextItems(RecordCount, ref itemIndex);
_dictPageItemIndex[PageUrlKey] = itemIndex;
_bindingItemsControl.ItemsSource = temp;
return itemIndex;
} else
return -1; }
Binding of XML items to Item Template controls.
Till now we know how to control the binding of Items to the control. Now we will see how actually the contents of Xml items get bound to each TextBLock or MediaElement or Image in the ItemsTemplate
<ItemsControl x:Name=”MY_XML_Template”>
<ItemsControl.Resources>
<my:XMLItemConverter x:Key=”conv” />
</ItemsControl.Resources>
<ItemsControl.Template>
<ControlTemplate TargetType=”ItemsControl”>
<StackPanel>
<TextBlock Text=”{Binding ConverterParameter=Header, Converter={StaticResource conv}}” />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation=”Vertical” />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background=”Beige” BorderThickness=”4″ CornerRadius=”10″ ScrollViewer.VerticalScrollBarVisibility=”Visible”>
<Grid VerticalAlignment=”Stretch” Margin=”4,4,4,4″ Background=”Red” Height=”150″>
<Grid.RowDefinitions>
<RowDefinition Height=”2*” />
<RowDefinition Height=”6*” />
<RowDefinition Height=”2*” />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”2.5*” />
<ColumnDefinition Width=”2.5*” />
<ColumnDefinition Width=”5*” />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row=”1″ Grid.Column=”1″ Text=”Address :” HorizontalAlignment=”Right” />
<TextBlock Grid.Row=”2″ Grid.Column=”1″ Text=”DOB :” HorizontalAlignment=”Right” />
<TextBlock Text=”{Binding ConverterParameter=Name, Converter={StaticResource conv}}” TextWrapping=”Wrap” x:Name=”mytext” Grid.ColumnSpan=”2″ Grid.Row=”0″ Grid.Column=”0″ HorizontalAlignment=”Left” />
<TextBlock Text=”{Binding ConverterParameter=DOB, Converter={StaticResource conv}}” Grid.Row=”2″ Grid.Column=”2″ HorizontalAlignment=”Left” />
<TextBlock Text=”{Binding ConverterParameter=Address, Converter={StaticResource conv}}” Grid.Row=”1″ Grid.Column=”2″ HorizontalAlignment=”Left” />
<Image Source=”{Binding ConverterParameter=Snap, Converter={StaticResource conv}}” Grid.Row=”1″ Grid.Column=”0″ Grid.RowSpan=”2″ VerticalAlignment=”Center” HorizontalAlignment=”Left” Height=”127″ Width=”127″ />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
First of all, if you look at line of code
List<Dictionary<string, string>> temp = xItemsCache.GetNextItems(RecordCount, ref itemIndex);
Each xml item are transformed into Dictionary<string, string>. So lets say our Xml is something like
<Persons>
<Person>
<Name>Tom</Name>
<DOB>15/02/1983</DOB>
<Snap>http://img1.jurko.net/wall/paper/tom_cruise_2.jpg</Snap>
</Person>
</Persons>
Then one list Item will contain a dictioanry
<Name, Tom>
<DOB, 15/02/83>
<Snap, http://img1.jurko.net/wall/paper/tom_cruise_2.jpg>
For each Item, there will be such Dictionary built. Following is the code to build such a dictionary.
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Result == null)
{
IsFetchComplete = true;
return;
}
using (Stream s = e.Result)
{
try
{
XDocument xDoc = XDocument.Load(s);
string itemNodeName = xDoc.Root.Descendants().FirstOrDefault<XElement>().Name.LocalName;
List<XElement> lstElements = xDoc.Descendants(itemNodeName).ToList<XElement>();
foreach (XElement ele in lstElements)
{
Dictionary<string, string> d = new Dictionary<string, string>();
foreach (XElement itemEle in ele.Elements())
{
if (!d.ContainsKey(itemEle.Name.LocalName))
d.Add(itemEle.Name.LocalName, itemEle.Value);
}
MYItems.Add(d);
}
IsFetchComplete = true;
this.ItemsLoadComplete();
}
catch (Exception ex) { return; }
}
Custom Binding converter
Now once you bind the List of such Dictionalries, rest is taken care by the Custom Binding Converter class that knows how to bind each control to items in Dictionary. Here is the class for Binding Converter.
public class XMLItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{ object result = string.Empty;
Dictionary<string, string> columnData = (Dictionary<string, string>)value;
if (columnData.ContainsKey(parameter.ToString()))
{ result = columnData[parameter.ToString()]; }
return result.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{ throw new NotImplementedException(); }
}
I guess now we can make sense of the Xaml <my:XMLItemConverter x:Key=”conv” />.
Here we are specifying the converter that the ItemsControl should use while binding to the Controls. The converter knows how to bind the Dictionary items to the appropriate attribute of the control.
E.g.
<TextBlock Text=”{Binding ConverterParameter=Header, Converter={StaticResource conv}}” />
<Image Source=”{Binding ConverterParameter=Snap, Converter={StaticResource conv}}” Grid.Row=”1″ Grid.Column=”0″ Grid.RowSpan=”2″ VerticalAlignment=”Center” HorizontalAlignment=”Left” Height=”127″ Width=”127″ />
In the above Xaml for Image, we specify Source Converter parameter as Snap and the converter class (in this case specified as a Resource above) as conv.
Unlike DataGrid control this controls allows us to be extremely flexible while presenting the Data and also read the data from a very simple XML format.