In my last blog posts I have been showing you how to create collection of entity objects using code that is custom for each table and object you create. Well, if you use a little reflection code you can shrink this code quite a bit. Yes, we all know that reflection is slow and probably should be avoided in most cases. What I have found out is that loading over 6200 product records into an entity collection still takes less than a second when using Reflection. So, I will leave it up to you to decide which way you wish to go.
We will once again use our Product class that uses nullable types as shown below:
C#
public class Product
{
public int? ProductId { get; set; }
public string ProductName { get; set; }
public DateTime? IntroductionDate { get; set; }
public decimal? Cost { get; set; }
public decimal? Price { get; set; }
public bool? IsDiscontinued { get; set; }
}
Visual Basic
Public Class Product
Public Property ProductId() AsNullable(Of Integer)
Public Property ProductName() As String
Public Property IntroductionDate() As Nullable(Of DateTime)
Public Property Cost() As Nullable(Of Decimal)
Public Property Price() As Nullable(Of Decimal)
Public Property IsDiscontinued() As Nullable(Of Boolean)
End Class
How Reflection Works
If you wish to set one of the properties on the Product class to a certain value, you write code like the following:
C#
Product entity = new Product();
entity.ProductName = "A New Product";
Visual Basic
Dim entity as New Product()
entity.ProductName = "A New Product"
Sometimes you might want to create a generic routine that you can pass a property name to and the value to set that property to. This can be accomplished using Reflection as shown in the following code:
C#
Product entity = new Product();
typeof(Product).InvokeMember("ProductName",
BindingFlags.SetProperty,
Type.DefaultBinder, entity,
new Object[] { "A New Product" });
Visual Basic
Dim entity as New Product()
GetType(Product).InvokeMember("ProductName", _
BindingFlags.SetProperty, _
Type.DefaultBinder, entity, _
New Object() { "A New Product" })
The InvokeMember is a method of the System.Type class. Using typeof() in C# or GetType() in Visual Basic returns an instance of the Type class which contains meta-data about the Product class. You pass 5 parameters to the InvokeMember method. The first parameter is the name of the property you wish to set. The second parameter is the name of the property or method you wish to invoke; in this case it is the Set property. The third parameter tells InvokeMember that you are using the default binder. The fourth parameter is the variable that contains a reference to an instance of the class specified by the type (in this case the Product object). The last parameter is an object array of whatever you need to pass to the method or property that you are invoking. For setting the ProductName property you only need a single object array of the string you are setting.
A Better Way to Set Property Values
While the InvokeMember method works for setting a property, it is actually quite slow. There is a more efficient way to set a property using reflection. There is a GetProperty method on the Type class you use to retrieve a PropertyInfo object. This PropertyInfo object has a SetValue method that you can use to set the value on that property. Below is an example of calling the SetValue method.
C#
Product entity = new Product();
typeof(Product).GetProperty("ProductName").
SetValue(entity, "A New Product", null);
MessageBox.Show(entity.ProductName);
Visual Basic
Dim entity As New Product()
GetType(Product).GetProperty("ProductName"). _
SetValue(entity, "A New Product", Nothing)
MessageBox.Show(entity.ProductName)
The above code is actually a little easier to understand than using the InvokeMember and is over 100% faster! That is a big difference and you should take advantage of it!
Apply Reflection to Loading Collections
When you wish to load a collection of entity classes you will loop through either a DataReader or a DataTable. Before you loop through, however, you should gather a collection of all properties on your Product class into an array of PropertyInfo objects. This way you only get the properties one time instead of each time through the rows you get a single property using the GetProperty method. In the code shown below you will use the GetProperties method to retrieve this array.
You will then build the data reader and move through each row of the data reader by using the Read method. For each row you will now loop through the PropertyInfo array and use the property name to retrieve the corresponding column in the data reader. Remember, this assumes that your column names are the same name as your entity class.
C#
public List<Product> GetProducts()
{
SqlCommand cmd = null;
List<Product> ret = new List<Product>();
Product entity = null;
// Get all the properties in Entity Class
PropertyInfo[] props = typeof(Product).GetProperties();
cmd = new SqlCommand("SELECT * FROM Product");
using (cmd.Connection = new
SqlConnection(AppSettings.Instance.ConnectString))
{
cmd.Connection.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
// Create new instance of Product Class
entity = new Product();
// Set all properties from the column names
// NOTE: This assumes your column names are the
// same name as your class property names
foreach (PropertyInfo col in props)
{
if (rdr[col.Name].Equals(DBNull.Value))
col.SetValue(entity, null, null);
else
col.SetValue(entity, rdr[col.Name], null);
}
ret.Add(entity);
}
}
}
return ret;
}
Visual Basic
Public Function GetProducts() As List(Of Product)
Dim cmd As SqlCommand = Nothing
Dim ret As New List(Of Product)()
Dim entity As Product = Nothing
' Get all the properties in Entity Class
Dim props As PropertyInfo() = _
GetType(Product).GetProperties()
cmd = New SqlCommand("SELECT * FROM Product")
Using cnn = New _
SqlConnection(AppSettings.Instance.ConnectString)
cmd.Connection = cnn
cmd.Connection.Open()
Using rdr = cmd.ExecuteReader()
While rdr.Read()
' Create new instance of Product Class
entity = New Product()
' Set all properties from the column names
' NOTE: This assumes your column names are the
' same name as your class property names
For Each col As PropertyInfo In props
If rdr(col.Name).Equals(DBNull.Value) Then
col.SetValue(entity, Nothing, Nothing)
Else
col.SetValue(entity, rdr(col.Name), Nothing)
End If
Next
ret.Add(entity)
End While
End Using
End Using
Return ret
End Function
Create Generic Base Class
Instead of writing all of the above code for each entity collection class you need to load, you can create a base class with a generic method that will build your collection for you. Create a class called ManagerBase to which you will create a method called BuildCollection. This BuildCollection method will allow you to specify the type of entity, symbolized by <T>, that you wish to create a collection of. Pass into this method the type of the entity and a SqlDataReader and this method will take care of the rest. With the entity Type you pass in this method can retrieve the array of PropertyInfo objects from that type. A loop is made through the data reader and a new instance of the entity is created using the Activator class’ CreateInstance method. All the properties in the array of PropertyInfo objects is looped through to gather the data into the entity. Each entity is finally added to a generic List<T> collection. When all records have been processed the generic list is returned.
C#
public class ManagerBase
{
public List<T> BuildCollection<T>(Type typ,
SqlDataReader rdr)
{
List<T> ret = new List<T>();
T entity;
// Get all the properties in Entity Class
PropertyInfo[] props = typ.GetProperties();
while (rdr.Read())
{
// Create new instance of Entity
entity = Activator.CreateInstance<T>();
// Set all properties from the column names
// NOTE: This assumes your column names are the
// same name as your class property names
foreach (PropertyInfo col in props)
{
if (rdr[col.Name].Equals(DBNull.Value))
col.SetValue(entity, null, null);
else
col.SetValue(entity, rdr[col.Name], null);
}
ret.Add(entity);
}
return ret;
}
}
Visual Basic
Public Class ManagerBase
Public Function BuildCollection(Of T)(typ As Type, _
rdr As SqlDataReader) As List(Of T)
Dim ret As New List(Of T)()
Dim entity As T
' Get all the properties in Entity Class
Dim props As PropertyInfo() = typ.GetProperties()
While rdr.Read()
' Create new instance of Entity
entity = Activator.CreateInstance(Of T)()
' Set all properties from the column names
' NOTE: This assumes your column names are the
' same name as your class property names
For Each col As PropertyInfo In props
If rdr(col.Name).Equals(DBNull.Value) Then
col.SetValue(entity, Nothing, Nothing)
Else
col.SetValue(entity, rdr(col.Name), Nothing)
End If
Next
ret.Add(entity)
End While
Return ret
End Function
End Class
Use Base Class
To use this base class you will create your ProductManager class that inherits from this ManagerBase class. You can rewrite the GetProducts method shown above with the code shown below. You can see that this significantly reduces the amount of code you need to write.
C#
public class ProductManager : ManagerBase
{
public List<Product> GetProducts()
{
SqlCommand cmd = null;
List<Product> ret = null;
cmd = new SqlCommand("SELECT * FROM Product");
using (cmd.Connection = new
SqlConnection(AppSettings.Instance.ConnectString))
{
cmd.Connection.Open();
using (var rdr = cmd.ExecuteReader())
{
// Build Collection of Entity Objets using Reflection
ret = BuildCollection<Product>(typeof(Product), rdr);
}
}
return ret;
}
}
Visual Basic
Public Class ProductManager
Inherits ManagerBase
Public Function GetProducts() As List(Of Product)
Dim cmd As SqlCommand = Nothing
Dim ret As List(Of Product) = Nothing
cmd = New SqlCommand("SELECT * FROM Product")
Using cnn = New _
SqlConnection(AppSettings.Instance.ConnectString)
cmd.Connection = cnn
cmd.Connection.Open()
Using rdr = cmd.ExecuteReader()
' Build Collection of Entity Objets using Reflection
ret = BuildCollection(Of Product)( _
GetType(Product), rdr)
End Using
End Using
Return ret
End Function
End Class
Summary
In this blog post you learned how to use reflection to fill a collection of entity objects. There are two different methods of setting properties using Reflection. You should use the SetValue method instead of the InvokeMember as it is more efficient. Creating a base class and using a generic method will eliminate a lot of repetitive code.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “Creating Collections of Entities using Reflection” from the drop down list.
Use the Code 'PDSA' when registering and receive $50 off your registration!
|
|
|
Top Microsoft Speakers Featured at DevIntersection & SQLIntersection, Including 3 Speakers from PDSA, Inc.
Top Microsoft and third-party speakers, including PDSA's Paul Sheriff, Michael Krasowski and John Kuhn, will be featured at the April DevIntersection & SQLIntersection conference, April 8-11, 2013 at the MGM Grand in Las Vegas. Look on the website to see the many sessions and workshops, plus save big with our Show Package and Complete Package pricing, and get a workshop, the conference, and a Surface with Windows RT bundled for a great deal.
|
|
|
|
Choose a Package Deal, Receive a Surface with Windows RT
Be part of the new crowd that is exploring Windows 8! If you are a SQL Server or .NET developer who will be faced with jumping into Windows 8 in the next year, have we got a great deal for you. The April 2013 SQLIntersection & DevIntersection Conference will offer two package deals that include attending three days of sessions in any track at the conference plus a Surface with Windows RT OR a $300 gift card. And we'll include one or two full-day workshops depending on which deal you choose. Sign up for SQLIntersection / DevIntersection Conference and start to explore the Windows 8 platform that businesses will use over the next decade.
|
|
DevIntersection Brochure Is Now Online

If you've been waiting for a copy of the DevIntersection brochure to show your supervisor or training coordinator, the wait is over. You can download it now. We invite you to compare our show to the competition. We have more speakers, more sessions, a better package deal, plus exciting giveaways and evening events.
|
|
As a friend of PDSA you can save $50 on DevIntersection / SQLIntersection registration. Use the Discount Code PDSA on the DevIntersection / SQLIntersection registration page.
|
|
|
|
|
|
Let’s now look at another advantage of using a DataTable. A lot of developers today are used to using LINQ. After loading data into a DataTable you can iterate using a foreach statement, or you can use LINQ to create a collection of entity objects. The DataRow class has an extension method called Field that allows you to check the data and return either a null or the real data value. Of course this means you have to use Nullable types for your properties in your class. Below is the definition of a Product class that uses all Nullable types.
C#
public class Product
{
public int? ProductId { get; set; }
public string ProductName { get; set; }
public DateTime? IntroductionDate { get; set; }
public decimal? Cost { get; set; }
public decimal? Price { get; set; }
public bool? IsDiscontinued { get; set; }
}
Visual Basic
Public Class Product
Public Property ProductId() AsNullable(Of Integer)
Public Property ProductName() As String
Public Property IntroductionDate() As Nullable(Of DateTime)
Public Property Cost() As Nullable(Of Decimal)
Public Property Price() As Nullable(Of Decimal)
Public Property IsDiscontinued() As Nullable(Of Boolean)
End Class
Reading Data into a Collection using LINQ
Field is a generic method that allows you to pass in the data type you wish to convert the data to if the data within the column is not null. In the code below you can see an example of filling a DataTable with product data, then iterating over the data table using a LINQ query. As you create each new instance of the product class use the Field method to retrieve the data and place it into your nullable property.
Because the Field method is an extension method you will need to add a reference to the System.Data.DataSetExtensions.dll in your project. You could use this method to load properties of a class that does not use Nullable types, but you could not have any null values in your table. This is not a very likely scenario, so you probably want to stick with nullable types.
C#
public List<Product> GetProducts()
{
DataTable dt = new DataTable();
SqlDataAdapter da = null;
da = new SqlDataAdapter("SELECT * FROM Product",
AppSettings.Instance.ConnectString);
da.Fill(dt);
var query = (from dr in dt.AsEnumerable()
select new Product
{
ProductId = dr.Field<int?>("ProductId"),
ProductName = dr.Field<string>("ProductName"),
IntroductionDate =
dr.Field<DateTime?>("IntroductionDate"),
Cost = dr.Field<decimal?>("Cost"),
Price = dr.Field<decimal?>("Price"),
IsDiscontinued = dr.Field<bool?>("IsDiscontinued")
});
return query.ToList();
}
Visual Basic
Public Function GetProducts() As List(Of Product)
Dim dt As New DataTable()
Dim da As SqlDataAdapter = Nothing
da = New SqlDataAdapter("SELECT * FROM Product", _
AppSettings.Instance.ConnectString)
da.Fill(dt)
Dim query = (From dr In dt.AsEnumerable() _
Select New Product() With { _
.ProductId = dr.Field(Of Nullable(Of _
Integer))("ProductId"), _
.ProductName = dr.Field(Of String)("ProductName"), _
.IntroductionDate = dr.Field(Of Nullable(Of _
DateTime))("IntroductionDate"), _
.Cost = dr.Field(Of Nullable(Of Decimal))("Cost"), _
.Price = dr.Field(Of Nullable(Of Decimal))("Price"), _
.IsDiscontinued = dr.Field(Of Nullable(Of _
Boolean))("IsDiscontinued") _
})
Return query.ToList()
End Function
Summary
In this blog post you learned how to use the Field method on the DataRow class in ADO.NET to help you load up a collection of product objects. When using the Field method you should use .NET nullable data types.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select "Tips & Tricks", then select "Creating Collections of Entity Objects using LINQ and Field Method" from the drop down list.
As discussed in my last two blog posts you have a variety of ways to create collections of Entity classes. Using a DataSet or DataTable is a little slower than using a DataReader, but in most cases the difference is in milliseconds so in a real world app this difference would not be a killer. For instance, in my sample data I was loading 6,261 records from the Product table discussed in the last blog post and it took 45 milliseconds on average to load those records into an entity collection using a DataTable. It took only 30 milliseconds on average to load the same entity collection using a DataReader. The rendering of that data would probably take longer than that, so you can choose which one you wish to use.
Let's now look at one advantage of using a DataTable. A lot of developers today use LINQ. After loading data into a DataTable you can iterate using a foreach statement, or you can use LINQ to create a collection of entity objects.
Below is a typical entity class that models a Product table in a database:
C#
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public DateTime IntroductionDate { get; set; }
public decimal Cost { get; set; }
public decimal Price { get; set; }
public bool IsDiscontinued { get; set; }
}
Visual Basic
Public Class Product
Public Property ProductId() Integer
Public Property ProductName() As String
Public Property IntroductionDate() As DateTime
Public Property Cost() As Decimal
Public Property Price() As Decimal
Public Property IsDiscontinued() As Boolean
End Class
Reading Data into a Collection using LINQ
Let's now use a LINQ query to iterate over the collection of DataRow objects within a DataTable. In the code below you can see the use of the SqlDataAdapter to fill a DataTable. You now use the AsEnumerable() method on the DataTable to turn the collection of DataRow objects into an enumerable list that can be used in a LINQ statement. In the LINQ statement you create the new Product object use the same DataConvert class to check for valid data and convert that data into a value that can be stored into each property.
C#
public List<Product> GetProducts()
{
DataTable dt = new DataTable();
SqlDataAdapter da = null;
da = new SqlDataAdapter("SELECT * FROM Product",
AppSettings.Instance.ConnectString);
da.Fill(dt);
var query =
(from dr in dt.AsEnumerable()
select new Product
{
ProductId = Convert.ToInt32(dr["ProductId"]),
ProductName = dr["ProductName"].ToString(),
IntroductionDate =
DataConvert.ConvertTo<DateTime>(
dr["IntroductionDate"], default(DateTime)),
Cost = DataConvert.ConvertTo<decimal>(
dr["Cost"], default(decimal)),
Price = DataConvert.ConvertTo<decimal>(
dr["Price"], default(decimal)),
IsDiscontinued = DataConvert.ConvertTo<bool>(
dr["IsDiscontinued"], default(bool))
});
return query.ToList();
}
Visual Basic
Public Function GetProducts() As List(Of Product)
Dim dt As New DataTable()
Dim da As SqlDataAdapter = Nothing
da = New SqlDataAdapter("SELECT * FROM Product", _
AppSettings.Instance.ConnectString)
da.Fill(dt)
Dim query = (From dr In dt.AsEnumerable() _
Select New Product() With { _
.ProductId = Convert.ToInt32(dr("ProductId")), _
.ProductName = dr("ProductName").ToString(), _
.IntroductionDate = DataConvert.ConvertTo(Of _
DateTime)(dr("IntroductionDate"), DateTime.MinValue), _
.Cost = DataConvert.ConvertTo(Of Decimal)(dr("Cost"), 0D), _
.Price = DataConvert.ConvertTo(Of Decimal) _
(dr("Price"), 0D), _
.IsDiscontinued = DataConvert.ConvertTo(Of _
Boolean)(dr("IsDiscontinued"), False) _
})
Return query.ToList()
End Function
Summary
In this blog post you learned how to create an entity class and a collection of entity classes using LINQ. When using a DataTable filled with data, LINQ allows you to write more expressive code to create a collection of entities compared to a foreach loop.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select "Tips & Tricks", then select "Creating Entity Collections using LINQ" from the drop down list.
As discussed in the last blog post, it is a best practice to build entity classes. In the last post we filled a DataTable with Category data and then iterated over that DataTable to create a collection of Entity classes. In this blog post we will use a SqlDataReader to fill the Entity classes.
When using a SqlDataReader you must ensure that you close the data reader after you are done with it. You can write a try…catch…finally and close the data reader in the finally block, or you can use the using statement. I like the using statement because you do not have to write as much code. In my tests with VS 2010, both ways run just as fast.
I am going to use a new table that I created called Product for this sample. Here is the definition of the Product table. I am switching to another table because I wanted to have a lot of data to run some timing comparisons. I have filled this Product table with over 6200 rows of data. In addition, I wanted some different data types such as DateTime, decimal and boolean to show how to perform conversions and take into account null values in a database.
CREATE TABLE Product
(
ProductId int PRIMARY KEY NONCLUSTERED
IDENTITY(1,1) NOT NULL,
ProductName varchar(50) NOT NULL,
IntroductionDate datetime NULL,
Cost money NULL,
Price money NULL,
IsDiscontinued bit NULL
)
We need to also create a Product class that looks like the following:
C#
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public DateTime IntroductionDate { get; set; }
public decimal Cost { get; set; }
public decimal Price { get; set; }
public bool IsDiscontinued { get; set; }
}
Visual Basic
Public Class Product
Public Property ProductId As Integer
Public Property ProductName As String
Public Property IntroductionDate As DateTime
Public Property Cost As Decimal
Public Property Price As Decimal
Public Property IsDiscontinued As Boolean
End Class
Now, here is the code to load a collection of Product data into a collection of Product objects.
C#
private List<Product> GetProducts()
{
SqlCommand cmd = null;
List<Product> ret = new List<Product>();
Product entity = null;
cmd = new SqlCommand("SELECT * FROM Product");
using (cmd.Connection = new SqlConnection(
"Server=Localhost;Database=Sandbox;Integrated
Security=Yes"))
{
cmd.Connection.Open();
using (var rdr =
cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (rdr.Read())
{
entity = new Product();
// ProductId is a NOT NULL field
entity.ProductId = Convert.ToInt32(rdr["ProductId"]);
// Strings automatically convert to "" if null.
entity.ProductName = rdr["ProductName"].ToString();
entity.IntroductionDate =
DataConvert.ConvertTo<DateTime>(
rdr["IntroductionDate"],
default(DateTime));
entity.Cost =
DataConvert.ConvertTo<decimal>(rdr["Cost"],
default(decimal));
entity.Price =
DataConvert.ConvertTo<decimal>(rdr["Price"],
default(decimal));
entity.IsDiscontinued =
DataConvert.ConvertTo<bool>(
rdr["IsDiscontinued"],
default(bool));
ret.Add(entity);
}
}
}
return ret;
}
Visual Basic
Private Function GetProducts() As List(Of Product)
Dim cmd As SqlCommand = Nothing
Dim ret As New List(Of Product)()
Dim entity As Product = Nothing
cmd = New SqlCommand("SELECT * FROM Product")
Using cnn As SqlConnection = _
New SqlConnection( _
"Server=Localhost;Database=Sandbox;Integrated
Security=Yes")
cmd.Connection = cnn
cmd.Connection.Open()
Using rdr As SqlDataReader = _
cmd.ExecuteReader(CommandBehavior.CloseConnection)
While rdr.Read()
entity = New Product()
' ProductId is a NOT NULL field
entity.ProductId = Convert.ToInt32(rdr("ProductId"))
' Strings automatically convert to "" if null.
entity.ProductName = rdr("ProductName").ToString()
entity.IntroductionDate = _
DataConvert.ConvertTo(Of DateTime) _
(rdr("IntroductionDate"), DateTime.MinValue)
entity.Cost = DataConvert.ConvertTo(Of Decimal) _
(rdr("Cost"), 0D)
entity.Price = DataConvert.ConvertTo(Of Decimal) _
(rdr("Price"), 0D)
entity.IsDiscontinued = _
DataConvert.ConvertTo(Of Boolean) _
(rdr("IsDiscontinued"), False)
ret.Add(entity)
End While
End Using
End Using
Return ret
End Function
The above code is fairly straight forward. Loop through each row and grab each column of data. Convert the data coming from the column into an appropriate value based on the data type. Remember when reading from a DataRow or from a column in the SqlDataReader that the data comes in as an "object" data type. So you must convert it in order to put it into a strongly typed property in your Product object. Of course, you must also handle null values and that is where the DataConvert class comes in.
The DataConvert Class
Whether you use a DataTable/DataSet like in my last blog post or whether you use a DataReader, you will need to check to see if the data read in from the database is a null value. If so, you either need to use Nullable data types in all of your classes, or you need to convert the null to some valid value for the appropriate data type. In the above code I used a class to check for and convert a null value into a default value for the data. The DataConvert class looks like the following:
C#
public class DataConvert
{
public static T ConvertTo<T>(object value,
T defaultValue) where T : struct
{
if (value.Equals(DBNull.Value))
return defaultValue;
else
return (T)value;
}
}
Visual Basic
Public Class DataConvert
Public Shared Function ConvertTo(Of T As Structure) _
(value As Object, defaultValue As T) As T
If value.Equals(DBNull.Value) Then
Return defaultValue
Else
Return DirectCast(value, T)
End If
End Function
End Class
I used a generic to specify the data type to convert to and then passed in the value from the column and a default value to return if the value is a null.
Summary
In this blog post saw how to create entity classes using a SqlDataReader instead of a Data Table as shown in the previous blog post. In addition you learned how to handle null values by using a DataConvert class.
What is an Entity Class
An Entity class has properties and typically no methods. An entity class is generally used to hold a single row of data from a table. So, if you have a Category table with the fields CategoryId, CategoryName and Description, you will create a Category class with properties of the same name. For example:
C#
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
Visual Basic
Public Class Category
Public Property CategoryId() As Integer
Public Property CategoryName() As String
Public Property Description() As String
End Class
You would then create a collection class to hold 1 or more entity classes. So if your Category table had 100 rows and you read all 100 using a DataSet/DataTable, then you would end up with 100 Category classes in a collection. Below is a definition of a collection class using the Generic List<> class.
C#
public class Categories : List<Category>
{
}
Visual Basic
Public Class Categories
Inherits List(Of Category)
End Class
There are many reasons for building an entity class and a collection class. Using an Entity class allows you to serialize this object and send across the web to any other application that needs a structure of the data. When you use an Entity class you get IntelliSense on the properties as opposed to a DataTable where you have to remember the name of the column in the collection. An Entity class also strongly types the data to what it was in the database. When the data is placed into a DataTable, each column goes in as the data type of object and comes back out as object. That means each time you access it you must convert it into the correct data type. There are many more reasons as well, but these are the main ones.
Reading Data into a Collection
Let's use the Categories table in the Northwind database to create a collection of entity classes. You will use a DataTable to read all of the rows in the table, then move each row of data into a new Category class.
C#
private List<Category> GetAllCategories()
{
List<Category> ret = new List<Category>();
Category cat;
SqlDataAdapter da;
DataTable dt = new DataTable();
da = new SqlDataAdapter("SELECT CategoryId, CategoryName,
Description FROM Categories",
"Server=Localhost;Database=Northwind;Integrated Security=Yes");
da.Fill(dt);
foreach (DataRow dr in dt.Rows)
{
cat = new Category();
cat.CategoryId = Convert.ToInt32(dr["CategoryId"]);
cat.CategoryName = Convert.ToString(dr["CategoryName"]);
cat.Description = Convert.ToString(dr["Description"]);
ret.Add(cat);
}
return ret;
}
Visual Basic
Private Function GetAllCategories() As List(Of Category)
Dim ret As New List(Of Category)()
Dim cat As Category
Dim da As SqlDataAdapter
Dim dt As New DataTable()
da = New SqlDataAdapter("SELECT CategoryId, CategoryName,
Description FROM Categories",
"Server=Localhost;Database=Northwind;Integrated Security=Yes")
da.Fill(dt)
For Each dr As DataRow In dt.Rows
cat = New Category()
cat.CategoryId = Convert.ToInt32(dr("CategoryId"))
cat.CategoryName = Convert.ToString(dr("CategoryName"))
cat.Description = Convert.ToString(dr("Description"))
ret.Add(cat)
Next
Return ret
End Function
Summary
In this blog post you learned how to create an entity class and a collection of entity classes. You should try to use entity classes in your programming instead of data tables as you get IntelliSense, strong typing and much more flexibility than you do with loosely-typed objects such as data tables.
One of the navigation mechanisms used in Windows 8 and Windows Phone is a Tile. A tile is a large rectangle that can have words and pictures that a user can click on. You can build your own version of a Tile in your WPF or Silverlight applications using a User Control. With just a little bit of XAML and a little bit of code-behind you can create a navigation system like that shown in Figure 1.

Figure 1: Use a Tile for navigation. You can build a Tile User Control with just a little bit of XAML and code.
The WPF application shown in Figure 1 uses a WrapPanel to display a series of Tile objects. There are two styles defined in this Window to give us a large tile and a small tile. These styles and the usage of the Tile will be shown later, but first let’s look at how you can create this tile user control.
The User Control
In a WPF or Silverlight application you can create user controls which are a composite of other controls grouped together as a single unit. This user control can then be dragged and dropped onto a Window or User Control from the Visual Studio Toolbox. To create a “Tile” you need a Border, Grid, Image and a TextBlock control. Of course you will need to style these to get the appearance you saw in Figure 1. You will also need to use a Visual State Manager to highlight the tile the user is currently hovering over. The complete XAML for the tile is shown below:
<Border x:Name="borMain"
Style="{StaticResource pdsaTileBorderStyle}"
MouseEnter="OnMouseEnter"
MouseLeave="OnMouseLeave"
MouseLeftButtonDown="OnMouseLeftButtonDown">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="MouseEnter">
<Storyboard>
<ColorAnimation
To="{StaticResource
pdsaTileBorderHighlightColor}"
Duration="00:00:00"
Storyboard.TargetName="borMain"
Storyboard.TargetProperty="BorderBrush.Color" />
</Storyboard>
</VisualState>
<VisualState Name="MouseLeave" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Row="0"
Name="imgMain"
Style="{StaticResource pdsaTileImageStyle}"
Source="{Binding TileImageUri}" />
<TextBlock Grid.Row="1"
Name="tbText"
Style="{StaticResource
pdsaTileTextBlockStyle}"
Text="{Binding TileText}" />
</Grid>
</Border>
The Border, the Image and TextBlock all have a style applied to them. A set of default styles are contained in a resource dictionary that comes with the user control. The user control and the resource dictionary are located in a DLL named PDSA.WPF. You can override the default resource dictionary with one of your own to create a different look and feel for your tiles. You only need to keep the names of the styles the same.
The Visual State Manager has just a single ColorAnimation when the mouse enters the Border. This ColorAnimation will change the border brush color to the value specified in the style named pdsaTileBorderHighlightColor. The Border will respond to the MouseEnter and MouseLeave events and call the Visual State Manager to move to the states defined in the XAML as shown in the code below:
private void OnMouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "MouseEnter", true);
}
private void OnMouseLeave(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "MouseLeave", true);
}
Creating the Click Event
In addition to the MouseEnter and MouseLeave events, the user control must also raise a Click event. The MouseLeftButtonDown event is defined on the Border control. When this event procedure is fired an instance of a class called PDSATileEventArgs is created and a Click event is raised. Here is the code for the MouseLeftButtonDown event.
private void OnMouseLeftButtonDown(object sender,
MouseButtonEventArgs e)
{
PDSATileEventArgs args = new PDSATileEventArgs();
args.Text = this.Text;
args.ViewName = this.ViewName;
if(ImageUri != null)
args.ImageUri = this.ImageUri.ToString();
if(ToolTip != null)
args.ToolTip = this.ToolTip.ToString();
RaiseClick(args);
}
public delegate void TileClickEventHandler(object sender,
PDSATileEventArgs e);
public event TileClickEventHandler Click;
protected void RaiseClick(PDSATileEventArgs e)
{
if (null != Click)
Click(this, e);
}
As you can see in the MouseLeftButtonDown event you create a new instance of a PDSATileEventArgs class. You gather the dependency properties from the user control and place those into this new PDSATileEventArgs object. Next, you call the RaiseClick method passing in this object. The Click event is raised from this method passing in the current tile object and the instance of the PDSATileEventArgs class. The PDSTileEventArgs class is shown below:
public class PDSATileEventArgs : EventArgs
{
public PDSATileEventArgs() : base()
{
ViewName = string.Empty;
Text = string.Empty;
ImageUri = string.Empty;
ToolTip = string.Empty;
}
public string ViewName { get; set; }
public string Text { get; set; }
public string ImageUri { get; set; }
public string ToolTip { get; set; }
}
Create a Tile in your Application
After you have built this user control you can add a reference to the DLL that contains the user control. This user control will now show up in the Visual Studio Toolbox. Drag and drop a Tile control onto a window and set the appropriate properties via the Property Window or directly in the XAML. Below is the XAML for the “Computer Cleaner” tile shown in the upper left hand corner of Figure 1.
<my:PDSAucTile
Name="tileComputerCleaner"
Text="Computer Cleaner"
ViewName="ComputerCleanerView"
ToolTip="Click here to run the Computer Cleaner"
ImageUri="/Images-Tiles/ComputerCleaner.png"
Click="tile_Click"
Style="{StaticResource tileLarge}" />
Responding to the Click Event
When you click on a tile a Click event will fire. This event has a normal event procedure signature where you are passed the object that fired the event and an event argument object. The event argument object is an instance of the PDSATileEventArgs class. This event argument object contains the Text, ViewName, ImageUri and the ToolTip properties that you set in the XAML. In the sample code below these values are simply displayed in text blocks on the main window.
private void tile_Click(object sender, PDSATileEventArgs e)
{
tbText.Text = e.Text;
tbViewName.Text = e.ViewName;
tbImageUri.Text = e.ImageUri;
tbToolTip.Text = e.ToolTip;
}
In your application you might use a switch statement on the ViewName property to figure out which view to display as shown below:
private void tile_Click(object sender, PDSATileEventArgs e)
{
switch (e.ViewName)
{
Case "ComputerCleanerView":
// Display the Computer Cleaner View
break;
Case "LicenseView":
// Display the License View
break;
... etc.
}
}
You should assign a unique ViewName to each tile on your window in order to easily determine which tile was clicked upon and thus what action your program needs to take.
Summary
A Windows Phone or Windows 8 tile is very easy to create in XAML. In this blog post you learned how just a few lines of XAML and some event wire-ups make short work of creating a list of Tile objects. In the sample that comes with this blog post a WrapPanel is used to allow the tiles to be moved around fairly easy. You could put a ScrollViewer control around the WrapPanel to allow the set of Tiles to grow in any direction you wish.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “Creating a XAML Tile Control” from the drop down list.
Some of our customers are asking us to give them a Windows 8 look and feel for their applications. This includes things like buttons, tiles, application bars, and other features. In this blog post I will describe how to create a button that looks similar to those you will find in a Windows 8 application bar.
In Figure 1 you can see two different kinds of buttons. In the top row is a WPF button where the content of the button includes a Border, an Image and a TextBlock. In the bottom row are four individual user controls that have a Windows 8 style look and feel. The “Edit” button in Figure 1 has the mouse hovering over it so you can see how it looks when the user is about to click on it.

Figure 1: It is best to create a custom user control to get a more polished look and feel for a button control.
If you read my previous blog post on creating a custom Button user control, you will find this blog post very similar.
There are many ways to create custom buttons and there are advantages and disadvantages to each way. The purpose of this blog post is to present one method that is easily understood by almost any XAML programmer, and hopefully to those new to XAML as well. User controls have been around since the Visual Basic 4.0 days. Most developers understand the value of using user controls. With XAML user controls you can put these controls into a WPF User Control Library or a Silverlight Class Library and reference those DLLs from any WPF or Silverlight application. This gives you great reusability.
The User Control
The XAML for this kind of Windows 8 application bar style-button is a little more complicated than the simple buttons shown in my previous blog posts. However, the basics are shown below:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Name="borMain"
Style="{StaticResource
pdsaButtonImageTextBorderStyle}"
MouseEnter="borMain_MouseEnter"
MouseLeave="borMain_MouseLeave"
MouseLeftButtonDown="borMain_MouseLeftButtonDown"
ToolTip="{Binding Path=ToolTip}">
<VisualStateManager.VisualStateGroups>
... MORE XAML HERE ...
</VisualStateManager.VisualStateGroups>
<Image Source="{Binding Path=ImageUri}"
Style="{StaticResource
pdsaButtonImageTextImageStyle}" />
</Border>
<TextBlock Grid.Row="1"
Name="tbText"
Style="{StaticResource
pdsaButtonImageTextTextBlockStyle}"
Text="{Binding Path=Text}" />
</Grid>
There is a Grid, a Border, an Image and a TextBlock control all combined to form the buttons shown in row 2 of Figure 1. The above XAML is fairly easy to understand as this is just combining standard controls into a format that gives you the look required for your button. The Border, the Image and the TextBlock have a named style applied to them. The definition for this user control is in a DLL named PDSA.WPF. A default resource dictionary is included in the DLL where this user control is located to give you a default look and feel; however, you can make a copy of this resource dictionary and change the look to meet your needs.
Adding the Visual State Manager
In the original blog post on creating a button user control I wrote code to change a button’s state using C#. In this blog post I have replaced most of this code with XAML in the form of the Visual State Manager. A Visual State Manager (VSM) is a container for a storyboard in which you specify a series of actions to perform on different attributes of your controls. To give the user feedback when they hover over a button you use the Visual State Manager to change attributes of controls.
In the following VSM there are two visual states; MouseEnter and MouseLeave. The MouseLeave is empty which tells the VSM to return all properties changed during the MouseEnter back to their original values. In the MouseEnter state is where you modify three properties of the Border control. First you modify the BorderBrush color to the color specified in the style named “pdsaButtonImageTextBorderHoverColor”. You also modify the Background color of the border to the color specified in the style name “pdsaButtonImageTextBackHoverColor”. Finally, the Margin property of the Border control is modified slightly in order to make the button appear to move up.
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="MouseEnter">
<Storyboard>
<ColorAnimation
To="{StaticResource
pdsaButtonImageTextBorderHoverColor}"
Duration="0:0:00.1"
Storyboard.TargetName="borMain"
Storyboard.TargetProperty="BorderBrush.Color" />
<ColorAnimation
To="{StaticResource
pdsaButtonImageTextBackHoverColor}"
Duration="0:0:00.1"
Storyboard.TargetName="borMain"
Storyboard.TargetProperty="Background.Color" />
<ThicknessAnimation
To="{StaticResource
pdsaButtonImageTextHoverThickness}"
Duration="0:0:00.1"
Storyboard.TargetName="borMain"
Storyboard.TargetProperty="Margin" />
</Storyboard>
</VisualState>
<VisualState Name="MouseLeave" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
The XAML below shows the default styles used in the Visual State Manager. These styles come from the PDSAButtonStyles.xaml resource dictionary contained in the PDSA.WPF dll.
<!-- Border color while hovering over button -->
<Color x:Key="pdsaButtonImageTextBorderHoverColor">
Gray
</Color>
<!-- Background color while hovering over button -->
<Color x:Key="pdsaButtonImageTextBackHoverColor">
Gray
</Color>
<!-- Thickness while hovering over button -->
<Thickness x:Key="pdsaButtonImageTextHoverThickness">
4,2,4,4
</Thickness>
Writing the Mouse Events
To trigger the Visual State Manager to run its storyboard in response to the specified event, you respond to the MouseEnter event on the Border control. In the code behind for this event call the GoToElementState() method of the VisualStateManager class exposed by the user control. To this method you will pass in the target element (“borMain”) and the state (“MouseEnter”). The VisualStateManager will then run the storyboard contained within the defined state in the XAML.
private void borMain_MouseEnter(object sender,
MouseEventArgs e)
{
VisualStateManager.GoToElementState(borMain,
"MouseEnter", true);
}
Write code in the MouseLeave event and call the VisualStateManager’s GoToElementState method and specify “MouseLeave” as the state to go to.
private void borMain_MouseLeave(object sender,
MouseEventArgs e)
{
VisualStateManager.GoToElementState(borMain,
"MouseLeave", true);
}
The Default Resource Dictionary
Below is the definition of the resource dictionary file contained in the PDSA.WPF DLL. This dictionary is used as the default look and feel for any Image/Text Button control you add to a window or user control.
<ResourceDictionary ...>
<!-- ****************************** -->
<!-- ** Image/Text Button Styles ** -->
<!-- ****************************** -->
<!-- Image/Text Button Border -->
<Style TargetType="Border"
x:Key="pdsaButtonImageTextBorderStyle">
<Setter Property="Margin"
Value="4" />
<Setter Property="BorderBrush"
Value="White" />
<Setter Property="BorderThickness"
Value="2" />
<Setter Property="HorizontalAlignment"
Value="Center" />
<Setter Property="CornerRadius"
Value="50" />
<Setter Property="Width"
Value="32" />
<Setter Property="Height"
Value="32" />
<Setter Property="Background"
Value="Transparent" />
</Style>
<!-- Image/Text Button Image -->
<Style TargetType="Image"
x:Key="pdsaButtonImageTextImageStyle">
<Setter Property="Margin"
Value="0" />
</Style>
<!-- Image/Text Button TextBlock -->
<Style TargetType="TextBlock"
x:Key="pdsaButtonImageTextTextBlockStyle">
<Setter Property="Margin"
Value="2" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="HorizontalAlignment"
Value="Center" />
<Setter Property="FontSize"
Value="9" />
</Style>
<!-- Border color while hovering over button -->
<Color x:Key="pdsaButtonImageTextBorderHoverColor">
Gray
</Color>
<!-- Background color while hovering over button -->
<Color x:Key="pdsaButtonImageTextBackHoverColor">
Gray
</Color>
<!-- Thickness while hovering over button -->
<Thickness x:Key="pdsaButtonImageTextHoverThickness">
4,2,4,4
</Thickness>
</ResourceDictionary>
Feel free to modify this resource dictionary, or copy it and modify your new copy in order to give another look and feel to these buttons. Keep the “x:Key” name the same, other than that, you can modify any other attribute.
Using the Button Control
Once you make a reference to the PDSA.WPF DLL from your WPF application you will see the “PDSAucButtonImageText” control appear in your Toolbox. Drag and drop the button onto a Window or User Control in your application. I have not referenced the PDSAButtonStyles.xaml file within the control itself so add a reference to this resource dictionary in your Application Resources section defined in App.xaml.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="/PDSA.WPF;component/PDSAButtonStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Your buttons now have a default look and feel unless you override the application resource dictionary on a specific Window/User Control or on an individual button. After you have given a global style to your application and you drag your image/text button onto a window, the following will appear in your XAML window.
<my:PDSAucButtonImageText ... />
There will be some other attributes set on the above XAML, but you just need to set the x:Name, the Text, ToolTip and ImageUri properties. You will also want to respond to the Click event procedure in order to associate an action with clicking on this button. In the sample code you download for this blog post you will find the declaration of the Edit button to be the following:
<my:PDSAucButtonImageText
Name="btnEdit"
ImageUri="/PDSA.WPF;component/Images/Edit_White.png"
Text="Edit"
Click="btnEdit_Click" />
The Text and ImageUri properties are dependency properties in the PDSAucButtonImageText user control. The x:Name and the ToolTip we get for free. Since a Border control does not have a Click event you will create one by using the MouseLeftButtonDown on the border to fire your custom “Click” event. Code the “Click” event in the PDSAucButtonImageText user control using the code shown below:
private void borMain_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e)
{
RaiseClick(e);
}
public delegate void ClickEventHandler(object sender,
RoutedEventArgs e);
public event ClickEventHandler Click;
protected void RaiseClick(RoutedEventArgs e)
{
if (null != Click)
Click(this, e);
}
Summary
This blog post built upon the previous posts where I explained how to build a button user control. The user control presented in this post adds both text and an image and adds a little XAML to the storyboard in the Visual State Manager. With the appropriate styles applied you can get a Windows 8 look and feel for these application bar buttons. Feel free to modify the styles to take on any look you want for your buttons.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “A WPF Image/Text Button” from the drop down list.
Instead of a normal button with words, sometimes you want a button that is just graphical. Yes, you can put an Image control in the Content of a normal Button control, but you still have the button outline, and trying to change the style can be rather difficult. Instead I like creating a user control that simulates a button, but just accepts an image. Figure 1 shows an example of three of these custom user controls to represent minimize, maximize and close buttons for a borderless window. Notice the highlighted image button has a gray rectangle around it. You will learn how to highlight using the VisualStateManager in this blog post.

Figure 1: Creating a custom user control for things like image buttons gives you complete control over the look and feel.
I would suggest you read my previous blog post on creating a custom Button user control as that is a good primer for what I am going to expand upon in this blog post. You can find this blog post at http://weblogs.asp.net/psheriff/archive/2012/08/10/create-your-own-wpf-button-user-controls.aspx.
The User Control
The XAML for this image button user control contains just a few controls, plus a Visual State Manager. The basic outline of the user control is shown below:
<Border Grid.Row="0"
Name="borMain"
Style="{StaticResource pdsaButtonImageBorderStyle}"
MouseEnter="borMain_MouseEnter"
MouseLeave="borMain_MouseLeave"
MouseLeftButtonDown="borMain_MouseLeftButtonDown">
<VisualStateManager.VisualStateGroups>
... MORE XAML HERE ...
</VisualStateManager.VisualStateGroups>
<Image Style="{StaticResource pdsaButtonImageImageStyle}"
Visibility="{Binding Path=Visibility}"
Source="{Binding Path=ImageUri}"
ToolTip="{Binding Path=ToolTip}" />
</Border>
There is a Border control named borMain and a single Image control in this user control. That is all that is needed to display the buttons shown in Figure 1. The definition for this user control is in a DLL named PDSA.WPF. The Style definitions for both the Border and the Image controls are contained in a resource dictionary names PDSAButtonStyles.xaml. Using a resource dictionary allows you to create a few different resource dictionaries, each with a different theme for the buttons.
The Visual State Manager
To display the highlight around the button as your mouse moves over the control, you will need to add a Visual State Manager group. Two different states are needed; MouseEnter and MouseLeave. In the MouseEnter you create a ColorAnimation to modify the BorderBrush color of the Border control. You specify the color to animate as “DarkGray”. You set the duration to less than a second. The TargetName of this storyboard is the name of the Border control “borMain” and since we are specifying a single color, you need to set the TargetProperty to “BorderBrush.Color”. You do not need any storyboard for the MouseLeave state. Leaving this VisualState empty tells the Visual State Manager to put everything back the way it was before the MouseEnter event.
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="MouseEnter">
<Storyboard>
<ColorAnimation
To="DarkGray"
Duration="0:0:00.1"
Storyboard.TargetName="borMain"
Storyboard.TargetProperty="BorderBrush.Color" />
</Storyboard>
</VisualState>
<VisualState Name="MouseLeave" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Writing the Mouse Events
To trigger the Visual State Manager to run its storyboard in response to the specified event, you need to respond to the MouseEnter event on the Border control. In the code behind for this event call the GoToElementState() method of the VisualStateManager class exposed by the user control. To this method you will pass in the target element (“borMain”) and the state (“MouseEnter”). The VisualStateManager will then run the storyboard contained within the defined state in the XAML.
private void borMain_MouseEnter(object sender,
MouseEventArgs e)
{
VisualStateManager.GoToElementState(borMain,
"MouseEnter", true);
}
You also need to respond to the MouseLeave event. In this event you call the VisualStateManager as well, but specify “MouseLeave” as the state to go to.
private void borMain_MouseLeave(object sender,
MouseEventArgs e)
{
VisualStateManager.GoToElementState(borMain,
"MouseLeave", true);
}
The Resource Dictionary
Below is the definition of the PDSAButtonStyles.xaml resource dictionary file contained in the PDSA.WPF DLL. This dictionary can be used as the default look and feel for any image button control you add to a window.
<ResourceDictionary ... >
<!-- ************************* -->
<!-- ** Image Button Styles ** -->
<!-- ************************* -->
<!-- Image/Text Button Border -->
<Style TargetType="Border"
x:Key="pdsaButtonImageBorderStyle">
<Setter Property="Margin"
Value="4" />
<Setter Property="Padding"
Value="2" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="HorizontalAlignment"
Value="Left" />
<Setter Property="Background"
Value="Transparent" />
</Style>
<!-- Image Button -->
<Style TargetType="Image"
x:Key="pdsaButtonImageImageStyle">
<Setter Property="Width"
Value="40" />
<Setter Property="Margin"
Value="6" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="HorizontalAlignment"
Value="Left" />
</Style>
</ResourceDictionary>
Using the Button Control
Once you make a reference to the PDSA.WPF DLL from your WPF application you will see the “PDSAucButtonImage” control appear in your Toolbox. Drag and drop the button onto a Window or User Control in your application. I have not referenced the PDSAButtonStyles.xaml file within the control itself so you do need to add a reference to this resource dictionary somewhere in your application such as in the App.xaml.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="/PDSA.WPF;component/PDSAButtonStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
This will give your buttons a default look and feel unless you override that dictionary on a specific Window or User Control or on an individual button. After you have given a global style to your application and you drag your image button onto a window, the following will appear in your XAML window.
<my:PDSAucButtonImage ... />
There will be some other attributes set on the above XAML, but you simply need to set the x:Name, the ToolTip and ImageUri properties. You will also want to respond to the Click event procedure in order to associate an action with clicking on this button. In the sample code you download for this blog post you will find the declaration of the Minimize button to be the following:
<my:PDSAucButtonImage
x:Name="btnMinimize"
Click="btnMinimize_Click"
ToolTip="Minimize Application"
ImageUri="/PDSA.WPF;component/Images/Minus.png" />
The ImageUri property is a dependency property in the PDSAucButtonImage user control. The x:Name and the ToolTip we get for free. You have to create the Click event procedure yourself. This is also created in the PDSAucButtonImage user control as follows:
private void borMain_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e)
{
RaiseClick(e);
}
public delegate void ClickEventHandler(object sender,
RoutedEventArgs e);
public event ClickEventHandler Click;
protected void RaiseClick(RoutedEventArgs e)
{
if (null != Click)
Click(this, e);
}
Since a Border control does not have a Click event you will create one by using the MouseLeftButtonDown on the border to fire an event you create called “Click”.
Summary
Creating your own image button control can be done in a variety of ways. In this blog post I showed you how to create a custom user control and simulate a button using a Border and Image control. With just a little bit of code to respond to the MouseLeftButtonDown event on the border you can raise your own Click event. Dependency properties, such as ImageUri, allow you to set attributes on your custom user control. Feel free to expand on this button by adding additional dependency properties, change the resource dictionary, and even the animation to make this button look and act like you want.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “A WPF Image Button” from the drop down list.
In any application, you want to keep the coupling between any two or more objects as loose as possible. Coupling happens when one class contains a property that is used in another class, or uses another class in one of its methods. If you have this situation, then this is called strong or tight coupling. One popular design pattern to help with keeping objects loosely coupled is called the Mediator design pattern. The basics of this pattern are very simple; avoid one object directly talking to another object, and instead use another class to mediate between the two. As with most of my blog posts, the purpose is to introduce you to a simple approach to using a message broker, not all of the fine details.
IPDSAMessageBroker Interface
As with most implementations of a design pattern, you typically start with an interface or an abstract base class. In this particular instance, an Interface will work just fine. The interface for our Message Broker class just contains a single method “SendMessage” and one event “MessageReceived”.
public delegate void MessageReceivedEventHandler(
object sender, PDSAMessageBrokerEventArgs e);
public interface IPDSAMessageBroker
{
void SendMessage(PDSAMessageBrokerMessage msg);
event MessageReceivedEventHandler MessageReceived;
}
PDSAMessageBrokerMessage Class
As you can see in the interface, the SendMessage method requires a type of PDSAMessageBrokerMessage to be passed to it. This class simply has a MessageName which is a ‘string’ type and a MessageBody property which is of the type ‘object’ so you can pass whatever you want in the body. You might pass a string in the body, or a complete Customer object. The MessageName property will help the receiver of the message know what is in the MessageBody property.
public class PDSAMessageBrokerMessage
{
public PDSAMessageBrokerMessage()
{
}
public PDSAMessageBrokerMessage(string name, object body)
{
MessageName = name;
MessageBody = body;
}
public string MessageName { get; set; }
public object MessageBody { get; set; }
}
PDSAMessageBrokerEventArgs Class
As our message broker class will be raising an event that others can respond to, it is a good idea to create your own event argument class. This class will inherit from the System.EventArgs class and add a couple of additional properties. The properties are the MessageName and Message. The MessageName property is simply a string value. The Message property is a type of a PDSAMessageBrokerMessage class.
public class PDSAMessageBrokerEventArgs : EventArgs
{
public PDSAMessageBrokerEventArgs()
{
}
public PDSAMessageBrokerEventArgs(string name,
PDSAMessageBrokerMessage msg)
{
MessageName = name;
Message = msg;
}
public string MessageName { get; set; }
public PDSAMessageBrokerMessage Message { get; set; }
}
PDSAMessageBroker Class
Now that you have an interface class and a class to pass a message through an event, it is time to create your actual PDSAMessageBroker class. This class implements the SendMessage method and will also create the event handler for the delegate created in your Interface.
public class PDSAMessageBroker : IPDSAMessageBroker
{
public void SendMessage(PDSAMessageBrokerMessage msg)
{
PDSAMessageBrokerEventArgs args;
args = new PDSAMessageBrokerEventArgs(
msg.MessageName, msg);
RaiseMessageReceived(args);
}
public event MessageReceivedEventHandler MessageReceived;
protected void RaiseMessageReceived(
PDSAMessageBrokerEventArgs e)
{
if (null != MessageReceived)
MessageReceived(this, e);
}
}
The SendMessage method will take a PDSAMessageBrokerMessage object as an argument. It then creates an instance of a PDSAMessageBrokerEventArgs class, passing to the constructor two items: the MessageName from the PDSAMessageBrokerMessage object and also the object itself. It may seem a little redundant to pass in the message name when that same message name is part of the message, but it does make consuming the event and checking for the message name a little cleaner – as you will see in the next section.
Create a Global Message Broker
In your WPF application, create an instance of this message broker class in the App class located in the App.xaml file. Create a public property in the App class and create a new instance of that class in the OnStartUp event procedure as shown in the following code:
public partial class App : Application
{
public PDSAMessageBroker MessageBroker { get; set; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MessageBroker = new PDSAMessageBroker();
}
}
Sending and Receiving Messages
Let’s assume you have a user control that you load into a control on your main window and you want to send a message from that user control to the main window. You might have the main window display a message box, or put a string into a status bar as shown in Figure 1.

Figure 1: The main window can receive and send messages
The first thing you do in the main window is to hook up an event procedure to the MessageReceived event of the global message broker. This is done in the constructor of the main window:
public MainWindow()
{
InitializeComponent();
(Application.Current as App).MessageBroker.
MessageReceived += new MessageReceivedEventHandler(
MessageBroker_MessageReceived);
}
One piece of code you might not be familiar with is accessing a property defined in the App class of your XAML application. Within the App.Xaml file is a class named App that inherits from the Application object. You access the global instance of this App class by using Application.Current. You cast Application.Current to ‘App’ prior to accessing any of the public properties or methods you defined in the App class. Thus, the code (Application.Current as App).MessageBroker, allows you to get at the MessageBroker property defined in the App class.
In the MessageReceived event procedure in the main window (shown below) you can now check to see if the MessageName property of the PDSAMessageBrokerEventArgs is equal to “StatusBar” and if it is, then display the message body into the status bar text block control.
void MessageBroker_MessageReceived(object sender,
PDSAMessageBrokerEventArgs e)
{
switch (e.MessageName)
{
case "StatusBar":
tbStatus.Text = e.Message.MessageBody.ToString();
break;
}
}
In the Page 1 user control’s Loaded event procedure you will send the message “StatusBar” through the global message broker to any listener using the following code:
private void UserControl_Loaded(object sender,
RoutedEventArgs e)
{
// Send Status Message
(Application.Current as App).MessageBroker.
SendMessage(new PDSAMessageBrokerMessage("StatusBar",
"This is Page 1"));
}
Since the main window is listening for the message ‘StatusBar’, it will display the value “This is Page 1” in the status bar at the bottom of the main window.
Sending a Message to a User Control
The previous example sent a message from the user control to the main window. You can also send messages from the main window to any listener as well. Remember that the global message broker is really just a broadcaster to anyone who has hooked into the MessageReceived event.
In the constructor of the user control named ucPage1 you can hook into the global message broker’s MessageReceived event. You can then listen for any messages that are sent to this control by using a similar switch-case structure like that in the main window.
public ucPage1()
{
InitializeComponent();
// Hook to the Global Message Broker
(Application.Current as App).MessageBroker.
MessageReceived += new MessageReceivedEventHandler(
MessageBroker_MessageReceived);
}
void MessageBroker_MessageReceived(object sender,
PDSAMessageBrokerEventArgs e)
{
// Look for messages intended for Page 1
switch (e.MessageName)
{
case "ForPage1":
MessageBox.Show(e.Message.MessageBody.ToString());
break;
}
}
Once the ucPage1 user control has been loaded into the main window you can then send a message using the following code:
private void btnSendToPage1_Click(object sender,
RoutedEventArgs e)
{
PDSAMessageBrokerMessage arg =
new PDSAMessageBrokerMessage();
arg.MessageName = "ForPage1";
arg.MessageBody = "Message For Page 1";
// Send a message to Page 1
(Application.Current as App).MessageBroker.SendMessage(arg);
}
Since the MessageName matches what is in the ucPage1 MessageReceived event procedure, ucPage1 can do anything in response to that event. It is important to note that when the message gets sent it is sent to all MessageReceived event procedures, not just the one that is looking for a message called “ForPage1”. If the user control ucPage1 is not loaded and this message is broadcast, but no other code is listening for it, then it is simply ignored.
Remove Event Handler
In each class where you add an event handler to the MessageReceived event you need to make sure to remove those event handlers when you are done. Failure to do so can cause a strong reference to the class and thus not allow that object to be garbage collected. In each of your user control’s make sure in the Unloaded event to remove the event handler.
private void UserControl_Unloaded(object sender,
RoutedEventArgs e)
{
if (_MessageBroker != null)
_MessageBroker.MessageReceived -=
_MessageBroker_MessageReceived;
}
Problems with Message Brokering
As with most “global” classes or classes that hook up events to other classes, garbage collection is something you need to consider. Just the simple act of hooking up an event procedure to a global event handler creates a reference between your user control and the message broker in the App class. This means that even when your user control is removed from your UI, the class will still be in memory because of the reference to the message broker. This can cause messages to still being handled even though the UI is not being displayed. It is up to you to make sure you remove those event handlers as discussed in the previous section. If you don’t, then the garbage collector cannot release those objects.
Instead of using events to send messages from one object to another you might consider registering your objects with a central message broker. This message broker now becomes a collection class into which you pass an object and what messages that object wishes to receive. You do end up with the same problem however. You have to un-register your objects; otherwise they still stay in memory. To alleviate this problem you can look into using the WeakReference class as a method to store your objects so they can be garbage collected if need be. Discussing Weak References is beyond the scope of this post, but you can look this up on the web.
Summary
In this blog post you learned how to create a simple message broker system that will allow you to send messages from one object to another without having to reference objects directly. This does reduce the coupling between objects in your application. You do need to remember to get rid of any event handlers prior to your objects going out of scope or you run the risk of having memory leaks and events being called even though you can no longer access the object that is responding to that event.
NOTE: You can download the sample code for this article by visiting my website at http://www.pdsa.com/downloads. Select “Tips & Tricks”, then select “A Communication System for XAML Applications” from the drop down list.
More Posts
Next page »