XAML Playground
about XAML and other Amenities

Simplify localization with a T4 template

2010-11-25T23:07:04+01:00 by Andrea Boschin

If you evere tryied to localize a Silverlight application, you have found for sure that it is not a straighforward task. Like all the other .NET tecnologies the Silverlight projects has resource files and you can create satellite assemblies to contains culture specific strings. But the problem is that it is not so easy to bring this string to the interface.

When you create a resx file Visual Studio generates a code file with a class ready to be used to read the strings. You can also choose to make this class public, so it is possible to use it across different projects. Unfortunately this class has an internal constructon so there is no way to add an instance to the App.xaml resources and use DataBinding to reference the strings in the user interface.

Someone suggest to manually change the ctor of the class but to me it is very frustrating to correct this simple bug every time I add a new resource. So it is required to create a proxy class, that wraps the static one. Every property will call the correspondent static property and then the proxy class will be used for the DataBinding. When you are developing a simple application this is a simple task, but what about when you have an huge number of strings?

With this in mind I've worked to a T4 template that it is able to read a resx file and automatically produces the proxy class. If you do not know it, the T4 template is a very powerful tool that exists since Visual Studio 2008. With it you can write code to generate code inside Visual Studio, and it is exactly what I'm searching for.

Here is the template:

   1: <#@ template debug="false" hostspecific="true" language="C#" #>
   2: <#@ output extension=".cs"  encoding="UTF8" #>
   3: <#@ assembly name="System.Core" #>
   4: <#@ assembly name="System.Xml" #>
   5: <#@ assembly name="System.Xml.Linq" #>
   6: <#@ import namespace="System.IO" #>
   7: <#@ import namespace="System.Text.RegularExpressions" #>
   8: <#@ import namespace="System.Linq" #>
   9: <#@ import namespace="System.Xml" #>
  10: <#@ import namespace="System.Xml.Linq" #>
  11: //------------------------------------------------------------------------------
  12: // <auto-generated>
  13: //     This code was generated by a tool.
  14: //
  15: //     Changes to this file may cause incorrect behavior and will be lost if
  16: //     the code is regenerated.
  17: // </auto-generated>
  18: //------------------------------------------------------------------------------
  19:  
  20: <#
  21: string appName = "ResXProxy Generator Template";
  22: string version = "1.0.3977.0";
  23: string ns = (string)System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint");
  24: string resxFileName = Path.ChangeExtension(Host.TemplateFile, ".resx");
  25: string resxClassName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
  26: string proxyClassName = string.Format("{0}ResourceProxy", resxClassName);
  27: XDocument document = XDocument.Parse(File.ReadAllText(resxFileName));
  28: #>
  29: namespace <#=ns#>
  30: {
  31:     using System.Globalization;
  32:     using System.Windows.Markup;
  33:  
  34:     /// <summary>
  35:     /// Represent a proxy class for "<#= resxClassName #>" resources
  36:     /// </summary>
  37:     [System.Diagnostics.DebuggerStepThroughAttribute()]
  38:     [System.CodeDom.Compiler.GeneratedCode("<#= appName #>", "<#= version #>")]
  39:     public class <#= proxyClassName #>
  40:     {
  41:         /// <summary>
  42:         /// Initializes the "<#= proxyClassName #>" class
  43:         /// </summary>
  44:         public <#= proxyClassName #>()
  45:         {}
  46:     
  47:         /// <summary>
  48:         /// Gets the current culture
  49:         /// </summary>
  50:         [System.CodeDom.Compiler.GeneratedCode("<#= appName #>", "<#= version #>")]
  51:         public CultureInfo CurrentCulture 
  52:         { 
  53:             get { return CultureInfo.CurrentCulture; } 
  54:         }
  55:  
  56:         /// <summary>
  57:         /// Gets the current UI Culture
  58:         /// </summary>
  59:         [System.CodeDom.Compiler.GeneratedCode("<#= appName #>", "<#= version #>")]
  60:         public CultureInfo CurrentUICulture 
  61:         {
  62:             get { return CultureInfo.CurrentUICulture; } 
  63:         }
  64:         
  65:         /// <summary>
  66:         /// Gets the current Xml Language property
  67:         /// </summary>
  68:         [System.CodeDom.Compiler.GeneratedCode("<#= appName #>", "<#= version #>")]
  69:         public XmlLanguage Language 
  70:         { 
  71:             get { return XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.Name); } 
  72:         }
  73: <# foreach(var item in document.Element("root").Elements("data")) 
  74:    { 
  75:         string name = EscapeName(item);
  76:     
  77:         if (item.Attributes("type").Count() == 0)
  78:         {
  79: #>
  80:  
  81:         /// <summary>
  82: <# if (item.Elements("comment").Count() == 1) { #>
  83:         /// <remarks><#= item.Element("comment").Value #></remarks>
  84: <# } #>
  85:         /// Gets the "<#= name #>" Property
  86:         /// </summary>
  87:         [System.CodeDom.Compiler.GeneratedCode("<#= appName #>", "<#= version #>")]
  88:         public string <#= name #> 
  89:         { 
  90:             get { return <#= resxClassName + "." + name #>; } 
  91:         }
  92: <# 
  93:         }
  94: }
  95: #>
  96:     }
  97: }<#+
  98: public string EscapeName(XElement item)
  99: {
 100:     string name = item.Attribute("name").Value;
 101:     return Regex.Replace(name, "[^a-zA-Z0-9_]{1,1}", "_");
 102: }
 103: #>

Using the template is really easy. First of all you have to configure the Silverlight application to be localizable ( you can find how to do)  and then create the required resx files. Finally you have to add the template using the same name of the main resource file but using the .tt extension. E.g. if your resx is called AppString.resx you have to call the template AppStrings.tt. Nothing else. Once you have added the template it automatically generates a class called AppStringResourceProxy you can easily reference from a XAML file and bind to the UI. Remember that every time you change the resx file you have to select "Run custom tool" from the context menu of the T4 template to generate the code again.

The generated class does not contains only the string properties but also expose a Language property. You can bind this property to the corresponding Language property of the page (the UserControl) and override the default culture that is usually set to "en-US". Doing this you will have also the Dates and numeric values converted to the right culture.

   1: <UserControl Language="{Binding Language, Source={StaticResource AppString}}" />

The tamplate works fine for Silverlight and Windows Phone. I've never tryied but I think it works also with WPF. Hope this helps.

Download Sample: Silverlightplayground.Localization.zip (96,3 KB)

Demo from Italy Remix 2010

2010-07-01T22:44:49+01:00 by Andrea Boschin

Last June 23 I had the pleasure of presenting an introductive session about Silverlight at . During the presentation, included in the “jump start” track, I’ve illustrated the basic capabilities of Silverlight using a simple application that consume a RSS feed from the . The feed give news about all the Earthquakes happening on the world surface and all them are geo tagged.

So, during the session, I’ve showed the application growing from a simple reader to a complex and effective tool to display the earthquakes on a map, using the Silverlight control for Bing Maps. The people was impressed by the application, so I decided to publish the sample on my blog, hoping it can become a good starting point for people wants to approach the Silverlight development.

The application is really simple, without any surprise for skilled people, but it show the main points, from where someone must start. You can see layout, shapes, brushes, animations, projections, network access, syndication and data binding, but also show the power of an Out of browser application and the customization of the window chrome. I hope you enjoy the sample.

Download: code | live demo

Keeping an ObservableCollection sorted with a method override

2010-04-27T16:30:52+01:00 by Andrea Boschin

Usually the order of elements in a collection is something one does not feel to be important. We have plenty of methods in the Framework to order things and now that there is LINQ to Objects the need to sort something is really matter of seconds.

Since the introduction of LINQ the main problem is that you have many cases where a few lines of code are adding really complex iteration logic and often this hurts performances when the count of milliseconds is something important.

In these days I had this problem because I did need to make a lot of updates to a collection in the short time as possible, because I was working into the a CompositionTarget_Rendering event. A lot of hidden iterations through the collection, made by LINQ queries, has degraded the performances of the application and I had to put my nose on the iteration logic to optimize it.

In a case like this, having a collection that ensure a given order of the elements was key to solve my problem. So I decided to implement a SortedObservableCollection that helped me to minimize scans of the collection. Here is the simple code:

   1: public class SortedObservableCollection<T> : ObservableCollection<T>
   2:     where T : IComparable
   3: {
   4:     protected override void InsertItem(int index, T item)
   5:     {
   6:         for (int i = 0; i < this.Count; i++)
   7:         {
   8:             switch (Math.Sign(this[i].CompareTo(item)))
   9:             {
  10:                 case 0:
  11:                     throw new InvalidOperationException("Cannot insert duplicated items");
  12:                 case 1:
  13:                     base.InsertItem(i, item);
  14:                     return;
  15:                 case -1:
  16:                     break;
  17:             }
  18:         }
  19:  
  20:         base.InsertItem(this.Count, item);
  21:     }
  22: }

The trick has been done in a few minutes, just because the ObservableCollection exposes some overridable methods that enable the developer to change the Insert and Remove behavior. So I simply overriden the InsertItem method. I changed it trying to put elements in the right place, evaluating the position over the elements already in the collection. This helped me to move the sorting logic in another context, out of the Rendering event, and maximize the performances.

The code I wrote in this sample is really simple. I figure out you can improve again the performances of the InsertItem method implementing a binary search algorithm. But it is a little concern when you know where to act.

A proxy class to tunnel Socket channels

2009-04-01T23:00:00+01:00 by Andrea Boschin

One of the requests I found, following the Silverlight forums, is the capability to connect a Socket to an arbitrary port out of the restricted range where Silverlight is allowed to work. Since the release 2.0, Silverlight is allowed to connect to the network using a Socket but has a limitation that forbid the access to ports out of the range from 4502 to 4534. Another requirements for the sockets to work is that the same service must expose a clientaccesspolicy.xml file on a well known port 943 stating the ports opened by the service.

This complicated context let the use of the Sockets difficult and limitated to custom services. The limit of 32 ports opened is fine if someone has to create a custom service giving realtime updates, but is frustrating if the need is to access a well know network service like DNS, Telnet and other. We have to know that some network protocols are impossible to be implemented with Silverlight because of them nature. Thinking about FTP the duplex nature of this service, where server and client exchange their role (the client become server when receiving a file)  let this protocol impossible to be implemented in Silverlight where there is no way to listen to incoming connections. There is a number of other existing services, and often a number of existing custom services that rely on ports out of the 4502-4534 range and that we would like to consume without changing the code because often they are already published to other systems.

In this article I will explain a simple project I've implemented to let some services to be "tunneled" moving their original port to another port in the range allowed to Silverlight. This service that is freely downloadable at the end of the post, also implements a channel on the port 943 to allow Silverlight to get the client access policies before to connect to the tunneled ports.

The system Architecture

Before showing some snippets of code I will try to explain how my solution works. I will go through the architecture showing the components and the reason of my choice. The first concept you may know about implementing Sockets is that when you are listening on a port you are waiting for accepting clients and when a client connect you need to start a thread that take the accepted client and manage it separately from the main listening socket. This behavioh allow you to manage multiple incoming clients giving each connection in charge to a separate thread and leaving the port free to return a the listening task. Another requirement we have developing the solution for Silverlight is to have two servers listening, one for the policy distribution and the other on the proxy service that forward traffic to the required port.

To solve this problem I've created a base class ThreadedObject that implements the base behavior of every subsystem. From this class I've inherited three types that manage the different parts of the problem.

classes

The ThreadedObject class is in charge of managing the lifetime of the thread. It is a IDisposable class so it is created with an using construct and this cause the closing of the thread when the class goes out of scope. The trick is the usage of a ManualResetEvent exposed to the subclasses. When this event will be set the thread has to exit gracefully.

The SocketProxy class

This class have in charge the task to listen for incoming connections on both the policy port 943 and the proxy channel that we will expose on the port 4530. I've created this class thinking about a developer that needs to embed a listening proxy in a Windows service. It is a simple class that request a few parameters in input and when started run a thread that listen. As you may know the parameters tells the host and the ports where to listen and forward connections. In my solution this parameters are read from the app.config file. The constructor wants also the path of a policy file. Here is the main thread procedure of the class:

   1: /// <summary>
   2: /// Listen the thread.
   3: /// </summary>
   4: /// <param name="state">The state.</param>
   5: protected override void ThreadProc()
   6: {
   7:     IPHostEntry hostEntry = Dns.GetHostEntry(this.ListenHost);
   8:     IPAddress ipAddress = hostEntry.AddressList.Where(entry => entry.AddressFamily == AddressFamily.InterNetwork).FirstOrDefault();
   9:  
  10:     if (ipAddress != null)
  11:     {
  12:         this.PolicyListener = new TcpListener(ipAddress, 943);
  13:         this.PolicyListener.Start();
  14:         this.ProxyListener = new TcpListener(ipAddress, this.ListenPort);
  15:         this.ProxyListener.Start();
  16:  
  17:         while (WaitHandle.WaitTimeout == WaitHandle.WaitAny(this.ExitHandles, 10))
  18:         {
  19:             if (this.PolicyListener.Pending())
  20:             {
  21:                 PolicyClient policyClient =
  22:                     new PolicyClient(this.PolicyListener.AcceptTcpClient(), this.PolicyFile);
  23:                 policyClient.Start();
  24:  
  25:             }
  26:             else if (this.ProxyListener.Pending())
  27:             {
  28:                 ProxyClient proxyClient = 
  29:                     new ProxyClient(this.ProxyListener.AcceptTcpClient(), this.TargetHost, this.TargetPort);
  30:                 proxyClient.Start();
  31:             }
  32:         }
  33:     }
  34:     else
  35:         throw new ApplicationException(Resources.CannotResolveAddress);
  36: }

The class starts two instances of TcpListener and then poll the Pending() method to check when there is waiting clients to accept. When a client is ready it starts an instance of the managing type.

The PolicyClient

This class implements a manager for the clients requesting the policies. It will be started by the SocketProxy class when a client ask for a connection. The class operate in two steps: first of all it wait for the client to send the policy request string then it sends the policy file and finally close the connection:

   1: /// <summary>
   2: /// Threads the proc.
   3: /// </summary>
   4: protected override void ThreadProc()
   5: {
   6:     try
   7:     {
   8:         while (WaitHandle.WaitTimeout == WaitHandle.WaitAny(this.ExitHandles, 10) &&
   9:             this.Client.Available == 0) ;
  10:  
  11:         ReadAndSend();
  12:     }
  13:     finally
  14:     {
  15:         this.Client.Close();
  16:     }
  17: }
  18:  
  19: /// <summary>
  20: /// Validates the and send.
  21: /// </summary>
  22: /// <returns></returns>
  23: private void ReadAndSend()
  24: {
  25:     NetworkStream stream = this.Client.GetStream();
  26:  
  27:     byte[] data = new byte[PolicyRequestString.Length];
  28:     stream.Read(data, 0, data.Length);
  29:  
  30:     string readString = Encoding.UTF8.GetString(data);
  31:  
  32:     if (readString == PolicyRequestString)
  33:     {
  34:         stream.Write(this.PolicyFile, 0, this.PolicyFile.Length);
  35:         stream.Flush();
  36:     }
  37: }

The class request in the constructor a byte array of the policy file to send. This buffer is loaded by the SocketProxy class and then decoded to a byte array and passed to all the instances.

The ProxyClient

This is the most critical class that have in charge the clients when tunneling the traffic in both the directions. It will have two clients; the silverlight part and the tunneled service part. It simply wait for traffic from each client and forward it to the other. The operation of tunneling the data is the critical part. I read the data from a channel in 1024 bytes chunks and write the same bytes on the other client. I think this part has to be tested on various services and tuned finding the best strategy. The current implementation may fail when both parts of the channel is data intensive because reading data from a channel stops reading data from the other.

   1: /// <summary>
   2: /// Threads the proc.
   3: /// </summary>
   4: protected override void ThreadProc()
   5: {
   6:     try
   7:     {
   8:         while (WaitHandle.WaitTimeout == WaitHandle.WaitAny(this.ExitHandles, 10) &&
   9:             this.Client.Available == 0) ;
  10:  
  11:         ReadAndSend();
  12:     }
  13:     finally
  14:     {
  15:         this.Client.Close();
  16:     }
  17: }
  18:  
  19: /// <summary>
  20: /// Validates the and send.
  21: /// </summary>
  22: /// <returns></returns>
  23: private void ReadAndSend()
  24: {
  25:     NetworkStream stream = this.Client.GetStream();
  26:  
  27:     byte[] data = new byte[PolicyRequestString.Length];
  28:     stream.Read(data, 0, data.Length);
  29:  
  30:     string readString = Encoding.UTF8.GetString(data);
  31:  
  32:     if (readString == PolicyRequestString)
  33:     {
  34:         stream.Write(this.PolicyFile, 0, this.PolicyFile.Length);
  35:         stream.Flush();
  36:     }
  37: }

Testing the application

To test the class I've selected a service from the canonical ports exposed by a system.  Finally I've selected the daytime port 13. It is a service that on Windows has to be installed under the Simple TCP services and then started by the Services applet. The choice of this simple and slightly unuseful ports is only to give a very simple example that show how to expose a port that is really out of the Silverlight range. My simple application has a button and a TextBlock. When the Button is clicked a Socket connection will be made to appthe daytime service and the returned system time will be displayed in the TextBlock.

The server part is configured to tunnel connection incoming on port 4530 to the system dtaytime port and to serve a policy file from the file system.

I'm thinking to write a more powerful sample on this code. Opening ports to the silverlight applications may open a wide range of solutions limited only by your creativity and probably by your it manager. When exposing a port on the internet you have to concern about security issues. Every opened port to the network is a potential security disclosure but the security must be implemented in the protocol you are exposing. Also you have to know that exposing a well-know port may give an opportunity to use a well know bug to enter your system.

Download: Elite.Silverlight.SocketProxy.zip (1,1 MB)

Creating a MouseClickManager to handle single and double mouse clicks

2009-03-17T00:06:00+01:00 by Andrea Boschin

One of the missing things of Silverlight is the capability to handle mouse double click events. This problem apply not only to Silverlight 1.0 but also to Silverlight 2.0 Beta 1 and 2. Silverlight is rich about Mouse event handling but have two limitations. The first one is the missing right-mouse-button handling due to the presence of a contextual menu for configuration of the plugin. The second thing is the presence of mouse up and down left-button events but not of the click and double-click.

So, I decided to create a small class to handle this problem in a simply way. The class I created behave as a translator that receive mouse-up events and transform them in click/double-click. The only way to discriminate from single to double click is taking care of a brief timeout (300 ms) after the first incoming mouse-up event. If this timeout expires without another incoming mouse-up event we have to raise a click event. Instead, if a second mouse-up arrive we need to raise a double-click event.

Handling events in this way has a little bit problem. We need to receive an event and then wait for another event without blocking the caller and the user interface itself. With javascript I handled this problem using a setTimeout() that reset a flag and raise the correct event after the timeout. With Silverlight 2.0 we need to use a Thread because it is the only way to wait an event without blocking the main thread.

My MouseClickManager class handle the problem in this way. In the next code block I show the main methods of the class:

   1: /// <summary>
   2: /// Handles the click.
   3: /// </summary>
   4: /// <param name="sender">The sender.</param>
   5: /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
   6: public void HandleClick(object sender, MouseButtonEventArgs e)
   7: {
   8:     lock(this)
   9:     {
  10:         if (this.Clicked)
  11:         {
  12:             this.Clicked = false;
  13:             OnDoubleClick(sender, e);
  14:         }
  15:         else
  16:         {
  17:             this.Clicked = true;
  18:             ParameterizedThreadStart threadStart = new ParameterizedThreadStart(ResetThread);
  19:             Thread thread = new Thread(threadStart);
  20:             thread.Start(e);
  21:         }
  22:     }
  23: }
  24:  
  25: /// <summary>
  26: /// Resets the clicked flag after timeout.
  27: /// </summary>
  28: /// <param name="state">The state.</param>
  29: private void ResetThread(object state)
  30: {
  31:     Thread.Sleep(this.Timeout);
  32:  
  33:     lock (this)
  34:     {
  35:         if (this.Clicked)
  36:         {
  37:             this.Clicked = false;
  38:             OnClick(this, (MouseButtonEventArgs)state);
  39:         }
  40:     }
  41: }

In the HandleClick method we receive the incoming events. Probably the event handler of the MouseLeftButtonUp event simply call this method passing sender and arguments. In this method first of all we need to acquire a lock on a shared resource. This resource is the "clicked" flag that indicate if we are handling the first or second event. After acquiring the lock we have two choices. If the clicked flag is set to false we are handling the first click event so we need to set the flag and start a thread that will wait 300 milliseconds before reset the flag. So if the clicked flag has not been reset when we receive the second mouse-up then we are handling a double click event. 

This may appear simply, but it has a little drawback. When we need to raise the single-click event, we are running in a separate thread so we may incur in a cross thread situation and we need to marshal the thread context to the main thread itself to avoid this condition. This is a common problem in windows forms environment and also in WPF. To handle the problem we have to use the Dispatcher object. In this code snippet I show a brief example:

   1: /// <summary>
   2: /// Called when click occure.
   3: /// </summary>
   4: /// <param name="sender">The sender.</param>
   5: /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
   6: private void OnClick(object sender, MouseButtonEventArgs e)
   7: {
   8:     MouseButtonEventHandler handler = Click;
   9:  
  10:     if (handler != null)
  11:         this.Control.Dispatcher.BeginInvoke(handler, sender, e);
  12: }

"Control" is a reference to the control that we have to notify the event. So we will user the Control.Dispatcher.BeginInvoke() method to marshal the event. To use my class MouseClickManager you have simply to crate an instance passing a reference to the control that will receive the click/double click events. Than in the MouseLeftButtonUp event you will call the HandleClick method. The event handler connected to Click and DoubleClick events will be called appropriately.

The class has been designed to configure the the timeout lenght. Some experiments revealed that 200 ms is less, but 400 ms is too much because we will begin to feel the click event delay.

Download: http://www.codeplex.com/SilverlightLibrary

How to create a custom RadialPanel for Silverlight 2.0

2009-03-17T00:03:00+01:00 by Andrea Boschin

Silverlight 2.0 has introduced some new layout controls that match some controls from Windows Presentation Foundation. Grid, and StackPanel can handle the most of cases but there are many other cases where they are not useful and we have to return to the Canvas control and handle the resize event to get our custom layout to work.

Many people do not know that in Silverlight 2.0 it is possible to create a custom layout control and use it in XAML markup exactly like a usual layout control. Implementing a custom layout control is easy and require only that we are able to write the layout logic in the 2-pass layout system of Silverlight.

When the Silverlight runtime encounter a Layout control, it start a 2-pass algorithm to define measure and arrangement of the child controls. The first pass work to define the size of each element. During this passage the runtime calls the Measure method and collect the size requested by each control. When the first pass is completed and all the nodes of the XAML document has been visited and measured the runtime start to call the Arrange method where it request each control to do its own layout hierarchically. Every control has to perform layout of his children calling the Arrange method and define the position of every element.

In this sample I will show how to create a custom layout arranging the children in Radial positions. This control will be called RadialPanel and we will have to implement at least one of the two layout passages of the runtime to have it to work.

For a RadialPanel we have also to define two Attached Properties. This properties will contains the Angle and Distance from center for each children element. During the arrangement of control we will use this properties to define the position of the elements calculating it respect the center of the panel using a simple formula. So let's start our RadialPanel control creating the class

   1: public class RadialPanel : Panel
   2: {
   3: }

To create a layout control we have to inherits from the Panel class that is the base class for Canvas, Grid and StackPanel. This class handle the biggest part of the layout system and define two methods that we may override to implements our custom layout rule. Now that we have created the layout control we have to define the dependency properties:

   1: public static readonly DependencyProperty DistanceProperty = 
   2:     DependencyProperty.RegisterAttached("Distance", typeof(double), typeof(RadialPanel), null);
   3: public static readonly DependencyProperty AngleProperty = 
   4:     DependencyProperty.RegisterAttached("Angle", typeof(double), typeof(RadialPanel), null);

These declarations create two Dependency Properties that we register as Attached Properties using the RegisterAttached static method of the DependencyProperty class. This method require us to specify the property name (as we will write in XAML markup), the property type, the type of containing class and if needed a PropertyChangedCallback delegate that the runtime will call when the property changes its value. We have also to create four methods - two Set methods and two Get methods - that will Set and Get the value to and from the UIElement contained in the Panel. This is a simple operation:

   1: /// <summary>
   2: /// Sets the distance.
   3: /// </summary>
   4: /// <param name="element">The element.</param>
   5: /// <param name="distance">The distance.</param>
   6: public static void SetDistance(UIElement element, double distance)
   7: {
   8:     element.SetValue(DistanceProperty, distance);
   9: }
  10:  
  11: /// <summary>
  12: /// Gets the distance.
  13: /// </summary>
  14: /// <param name="element">The element.</param>
  15: /// <returns></returns>
  16: public static double GetDistance(UIElement element)
  17: {
  18:     return (double)element.GetValue(DistanceProperty);
  19: }
  20:  
  21:  
  22: /// <summary>
  23: /// Sets the angle.
  24: /// </summary>
  25: /// <param name="element">The element.</param>
  26: /// <param name="distance">The distance.</param>
  27: public static void SetAngle(UIElement element, double distance)
  28: {
  29:     element.SetValue(AngleProperty, distance);
  30: }
  31:  
  32: /// <summary>
  33: /// Gets the angle.
  34: /// </summary>
  35: /// <param name="element">The element.</param>
  36: /// <returns></returns>
  37: public static double GetAngle(UIElement element)
  38: {
  39:     return (double)element.GetValue(AngleProperty);
  40: }

Now it is time to implement our custom layout logic. The Panel class expose two methods to override to customize the layout algorithm. These methods are MeasureOverride, called during the first pass and ArrangeOverride called during the second pass. For our RadialPanel to work whe need only to override the ArrangeOverride method because the default implementation of MeasureOverride is perfect for us. Here is the implementation of this method:

   1: /// <summary>
   2: /// When implemented in a derived class, provides the behavior for the "Arrange" pass of Silverlight layout.
   3: /// </summary>
   4: /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
   5: /// <returns>The actual size used.</returns>
   6: protected override Size ArrangeOverride(Size finalSize)
   7: {
   8:     foreach (UIElement element in this.Children)
   9:     {
  10:         double distance = (double)element.GetValue(DistanceProperty);
  11:         double angle = Math.PI / 180 * (double)element.GetValue(AngleProperty);
  12:  
  13:         double x = distance * Math.Cos(angle);
  14:         double y = distance * Math.Sin(angle);
  15:  
  16:         element.Arrange(new Rect(x, y, finalSize.Width, finalSize.Height));
  17:     }
  18:  
  19:     return base.ArrangeOverride(finalSize);
  20: }

The code is very simple. We loop all the UIElement in the Children collection and for every element found we acquire its angle and distance property. The angle property need to be converted from degree to radians because we have to use this parameter in the trigonometric functions Math.Sin() and Math.Cos(). The next lines calculate the x and y position referred to the panel center. Finally we call the Arrange method on the UIElement passing the rectangle defining position and size granted to the element itself. As size we simply pass the entire area. It is not an our concern the size the element will have. It may fill the entire panel.

Now that the RadialPanel has been completed we need to use it in a XAML markup. The first thing to do is to reference the assembly in the XAML file. It has to be done by inserting the namespace declaration in the UserControl root element of the XAML page:

   1: <UserControl
   2:     x:Class="Elite.Silverlight.Samples.RadialPanelSample"
   3:     xmlns="http://schemas.microsoft.com/client/2007" 
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   5:     xmlns:ec="clr-namespace:Elite.Silverlight.Controls;assembly=Elite.Silverlight"
   6:     Width="Auto" Height="Auto">
   7:  
   8: </UserControl>

In the namespace declaration we have to insert also the "assembly=Elite.Silverlight" part of the declaration otherwise the AttachedProperties will not work. It appears there is an issue in the Silverlight runtime affecting this kind of properties. Now we have to insert the RadialPanel in the markup. It is needed we use the declared namespace "ec" and Visual Studio 2008 will show us a powerful intellisense window.

   1: <UserControl 
   2:     x:Class="Elite.Silverlight.Samples.RadialPanelSample"
   3:     xmlns="http://schemas.microsoft.com/client/2007" 
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   5:     xmlns:ec="clr-namespace:Elite.Silverlight.Controls;assembly=Elite.Silverlight"
   6:     Width="Auto" Height="Auto">
   7:  
   8:     <ec:RadialPanel>
   9:         
  10:     </ec:RadialPanel>
  11:     
  12: </UserControl>

Finally we may insert some TextBloks specifing the Angle and distance properties. We have to specify the namespace too using the Attached Properties to have is working.

   1: <UserControl x:Class="Elite.Silverlight.Samples.RadialPanelSample"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:ec="clr-namespace:Elite.Silverlight.Controls;assembly=Elite.Silverlight"
   5:     Width="Auto" Height="Auto">
   6:     <ec:RadialPanel>
   7:         <TextBlock Text="0" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="0" Width="50" Height="50" />
   8:         <TextBlock Text="45" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="45" Width="50" Height="50" />
   9:         <TextBlock Text="90" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="90" Width="50" Height="50" />
  10:         <TextBlock Text="135" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="135" Width="50" Height="50" />
  11:         <TextBlock Text="180" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="180" Width="50" Height="50" />
  12:         <TextBlock Text="225" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="225" Width="50" Height="50" />
  13:         <TextBlock Text="270" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="270" Width="50" Height="50" />
  14:         <TextBlock Text="315" ec:RadialPanel.Distance="100" ec:RadialPanel.Angle="315" Width="50" Height="50" />
  15:     </ec:RadialPanel>    
  16: </UserControl>

The example is now complete and it will show eight Textblock arranged in a circle around the center of the layout panel. Each Textblock Text property contains the angle we have specified to show how the panel works.

I think that custom layout will be very powerful. Thanks to this kind of extensibility we can easily fill the gap between Silverlight 2.0 and WPF layout system. Probably is not so hard to implement a DockPanel and a WrapPanel using this tecnique.

Conclusion

The code I shown in this article has been added to the Elite.Silverlight library I've uploaded to codeplex at this address. I think I will add all the working samples to this library because I think that it is useful to have a library exposing this useful controls and code snippets.

Link: http://www.codeplex.com/silverlightlibrary (release 1.1.3043.0)

Tags:   , ,
Categories:   Layout
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Improving Silverlight 2.0 databinding with a CollectionViewSource control

2009-03-16T23:59:00+01:00 by Andrea Boschin

CollectionViewSource screenshot People that have worked with Windows Presentation Foundation probably know the usefulness of the CollectionViewSource control. This control is simple to be configured and enables to easily filter, sort and group data items before binding them to an ItemsControl, without any need to change the underlying datasource. Many times we have to operate filtering on a datasource before displaying it in a listbox, or we need to sort the same data answering to a user request. Both this behaviors may be handled using the CollectionViewSource; filtering will raise an event asking to choice the items to show or to hide and we have simply to select the elements with the correct criteria. On the other side, the control can sort elements on one or more properties ascending or descending. If the target control support grouping we can also specify a grouping criteria and the control will operate seamless for us. But probably the better thing is that we can change sorting, filtering or grouping and the target control will be updated according to the new criteria.

Unfortunately Silverlight 2.0 does not have a CollectionViewSource but while exploring the capabilities of databinding I found that it is possible to build a control similar to the CollectionViewSource that support both filtering and sorting. There aren't controls supporting grouping in Silverlight 2.0 so it is unuseful to implement it in this control. Before getting to know how the control works it is better to understand how databinding works in Silverlight 2.0 so let me begin with a brief introduction to this argument.

Databinding in Silverlight 2.0

Databinding is a feature introduced many years ago so there is no need to explain what it is. Windows Presentation Foundation has taken this ancient tecnique to an high level of simplicity and Silverlight 2.0 implements a databinding capability very close to WPF with some little limitations.

The two main components of databinding are DataContext and the Binding markup extension. DataContext is a property, exposed by all the classes inheriting from FrameworkElement so it is available on all the controls, shapes, panels and other user interface elements. Through DataContext property we may specify a class instance that will be the source of databinding for the target element and for all his children. The DataContext will propagate to children elements until the end of the hierarchy or until another DataContext will be encountered. All the elements of the hierarchy may be binded with a property of the object that is associated to the DataContext. To bind the properties each other we have to use the Binding markup extension. In the simplest form the markup extension require that we specify the property of the datasource to bind to.

Another way to bind to a user interface control is using an ItemsControl. Connecting a datasource to its ItemsSource property will cause the collection to be displayed in the control repeating a DataTemplate for every item in the collection. The DataTemplate will be binded to each element throught the Binding markup extension itself. Here is an example showing both the tecniques applied.

   1: <UserControl x:Class="Elite.Silverlight.Samples.Databinding"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:d="clr-namespace:Elite.Silverlight.Samples"
   5:     Width="400" Height="300">
   6:     <UserControl.Resources>
   7:         <d:DataSource x:Key="data" />
   8:     </UserControl.Resources>
   9:     <Grid x:Name="LayoutRoot" DataContext="{StaticResource data}" Background="White">
  10:         <ItemsControl ItemsSource="{Binding Strings}">
  11:             <ItemsPanelTemplate>
  12:                 <DataTemplate>
  13:                     <TextBlock Text="{Binding}" />
  14:                 </DataTemplate>
  15:             </ItemsPanelTemplate>
  16:         </ItemsControl>
  17:     </Grid>
  18: </UserControl>

In this sample an instance of a class named DataSource has been referenced in the user control resources. This class has a property named "Strings" returning an IEnumerable<string>. The yielded strings is the data to be displayed in the ItemsControl. The DataContext property of the Grid is assigned to a reference of the DataSource class and the ItemsSource property is binded to the Strings property throught the Binding markup extension. The data collected from the datasource become the DataContext for the each item with the template specified in ItemsTemplatePanel so the Binding markup extension assign each string to the Text property of the TextBlock.

For the purpose of this article we don't need to deepen our knowledge of this aspect. Instead, we need to concentrate on another aspect. Databinding enable developers to bind properties of a control to properties of a data item and have them updated when the data item change. In the same way it is possible to bind a collection to a specific property and have the control updated when we add or remove items to the collection. This two behaviors came from two interfaces: INotifyPropertyChanged and INotifyCollectionChanged. Objects implementing one of these interfaces have to notify internal changes through an event. PropertyChanged notify changes to single properties and is useful for objects binded directly to control properties, instead CollectionChanged notify about adding or removal of items from a collection and it has to be used for controls binded to collections like an ItemsControl.

Silverlight 2.0 and WPF come with a generic collection ObservableCollection<T> that implements INotifyCollectionChanged interface. This class helps us to bind collection of objects to an ItemsControl. We may create a class, expose a property of this type and bind it to an ItemsControl. Then we can add or remove elements to the collection and them will be added to or removed from the ItemsControl seamless.

Thinking about our CollectionViewSource

It is now the time to think about how to implement our CollectionViewSource. We need to create the control as a filter that will be interposed between the datasource and the consuming control and it will operate notifying changes responding to user choices. So probably our control has to appear as a collection for  the control that will displays the data items. To notify changes we need to implement the INotifyCollectionChanged interface so we get the consuming control to update its appearance responding to changes to the filtering or sorting criteria. Watching to the control from the side of the datasource it has to expose a Source property that we'll connect to the underlying datasource we need to transform.

So let start creating the control. It will be called CollectionViewSource as expected and extends the Control class. We cannot create a simple class that does not implement any other base class because we need to get access to the DependencyProperty system. The Control class is tipically dedicated to elements that have a visual aspect, so probably it would be better we extend a class like DependencyObject for our control to work. Unfortunately we have to use the Control class because all the base classes from FrameworkElement to DependencyObject does not have a public constructor so we are unable to inherit from them. The Control class is not the best choice, but it is the only choice available.

   1: public class CollectionViewSource : Control, IEnumerable, INotifyCollectionChanged
   2: {
   3:     // implement the control... 
   4: }

The class has also to implement two interfaces: IEnumerable and INotifyCollectionChanged. The first interface is required to grant our control the capability to be consumed from an ItemsControl. Using the IEnumerable interface we get two goals. We can assign the CollectionViewSource directly to the ItemsSource property and we can use LINQ to Objects to yield resulting items to the consuming control. Taking advantage of LINQ to Objects will simplify our control structure and get our code to a better level of readability.

   1: #region IEnumerable Members
   2:  
   3: /// <summary>
   4: /// Returns an enumerator that iterates through a collection.
   5: /// </summary>
   6: /// <returns>
   7: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
   8: /// </returns>
   9: public IEnumerator GetEnumerator()
  10: {
  11:     var result = this.Source
  12:         .Cast<object>();
  13:  
  14:     return result.GetEnumerator();
  15: }
  16:  
  17: #endregion

For the filtering to work we have to expose an event that we'll call every time we have to filter the collection, one time for every item in the collection asking the user to choice the items to show. The simplest thing to do is to create a delegate, that we use as a Where clause lambda expression in the Linq query yielding items. So the delegate will be called by the query every time it needs to examine an element to decide if it has to be yielded or skipped.

   1: /// <summary>
   2: /// Occurs when [filter].
   3: /// </summary>
   4: public event EventHandler<FilterCollectionEventArgs> Filter;
   5:  
   6: /// <summary>
   7: /// Returns an enumerator that iterates through a collection.
   8: /// </summary>
   9: /// <returns>
  10: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
  11: /// </returns>
  12: public IEnumerator GetEnumerator()
  13: {
  14:     var result = this.Source
  15:         .Cast<object>()
  16:         .Where(FilterItem);
  17:  
  18:     return result.GetEnumerator();
  19: }
  20:  
  21: /// <summary>
  22: /// Filters the item.
  23: /// </summary>
  24: /// <param name="o">The o.</param>
  25: /// <returns></returns>
  26: private bool FilterItem(object o)
  27: {
  28:     FilterCollectionEventArgs args = new FilterCollectionEventArgs(o);
  29:     OnFilter(args);
  30:     return args.Accept;
  31: }
  32:  
  33: /// <summary>
  34: /// Raises the <see cref="E:Filter"/> event.
  35: /// </summary>
  36: /// <param name="e">The <see cref="Elite.Silverlight.FilterCollectionEventArgs"/> instance containing the event data.</param>
  37: protected virtual void OnFilter(FilterCollectionEventArgs e)
  38: {
  39:     EventHandler<FilterCollectionEventArgs> handler = Filter;
  40:  
  41:     if (handler != null)
  42:         handler(this, e);
  43: }

To configure the sorting behavior we have to expose a property that collect the sorting criteria in a way similar to the CollectionViewSource contained in the Windows Presentation Foundation. This require to create a SortDescriptionCollection class that will collect the SortDescription instances. Every SortDescription will require a PropertyName and a Direction property to tell the control wich property use for sorting and the direction of the sorting. All the names I've proposed come directly from the WPF control. Using this namig will grant our CollectionViewSource the maximum compatibility with the WPF control so we may use code snippet created for WPF in Silverlight 2.0.

We use the same LINQ to Objects expression adding OrderBy and ThenBy calls to the query if the sorting is enabled. The sorting will be called immediately after the Where clause.

   1: /// <summary>
   2: /// Returns an enumerator that iterates through a collection.
   3: /// </summary>
   4: /// <returns>
   5: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
   6: /// </returns>
   7: public IEnumerator GetEnumerator()
   8: {
   9:     var result = this.Source
  10:         .Cast<object>()
  11:         .Where(FilterItem);
  12:  
  13:     foreach (SortDescription sd in this.SortDescriptions)
  14:     {
  15:         if (sd.Direction == ListSortDirection.Ascending)
  16:             result = AppendOrderBy(result, sd.PropertyName);
  17:         else
  18:             result = AppendOrderByDescending(result, sd.PropertyName);
  19:     }
  20:  
  21:     return result.GetEnumerator();
  22: }
  23:  
  24: /// <summary>
  25: /// Appends the order by.
  26: /// </summary>
  27: /// <param name="result">The result.</param>
  28: /// <param name="propertyName">Name of the property.</param>
  29: /// <returns></returns>
  30: private IEnumerable<object> AppendOrderBy(IEnumerable<object> result, string propertyName)
  31: {
  32:     if (result is IOrderedEnumerable<object>)
  33:         return ((IOrderedEnumerable<object>)result).ThenBy(o => GetPropertyValue(o, propertyName));
  34:  
  35:     return result.OrderBy(o => GetPropertyValue(o, propertyName));
  36: }
  37:  
  38: /// <summary>
  39: /// Appends the order by descending.
  40: /// </summary>
  41: /// <param name="result">The result.</param>
  42: /// <param name="propertyName">Name of the property.</param>
  43: /// <returns></returns>
  44: private IEnumerable<object> AppendOrderByDescending(IEnumerable<object> result, string propertyName)
  45: {
  46:     if (result is IOrderedEnumerable<object>)
  47:         return ((IOrderedEnumerable<object>)result).ThenByDescending(o => GetPropertyValue(o, propertyName));
  48:  
  49:     return result.OrderByDescending(o => GetPropertyValue(o, propertyName));
  50: }

With the INotifyCollectionChanged interface we'll notify the control to update his aspect when changes occur. The only thing we have to take care is the way to notify events. The NotifyCollectionChangedEventArgs class we have to use to raise the CollectionChanged event require us to specify the kind of event we are raising. The constructor of this class need to specify a NotifyCollectionChangedAction enumeration parameter that define if we are updating the datasource for Add, Remove, Replace, Move or Reset. To get the ItemsControl updating we need to specify the NotifyCollectionChangedAction.Reset value because every other value does not have an effect on the ItemsControl. This behavior become from the implementation of the ItemsSource property. Inspecting this property with Lutz Roeder Reflector it will show us that it is wrapped with an internal private EnumerableCollectionView that will mask every other type of event. The drawback of this choice is that the target control will be updated entirely instead of handling only the added or removed items. I hope that a future implementation of this control will change this thing, but now we have to accept this behavior to get the CollectionViewSource working.

We have to handle two type of events that raise the CollectionChanged event. The first one is when the underliyng collection raise the same event. This is because if the datasource change we have to reflect this change to the user interface itself forwarding this event. So when the Source property will be assigned we have to hook up its CollectionChanged event (if the source itself implements the INotifyCollectionChanged). For this purpose we create the source property as a DependencyProperty to take advantage of the PropertyChangedCallback that will notify when the Source property change its value. When the value change we have to remove event from a previous instance before to hook the new instance to avoid memory leaks and unexpected results.

   1: public static readonly DependencyProperty SourceProperty =
   2:     DependencyProperty.Register("Source", typeof(IEnumerable), typeof(CollectionViewSource), new PropertyChangedCallback(SourceProperty_Changed));
   3:  
   4: /// <summary>
   5: /// Gets or sets the source.
   6: /// </summary>
   7: /// <value>The source.</value>
   8: public IEnumerable Source
   9: {
  10:     get { return (IEnumerable)this.GetValue(SourceProperty); }
  11:     set { this.SetValue(SourceProperty, value); }
  12: }
  13:  
  14: /// <summary>
  15: /// Sources the property_ changed.
  16: /// </summary>
  17: /// <param name="sender">The sender.</param>
  18: /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
  19: private static void SourceProperty_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  20: {
  21:     CollectionViewSource cvs = sender as CollectionViewSource;
  22:  
  23:     if (cvs != null)
  24:     {
  25:         if (e.OldValue != null && e.OldValue is INotifyCollectionChanged)
  26:             ((INotifyCollectionChanged)e.OldValue).CollectionChanged -= new NotifyCollectionChangedEventHandler(cvs.CollectionViewSource_CollectionChanged);
  27:  
  28:         if (e.NewValue != null && e.NewValue is INotifyCollectionChanged)
  29:             ((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(cvs.CollectionViewSource_CollectionChanged);
  30:     }
  31: }
  32:  
  33: /// <summary>
  34: /// Handles the CollectionChanged event of the CollectionViewSource control.
  35: /// </summary>
  36: /// <param name="sender">The source of the event.</param>
  37: /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
  38: private void CollectionViewSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  39: {
  40:     if (sender != null)
  41:         this.OnCollectionChanged(e);
  42: }
  43:  
  44: /// <summary>
  45: /// Raises the <see cref="E:CollectionChanged"/> event.
  46: /// </summary>
  47: /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
  48: protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  49: {
  50:     NotifyCollectionChangedEventHandler handler = CollectionChanged;
  51:  
  52:     if (handler != null)
  53:         handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  54: }

Last but not least we have to expose a method to enable users to force the CollectionViewSource to refresh the criteria and notifying the changes if needed. This is the second type of event we have to handle to notify the collection change. If the user call this method we will assume that something has been changed in the criteria and we raise the CollectionChanged event.

   1: /// <summary>
   2: /// Refreshes this instance.
   3: /// </summary>
   4: public void Refresh()
   5: {
   6:     this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
   7: }

Using the CollectionViewSource control

Now that the control has been completed we have to build a sample project that use the CollectionViewSource. For this purpose I have created an application that read the rss feed of my weblog (http://blog.boschin.it) and display it in an ItemsControl. The interface have a TextBox that we use to filter the post by title and two radio buttons that decide if sort the items ascending or descending by the publish date. The application is very simple but its purpose is only to dimostrate how to use the CollectionViewSource control and how simple is to use and customize its behavior. Here is a brief markup defining the application interface:

   1: <UserControl x:Class="Silverlight.Page"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:s="clr-namespace:Silverlight" Loaded="UserControl_Loaded"
   5:     xmlns:e="clr-namespace:Elite.Silverlight;assembly=Elite.Silverlight"
   6:     xmlns:ed="clr-namespace:Elite.Silverlight.Data;assembly=Elite.Silverlight"
   7:     Width="400" Height="400">
   8:     <UserControl.Resources>
   9:         <s:DataSource x:Name="dataSource" />
  10:         <ed:CollectionViewSource x:Name="rssView" Source="{Binding Items, Source={StaticResource dataSource}}" Filter="rssView_Filter">
  11:             <CollectionViewSource.SortDescriptions>
  12:                 <ed:SortDescription PropertyName="PublishDate" Direction="Ascending" />
  13:             </CollectionViewSource.SortDescriptions>
  14:         </ed:CollectionViewSource>
  15:         <e:StringToUriConverter x:Name="stringToUri" />
  16:         <e:FormatDateTimeConverter x:Name="dateToString" />
  17:     </UserControl.Resources>
  18:     <Grid x:Name="LayoutRoot" Background="#ffffffff">
  19:         <Grid.RowDefinitions>
  20:             <RowDefinition Height="*" />
  21:             <RowDefinition Height="80" />
  22:         </Grid.RowDefinitions>
  23:         <Grid.Resources>
  24:             <DataTemplate x:Name="rssTemplate">
  25:                 <Border Background="#ffeeeeee" Margin="2,2,2,0" Padding="5" BorderBrush="#00000000" BorderThickness="1" CornerRadius="3,3">
  26:                     <Grid>
  27:                         <Grid.ColumnDefinitions>
  28:                             <ColumnDefinition Width="50*"/>
  29:                             <ColumnDefinition Width="50*"/>
  30:                         </Grid.ColumnDefinitions>
  31:                         <Grid.RowDefinitions>
  32:                             <RowDefinition Height="60*" />
  33:                             <RowDefinition Height="40*" />
  34:                         </Grid.RowDefinitions>
  35:                         <HyperlinkButton x:Name="itemLabel" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="3,3,3,3" 
  36:                                          Content="{Binding Title.Text}" NavigateUri="{Binding Id, Converter={StaticResource stringToUri}}" 
  37:                                          TargetName="_blank" FontSize="14" FontFamily="Verdana" Foreground="#ffff9900" />
  38:                         <TextBlock Grid.Column="0" Grid.Row="1" Margin="3,3,3,3" FontSize="14" FontFamily="Verdana" 
  39:                                    Foreground="#ff666666" Text="{Binding PublishDate, Converter={StaticResource dateToString}, ConverterParameter=dd.MM.yyyy - hh:mm}" />
  40:                         <TextBlock Grid.Column="1" Grid.Row="1" Margin="3,3,3,3" FontSize="14" FontFamily="Verdana"
  41:                                    Foreground="#ff666666" Text="{Binding Copyright}" />
  42:                     </Grid>
  43:                 </Border>
  44:             </DataTemplate>
  45:         </Grid.Resources>
  46:         <Border Grid.Column="0" Grid.Row="0" Margin="2" BorderBrush="#ffff9900" BorderThickness="1" CornerRadius="3,3">
  47:             <ScrollViewer DataContext="{StaticResource dataSource}" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
  48:                 <ItemsControl ItemsSource="{Binding Source={StaticResource rssView}}" ItemTemplate="{StaticResource rssTemplate}">
  49:                 </ItemsControl>
  50:             </ScrollViewer>
  51:         </Border>
  52:         <Grid Grid.Column="0" Grid.Row="1" Background="#55ff9900" Margin="2">
  53:             <Grid.ColumnDefinitions>
  54:                 <ColumnDefinition Width="*"/>
  55:                 <ColumnDefinition Width="*"/>
  56:             </Grid.ColumnDefinitions>
  57:             <Grid.RowDefinitions>
  58:                 <RowDefinition Height="50*" />
  59:                 <RowDefinition Height="50*" />
  60:             </Grid.RowDefinitions>
  61:             <RadioButton x:Name="sortDescending" TextAlignment="Right" HorizontalAlignment="Left" Margin="10" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="1" Content="Descending" GroupName="sort" Checked="sort_Checked" IsChecked="true" />
  62:             <RadioButton x:Name="sortAscending" TextAlignment="Left" HorizontalAlignment="Right" Margin="10" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="1" Content="Ascending" GroupName="sort" Checked="sort_Checked" />
  63:             <WatermarkedTextBox x:Name="txtKey" Padding="3" Grid.Column="0" Grid.Row="0" Margin="5" FontSize="14" Grid.ColumnSpan="2" TextChanged="txtKey_TextChanged" Watermark="Insert Keywork..." />
  64:         </Grid>
  65:     </Grid>
  66: </UserControl>

In this code snippet it is visible how to bind correctly the datasource to the CollectionViewSource and the CollectionViewSource itselft to the consuming ItemsControl. When we have established this connection we hook up the filtering event where we use a Contains() to check that the element contains the text written in the textbox. Every time we press a key the TextChanged event simply call the Refresh() method of the CollectionViewSource causing the filtering to happen again with the new text entered. This is a very intensive use of the  CollectionViewSource but with a few collection items (up to one or two hundred) there will be no side effects. In a production application this scenario probably will be handled in a different way, but I'm sure there is many other times you may take advantage of my control. The sorting happen in a similar way. When a radio button has been hit we swap the direction of the configured sorting and then recall the Refresh() method to rebind the interface. Try changing the SortDescription properties to sort by another property.

   1: public partial class Page : UserControl
   2: {
   3:     public Page()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     private void UserControl_Loaded(object sender, RoutedEventArgs e)
   9:     {
  10:         this.dataSource = this.Resources["dataSource"] as DataSource;
  11:         this.dataSource.Load();
  12:     }
  13:  
  14:     private void sort_Checked(object sender, RoutedEventArgs e)
  15:     {
  16:         CollectionViewSource rssView = this.Resources["rssView"] as CollectionViewSource;
  17:  
  18:         if (rssView != null)
  19:         {
  20:             SortDescription sort = rssView.SortDescriptions[0];
  21:             sort.Direction = (sender == sortAscending ? ListSortDirection.Ascending : ListSortDirection.Descending);
  22:             rssView.Refresh();
  23:         }
  24:     }
  25:  
  26:     private void txtKey_TextChanged(object sender, TextChangedEventArgs e)
  27:     {
  28:         rssView.Refresh();
  29:     }
  30:  
  31:     private void rssView_Filter(object sender, Elite.Silverlight.FilterCollectionEventArgs e)
  32:     {
  33:         SyndicationItem item = e.Item as SyndicationItem;
  34:  
  35:         if (item != null)
  36:         {
  37:             e.Accept = item.Title.Text.ToLower().Contains(txtKey.Text.ToLower()) || txtKey.Text == txtKey.Watermark || txtKey.Text == string.Empty;
  38:         }
  39:     }
  40: }

Now we may run the application and try to enter some text in the TextBox. We will see that the displayed item will change according to the text entered. Also checking and unchecking the sort direction radio button will change the order of the the items.

Conclusion

The CollectionViewSource control is part of my Elite.Silverlight Library 1.0.3037.0 where I'm collecting all my reusable controls and classes. Please download it from codeplex (http://www.codeplex.com/silverlightlibrary) and use for free in your projects. The project come under the CCPL license and is free for non-commercial purposes. For use in commercial project please contact me on my weblog contact form at (http://blog.boschin.it/contact.aspx).