Exchanging Data between Silverlight 3.0 User Controls using Dependency Properties and Events
A week ago, I was discussing with one of my clients about developing Silverlight user controls and exchange data between them. There were a few interesting points that came up during the discussion and I thought this would make a good article. In this article, we will see how to exchange data between Silverlight 3.0 user controls.
In my previous article, we discussed creating Silverlight 3.0 user controls and developing data driven applications. In this article, we will create two Silverlight 3.0 controls, the details of which are mentioned below:
- SearchUserControl: This control will be used to search and filter data from collection.
- DetailedControl: This control will be used to display data filtered by the ‘SearchUserControl’.
In this article, I have created a WCF service, which will be used to fetch data from database.
Step1: Open VS2008 and create a blank solution, name this as ‘SILV3_UserControlSharedData’.
Step 2: To this solution, add a new Silverlight Class Library Project, name it as ‘SILV_SearchUserControl’. You will get ‘Class1.cs’ > delete it and add a new Silverlight User Control in this project and name it as ‘SearchValue.xaml’ as shown below:
Step 3: In ‘SearchValue.xam’ write the following xaml code:
<UserControl x:Class="SILV_SearchUserControl.SearchValue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="50">
<Grid x:Name="LayoutRoot" Background="White" Width="400" Height="50">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="Search Data:" Grid.Column="0" Margin="1,1,1,1"
FontFamily="Times New Roman" FontSize="27"
Foreground="Red" FontWeight="Bold"></TextBlock>
<TextBox x:Name="txtSearch" Grid.Column="1" Margin="1,1,1,1"
TextChanged="txtSearch_TextChanged" Height="40" Width="100"></TextBox>
</Grid>
</UserControl>
UI of the above xaml will be as below:
Step 4: Open ‘SearchValue.xaml.cs’ and write the following code:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SILV_SearchUserControl
{
public partial class SearchValue : UserControl
{
public SearchValue()
{
InitializeComponent();
}
public string strData
{
get { return (string)GetValue(strDataProperty); }
set { SetValue(strDataProperty, value); }
}
// Using a DependencyProperty as the backing store for strData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty strDataProperty =
DependencyProperty.Register("strData", typeof(string), typeof(SearchValue), new PropertyMetadata(""));
//Define Delegate and Events
public delegate void TextValueChangedEventHandler(object s, EventArgs e);
public event TextChangedEventHandler TextBoxChanged;
private void txtSearch_TextChanged(object sender, TextChangedEventArgs e)
{
strData = txtSearch.Text;
if (TextBoxChanged != null)
{
TextBoxChanged(this, e);
}
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Namespace SILV_SearchUserControl
Partial Public Class SearchValue
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
Public Property strData() As String
Get
Return CStr(GetValue(strDataProperty))
End Get
Set(ByVal value As String)
SetValue(strDataProperty, value)
End Set
End Property
' Using a DependencyProperty as the backing store for strData. This enables animation, styling, binding, etc...
Public Shared ReadOnly strDataProperty As DependencyProperty = DependencyProperty.Register("strData", GetType(String), GetType(SearchValue), New PropertyMetadata(""))
'Define Delegate and Events
Public Delegate Sub TextValueChangedEventHandler(ByVal s As Object, ByVal e As EventArgs)
Public Event TextBoxChanged As TextChangedEventHandler
Private Sub txtSearch_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
strData = txtSearch.Text
RaiseEvent TextBoxChanged(Me, e)
End Sub
End Class
End Namespace
The above code defines dependency property for exchanging data between user control and the consumer. The important part here is that since Silverlight 3.0 does not support creating custom routed events, a standard mechanism (Delegate and event) is used for event creation.
Step 5: Build the project, make sure that it is error free.
Step 6: To the solution, add a new Silverlight class library project, name it as ‘SILV_DetailedControl’. Remove the existing ‘Class1.cs’ and add a new Silverlight user control and name it as ‘DetailedControl.xaml’. Write the following xaml in it:
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SILV_DetailedControl.DetailedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="400">
<Grid x:Name="LayoutRoot" Background="White" Width="500" Height="400">
<Grid.RowDefinitions>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="300"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Information Details"
FontFamily="Times New Roman"
TextAlignment="Center"
FontWeight="Bold"
Foreground="Red"
FontSize="56"></TextBlock>
<data:DataGrid Grid.Row="1"
AlternatingRowBackground="Azure"
AutoGenerateColumns="True"
Background="Coral"
x:Name="dgDetails">
</data:DataGrid>
</Grid>
</UserControl>
The above xaml shows the following design:
The above grid will display data which will be filtered using the first user control.
Step 7: Open ‘DetailedControl.xaml.cs’ and write the following code:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace SILV_DetailedControl
{
public partial class DetailedControl : UserControl
{
public DetailedControl()
{
InitializeComponent();
}
public void BindDataGrid(object[] Source)
{
dgDetails.ItemsSource = Source;
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Imports System.Collections.ObjectModel
Namespace SILV_DetailedControl
Partial Public Class DetailedControl
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
Public Sub BindDataGrid(ByVal Source() As Object)
dgDetails.ItemsSource = Source
End Sub
End Class
End Namespace
The above code defines ‘BindDataGrid()’ method which will be called by the consumer to assign data to the DataGrid.
Step 8: To the Solution, add a new WCF service and name it as ‘WCF_Service’. Rename ‘IService1.cs’ to ‘IService.cs’ and write the following code by removing the code which is by default available to you:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCF_Service
{
[ServiceContract]
public interface IService
{
[OperationContract]
Customer[] GetAllCustomers();
}
[DataContract]
public class Customer
{
[DataMember]
public int CustomerID { get; set; }
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public string Address { get; set; }
[DataMember]
public string City { get; set; }
[DataMember]
public string State { get; set; }
[DataMember]
public int Age { get; set; }
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports System.Text
Namespace WCF_Service
<ServiceContract> _
Public Interface IService
<OperationContract> _
Function GetAllCustomers() As Customer()
End Interface
<DataContract> _
Public Class Customer
Private privateCustomerID As Integer
<DataMember> _
Public Property CustomerID() As Integer
Get
Return privateCustomerID
End Get
Set(ByVal value As Integer)
privateCustomerID = value
End Set
End Property
Private privateCustomerName As String
<DataMember> _
Public Property CustomerName() As String
Get
Return privateCustomerName
End Get
Set(ByVal value As String)
privateCustomerName = value
End Set
End Property
Private privateAddress As String
<DataMember> _
Public Property Address() As String
Get
Return privateAddress
End Get
Set(ByVal value As String)
privateAddress = value
End Set
End Property
Private privateCity As String
<DataMember> _
Public Property City() As String
Get
Return privateCity
End Get
Set(ByVal value As String)
privateCity = value
End Set
End Property
Private privateState As String
<DataMember> _
Public Property State() As String
Get
Return privateState
End Get
Set(ByVal value As String)
privateState = value
End Set
End Property
Private privateAge As Integer
<DataMember> _
Public Property Age() As Integer
Get
Return privateAge
End Get
Set(ByVal value As Integer)
privateAge = value
End Set
End Property
End Class
End Namespace
Step 9: Change ‘Service1.svc’ to ‘Service.svc’ and write the following code in ‘Service.svc.cs’:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace WCF_Service
{
public class Service : IService
{
SqlConnection Conn;
SqlCommand Cmd;
SqlDataReader Reader;
#region IService Members
public Customer[] GetAllCustomers()
{
Conn = new SqlConnection("Data Source=.;Initial Catalog=Company;Integrated Security=SSPI");
Conn.Open();
Cmd = new SqlCommand();
Cmd.Connection = Conn;
Cmd.CommandText = "Select * from Customers";
Reader = Cmd.ExecuteReader();
DataTable dtCustomers = new DataTable();
dtCustomers.Load(Reader);
Customer[] arrCustomers = new Customer[dtCustomers.Rows.Count];
int rowCount = 0;
foreach (DataRow Dr in dtCustomers.Rows)
{
arrCustomers[rowCount] = new Customer();
arrCustomers[rowCount].CustomerID = Convert.ToInt32(Dr["CustomerID"]);
arrCustomers[rowCount].CustomerName = Dr["CustomerName"].ToString();
arrCustomers[rowCount].Address = Dr["Address"].ToString();
arrCustomers[rowCount].City = Dr["City"].ToString();
arrCustomers[rowCount].State = Dr["State"].ToString();
arrCustomers[rowCount].Age = Convert.ToInt32(Dr["Age"]);
rowCount++;
}
Conn.Close();
Cmd.Dispose();
Conn.Dispose();
return arrCustomers;
}
#endregion
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports System.Text
Imports System.Data
Imports System.Data.SqlClient
Namespace WCF_Service
Public Class Service
Implements IService
Private Conn As SqlConnection
Private Cmd As SqlCommand
Private Reader As SqlDataReader
#Region "IService Members"
Public Function GetAllCustomers() As Customer()
Conn = New SqlConnection("Data Source=.;Initial Catalog=Company;Integrated Security=SSPI")
Conn.Open()
Cmd = New SqlCommand()
Cmd.Connection = Conn
Cmd.CommandText = "Select * from Customers"
Reader = Cmd.ExecuteReader()
Dim dtCustomers As New DataTable()
dtCustomers.Load(Reader)
Dim arrCustomers(dtCustomers.Rows.Count - 1) As Customer
Dim rowCount As Integer = 0
For Each Dr As DataRow In dtCustomers.Rows
arrCustomers(rowCount) = New Customer()
arrCustomers(rowCount).CustomerID = Convert.ToInt32(Dr("CustomerID"))
arrCustomers(rowCount).CustomerName = Dr("CustomerName").ToString()
arrCustomers(rowCount).Address = Dr("Address").ToString()
arrCustomers(rowCount).City = Dr("City").ToString()
arrCustomers(rowCount).State = Dr("State").ToString()
arrCustomers(rowCount).Age = Convert.ToInt32(Dr("Age"))
rowCount += 1
Next Dr
Conn.Close()
Cmd.Dispose()
Conn.Dispose()
Return arrCustomers
End Function
#End Region
End Class
End Namespace
Step 10: In the Web.Config file, change the default ‘wsHttpBinding’ to ‘basicHttpBinding’ and remove ‘mexHttpBinding’.
Step 11: Compile the WCF service and publish the WCF service on IIS.
Step 12: To the same solution, add a new Silverlight application and name it as ‘SILV3_UserControlNAvigation’. To this project, add references of assemblies of the user controls which we have created in the above steps.
Step 13: In this Silverlight project add WCF service reference.
Step 14: To display Silverlight user controls referred in this project write the following xaml in ‘MainPage.Xaml’:
<UserControl x:Class="SILV3_UserControlNAvigation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Search="clr-namespace:SILV_SearchUserControl;assembly=SILV_SearchUserControl"
xmlns:Details="clr-namespace:SILV_DetailedControl;assembly=SILV_DetailedControl"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="500"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Width="620" Height="480">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="420"></RowDefinition>
</Grid.RowDefinitions>
<Search:SearchValue x:Name="searchData" Grid.Row="0"></Search:SearchValue>
<Details:DetailedControl x:Name="detailedData" Grid.Row="1"></Details:DetailedControl>
</Grid>
</UserControl>
The above xaml shows, the way using which Silverlight user controls can be referred into the application, the code is as below:
xmlns:Search="clr-namespace:SILV_SearchUserControl;assembly=SILV_SearchUserControl"
xmlns:Details="clr-namespace:SILV_DetailedControl;assembly=SILV_DetailedControl"
Step 15: Open ‘MainPage.Xaml.cs’ and write the following code it is:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
namespace SILV3_UserControlNAvigation
{
public partial class MainPage : UserControl
{
MyRef.ServiceClient Proxy;
MyRef.Customer[] arr;
public MainPage()
{
InitializeComponent();
searchData.TextBoxChanged += new TextChangedEventHandler(searchData_TextBoxChanged);
}
void searchData_TextBoxChanged(object sender, TextChangedEventArgs e)
{
//USe Linq to Filter Data
var FilteredData = from Cust in arr
where Cust.CustomerName.StartsWith(searchData.strData)
select Cust;
//Convert the IEnumaration to Object Array
object[] CollectionToBind = FilteredData.ToArray();
detailedData.BindDataGrid(CollectionToBind);
if (CollectionToBind.Length == 0 || CollectionToBind == null)
{
HtmlPage.Window.Alert("Sorry No Data Available..");
}
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Proxy = new SILV3_UserControlNAvigation.MyRef.ServiceClient();
Proxy.GetAllCustomersCompleted += new EventHandler<SILV3_UserControlNAvigation.MyRef.GetAllCustomersCompletedEventArgs>(Proxy_GetAllCustomersCompleted);
Proxy.GetAllCustomersAsync();
}
void Proxy_GetAllCustomersCompleted(object sender, SILV3_UserControlNAvigation.MyRef.GetAllCustomersCompletedEventArgs e)
{
if (e.Result != null)
{
arr = e.Result.ToArray();
detailedData.BindDataGrid(arr);
}
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Imports System.Windows.Browser
Namespace SILV3_UserControlNAvigation
Partial Public Class MainPage
Inherits UserControl
Private Proxy As MyRef.ServiceClient
Private arr() As MyRef.Customer
Public Sub New()
InitializeComponent()
AddHandler searchData.TextBoxChanged, AddressOf searchData_TextBoxChanged
End Sub
Private Sub searchData_TextBoxChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
'USe Linq to Filter Data
Dim FilteredData = From Cust In arr _
Where Cust.CustomerName.StartsWith(searchData.strData) _
Select Cust
'Convert the IEnumaration to Object Array
Dim CollectionToBind() As Object = FilteredData.ToArray()
detailedData.BindDataGrid(CollectionToBind)
If CollectionToBind.Length = 0 OrElse CollectionToBind Is Nothing Then
HtmlPage.Window.Alert("Sorry No Data Available..")
End If
End Sub
Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
Proxy = New SILV3_UserControlNAvigation.MyRef.ServiceClient()
AddHandler Proxy.GetAllCustomersCompleted, AddressOf Proxy_GetAllCustomersCompleted
Proxy.GetAllCustomersAsync()
End Sub
Private Sub Proxy_GetAllCustomersCompleted(ByVal sender As Object, ByVal e As SILV3_UserControlNAvigation.MyRef.GetAllCustomersCompletedEventArgs)
If e.Result IsNot Nothing Then
arr = e.Result.ToArray()
detailedData.BindDataGrid(arr)
End If
End Sub
End Class
End Namespace
If you see the code above, the loaded event of the Silverlight calls WCF service asynchronously and passes the retrieved values to ‘BindDataGrid()’ method of the ‘SILV_DetailedControl’.
In the ‘TextBoxChanged’ event of ‘SILV_SearchUserControl’, LINQ is used to query the result returned from WCF service by using ‘’strData’ dependency property declared into the ‘SILV_SearchUserControl’. This filtered data is converted into an array and passed to ‘BindDataGrid()’ method. This event actually raise the ‘TextChanged’ event into the ‘SILV_SearchUserControl’.
So the above code shows that ‘MainPage.Xaml.cs’ establishes communication between ‘SILV_SearchUserControl’ and ‘SILV_DetailedControl’ using ‘TextBoxChanged’ event and ‘strData’ dependency property declared into ‘SILV_SearchUserControl’ and ‘BindDataGrid()’ method declared in ‘SILV_DetailedControl’.
Step 16: Run the application and the following result will be displayed:
Step 17: Start entering a name into ‘Search Data’ text box and if the matching name is found, then following output will be displayed:
The entire source code of this article can be downloaded over here.
Give me a +1 if you think it was a good article. Thanks!