You go to great pains to add styles, colors, gradients, and a really cool look and feel to your WPF application only to have that ruined by the standard Windows message box as shown in Figure 1.

Figure 1: The normal Windows message box just does not look right on a styled WPF application.
What would be nice is if Microsoft offered a styled message box. But, they don’t. So it is up to us to create a window that we can style and do whatever we want with it. Thus, I can up with a message box that looks like Figure 2 and Figure 3.

Figure 2: Creating your own dialog from a Window, a Text Block and a custom button control.

Figure 3: You can completely change the theme of your button through the use of resource dictionaries.
The PDSAMessageBoxView XAML
The first step in creating a custom message box is to add a new Window to your WPF project. You then need to set some styles to get a border-less window. You set the following attributes on your Window.
WindowStyle="None"
ShowInTaskbar="True"
ResizeMode=”NoResize”
AllowsTransparency="True"
Background="Transparent"
The WindowStyle attribute normally allows you to set a single border, three-D border, or a Tool Window border. Setting this attribute to None will eliminate the border. The ShowInTaskbar attribute is optional, but if you are doing a dialog window you probably want this window to show up in the Task Bar. Since this is a dialog window, you probably do not want to allow the user to resize this window, thus you set the ResizeMode to “NoResize”. The next two attributes, AllowsTransparency and Background work together. You must set AllowsTransparency to True to allow the Background to be set to Transparent. If you do not set these two attributes, then your border will still show up.
The listing that follows shows the complete XAML for the PDSAMessageBoxView.xaml file. This XAML file contains a Border, a Grid, a TextBlock for the message, and a StackPanel control with four PDSAucButton controls (see my previous blog post on how to create these buttons). All of the attributes for the border, the text block and the buttons are controlled via styles in a Resource Dictionary file. Using a resource dictionary allows you to create new resource dictionaries with different colors and other attributes to style the message box in any manner you see fit.
<Window x:Class="PDSA.WPF.PDSAMessageBoxView"
...
xmlns:my="clr-namespace:PDSA.WPF"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
ResizeMode="NoResize"
ShowInTaskbar="True"
FontFamily="Segoe UI"
WindowStartupLocation="CenterScreen"
Height="300"
Width="420"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
Deactivated="Window_Deactivated" >
<Window.Resources>
<!-- Set style for PDSA Button -->
<Style TargetType="my:PDSAucButton">
<Setter Property="Effect"
Value="{StaticResource pdsaMessageBoxButtonShadow}" />
<Setter Property="Width"
Value="80" />
</Style>
</Window.Resources>
<Border Style="{StaticResource pdsaMessageBoxBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Name="tbMessage"
Style="{StaticResource pdsaMessageBoxTextBlock}"
Text="Message goes here..."
TextWrapping="Wrap" />
<StackPanel Grid.Row="1"
Style="{StaticResource pdsaMessageBoxStackPanel}">
<my:PDSAucButton Text="Yes"
x:Name="btnYes"
Click="btnYes_Click" />
<my:PDSAucButton Text="No"
x:Name="btnNo"
Click="btnNo_Click" />
<my:PDSAucButton Text="OK"
x:Name="btnOk"
Click="btnOk_Click" />
<my:PDSAucButton Text="Cancel"
x:Name="btnCancel"
Click="btnCancel_Click" />
</StackPanel>
</Grid>
</Border>
</Window>
Styles for PDSAMessageBox Class
As mentioned previously, all of the styles for the PDSAMessageBoxView class are contained within a resource dictionary. There is one resource dictionary in the PDSA.WPF DLL where the PDSAMessageBoxView class is located. However, there is an additional resource dictionary in the main project with a blue theme as shown in Figure 3. The listing below is the complete resource dictionary for the gray message box theme.
<ResourceDictionary ... >
<!-- Background for Message Box -->
<LinearGradientBrush x:Key="pdsaMessageBoxBackground"
StartPoint="0.5,0"
EndPoint="0.5,1">
<GradientStop Color="DarkSlateGray"
Offset="0" />
<GradientStop Color="LightGray"
Offset="0.70" />
<GradientStop Color="DarkSlateGray"
Offset="1" />
</LinearGradientBrush>
<!-- Drop Shadow Effect for Message Box -->
<DropShadowEffect Color="Black"
x:Key="pdsaMessageBoxDropShadow"
Opacity=".85"
ShadowDepth="16" />
<!-- Drop Shadow for Message Box Buttons -->
<DropShadowEffect x:Key="pdsaMessageBoxButtonShadow"
Color="Black"
ShadowDepth="8" />
<!-- Border for Message Box -->
<Style TargetType="Border"
x:Key="pdsaMessageBoxBorder">
<Setter Property="CornerRadius"
Value="10" />
<Setter Property="BorderBrush"
Value="Black" />
<Setter Property="BorderThickness"
Value="3" />
<Setter Property="Margin"
Value="16" />
<Setter Property="Effect"
Value="{StaticResource pdsaMessageBoxDropShadow}" />
<Setter Property="Background"
Value="{StaticResource pdsaMessageBoxBackground}" />
</Style>
<!-- Buttons for Message Box -->
<Style TargetType="Button"
x:Key="pdsaMessageBoxButton">
<Setter Property="Margin"
Value="10" />
<Setter Property="Width"
Value="70" />
<Setter Property="Height"
Value="35" />
<Setter Property="FontSize"
Value="14" />
<Setter Property="FontWeight"
Value="SemiBold" />
<Setter Property="Effect"
Value="{StaticResource pdsaMessageBoxButtonShadow}" />
</Style>
<!-- Text Block for Message Box -->
<Style TargetType="TextBlock"
x:Key="pdsaMessageBoxTextBlock">
<Setter Property="Foreground"
Value="Black" />
<Setter Property="Margin"
Value="20,40,20,10" />
<Setter Property="FontSize"
Value="28" />
<Setter Property="TextWrapping"
Value="Wrap" />
</Style>
<!-- StackPanel for Message Box -->
<Style TargetType="StackPanel"
x:Key="pdsaMessageBoxStackPanel">
<Setter Property="Orientation"
Value="Horizontal" />
<Setter Property="HorizontalAlignment"
Value="Right" />
<Setter Property="Margin"
Value="8" />
</Style>
</ResourceDictionary>
Feel free to modify any of the above styles to make the message box look like however you want.
The PDSAMessageBox Class
Just like .NET has the MessageBox class in order to display the dialog, I have created a PDSAMessageBox class with a Show method as well. I made the Show method with the same parameters as the MessageBox class in order to help you make an easier transition to the PDSAMessageBox class. However, I do not use any of the MessageBoxImage parameters, so you will need to remove any of these, or add an image to this view if you so desire. Below is the PDSAMessageBox class with the static Show methods defined.
public class PDSAMessageBox
{
public static MessageBoxResult Show(string message)
{
return Show(message, string.Empty, MessageBoxButton.OK);
}
public static MessageBoxResult Show(string message,
string caption)
{
return Show(message, caption, MessageBoxButton.OK);
}
public static MessageBoxResult Show(string message,
string caption, MessageBoxButton buttons)
{
MessageBoxResult result = MessageBoxResult.None;
PDSAMessageBoxView dialog = new PDSAMessageBoxView();
dialog.Title = caption;
dialog.tbMessage.Text = message;
dialog.Buttons = buttons;
// If just an OK button, allow the user to just
// move away from the dialog
if (buttons == MessageBoxButton.OK)
dialog.Show();
else
{
dialog.ShowDialog();
result = dialog.Result;
}
return result;
}
}
All of the Show methods end up calling just a single Show that takes care of displaying the PDSAMessageBoxView dialog. One thing I did change in my Show method is that if the user pops up a message box with just an “Ok” button, I do allow the user to navigate away from this dialog without pressing the Ok button. To me, it does not make sense to force the user to press a single button on the form. This could have side-effects however, if you have code immediately following the call to the Show method because this is no longer a modal dialog. Again, feel free to modify this code if you do not like this functionality.
Calling the PDSAMessageBox
Below is an example of calling the PDSAMessageBox. The Show method will return a MessageBoxResult enumeration. There was no need to change the return value from that of the normal MessageBox class. This code can be found in the sample that you download along with this blog post.
private void btnOKOnly_Click(object sender, RoutedEventArgs e)
{
PDSAMessageBox.Show("This just displays an OK Button",
"OK", MessageBoxButton.OK);
}
private void btnYesNo_Click(object sender, RoutedEventArgs e)
{
MessageBoxResult result;
result = PDSAMessageBox.Show("Do you want to Quit?",
"Quit?", MessageBoxButton.YesNo);
MessageBox.Show("Result is " + result.ToString());
}
Summary
Creating your own dialog box is very easy using just a little bit of XAML, some styles and a class with a few methods. Break up your styles into resource dictionaries in order to have the flexibility to modify the look and feel of your dialogs. Keep your dialog boxes and buttons in a separate DLL in order to make it easy to reuse your controls.
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 Message Box you can Style” from the drop down list.
In Figure 1 you can see examples of the standard WPF Button controls. You can add a drop shadow and you can change the color, but you can’t change much else without creating a whole new control template. For example, you are unable to modify the BorderBrush or the BorderThickness properties of the Button control. Additionally you might want to use some other animation than the default, which again requires you to change the control template.
Sometimes all you want to do is to just have some simple buttons where you can modify the border brush and the thickness and have different color options via styles. I have found that instead of working with the whole control template thing, just creating a User Control is sometimes much easier.

Figure 1: The normal WPF Buttons cannot be styled very well.
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. By using resource dictionaries and appropriate use of styles you can make your user controls very customizable as shown in Figure 1. The 2nd row of buttons you see are the same button user control with just different styles applied.
The User Control
The XAML for your Button user control is actually very simple. You use a single Border control and a TextBlock control within that Border as shown in Listing 1.
<Border Name="hoverBorder"
Style="{DynamicResource hoverBorder}"
MouseDown="hoverBorder_MouseDown"
MouseUp="hoverBorder_MouseUp"
MouseEnter="hoverBorder_MouseEnter"
MouseLeave="hoverBorder_MouseLeave"
MouseLeftButtonUp="hoverBorder_MouseLeftButtonUp">
<TextBlock Text="{Binding Path=Text}"
Name="tbText"
Style="{DynamicResource hoverTextBlock}" />
</Border>
Listing 1: The XAML for a Button user control is just a Border and TextBlock
The definition for this user control is in a DLL named PDSA.WPF. Notice that there are Style definitions for both the Border and the TextBlock. We used styles contained in a resource dictionary as opposed to setting up dependency properties for each individual attribute that we might want to set. This allows us to create a few different resource dictionaries, each with a different theme for the buttons. You can see the three different themes we created in Figure 1. The Gray Style button uses a resource dictionary that is contained in the PDSA.WPF DLL. The other two styles are in the main project and can be referenced from your App.xaml or from within the Window/User Control where you need the button.
Changing Colors in Response to Mouse Events
When a user moves over a button or presses a button you should give some visual feedback to that user. You can do this with animation using the Visual State Manager or Event Triggers in WPF. To keep things simple for this blog post, I simply respond to the various mouse events and change the Background property to a different color. When the mouse is pressed, I also change the Foreground color of the Text in the TextBlock control. The code for each of the mouse events is shown below.
private void pdsaButtonBorderStyle_MouseEnter(
object sender, MouseEventArgs e)
{
pdsaButtonBorderStyle.Background =
(Brush)this.FindResource("pdsaButtonOverStyle");
}
private void pdsaButtonBorderStyle_MouseLeave(
object sender, MouseEventArgs e)
{
pdsaButtonBorderStyle.Background =
(Brush)this.FindResource("pdsaButtonNormalStyle");
}
private void pdsaButtonBorderStyle_MouseDown(
object sender, MouseButtonEventArgs e)
{
// Save old Foreground Brush
_TextBrush = tbText.Foreground;
pdsaButtonBorderStyle.Background =
(Brush)this.FindResource("pdsaButtonPressedStyle");
tbText.Foreground =
(SolidColorBrush)this.FindResource(
"pdsaButtonTextBlockStylePressed");
}
private void pdsaButtonBorderStyle_MouseUp(
object sender, MouseButtonEventArgs e)
{
RestoreNormal();
}
private void RestoreNormal()
{
pdsaButtonBorderStyle.Background =
(Brush)this.FindResource("pdsaButtonNormalStyle");
tbText.Foreground = _TextBrush;
}
Notice that in the code above that you use the FindResource() method instead of accessing the this.Resources[] collection. That is because you want to be able to set the resource dictionary at any level, not just on this user control. FindResource() will search upward through your UI tree looking for a resource that match the names you see that start with “pdsa”.
You will need a Click event that you can raise when the user clicks on the button. Here is the definition for that Click event.
private void pdsaButtonBorderStyle_MouseLeftButtonUp(
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);
RestoreNormal();
}
The Default Resource Dictionary
Below is the definition of the resource dictionary file contained in the PDSA.WPF DLL. This dictionary can be used as the default look and feel for any button control you add to a window. I have included two additional resource dictionaries in the main project as examples of how you can change the resources to give your buttons a different look. You need to keep the x:Key names the same, but you can change any of the attributes of color or thickness that you want. You can even change from Gradient colors to a SolidColorBrush as you can see I did when you look at the different resource dictionaries.
<ResourceDictionary xmlns="http://schemas.microsoft.com/
winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Hover TextBlock Style -->
<Style TargetType="TextBlock"
x:Key="pdsaButtonTextBlockStyle">
<Setter Property="Foreground"
Value="GhostWhite" />
<Setter Property="Margin"
Value="10" />
<Setter Property="FontSize"
Value="14" />
</Style>
<!-- Hover Brush for Pressed Hover Button Text Block -->
<SolidColorBrush x:Key="pdsaButtonTextBlockStylePressed"
Color="Black" />
<!-- Hover Border Thickness -->
<Thickness x:Key="pdsaButtonBorderStyleThickness"
Bottom="0"
Left="0"
Right="0"
Top="0" />
<!-- Hover Border Brush -->
<SolidColorBrush x:Key="pdsaButtonBorderBrushStyle"
Color="Black" />
<!-- Style for when hovering over button -->
<RadialGradientBrush x:Key="pdsaButtonOverStyle">
<GradientStop Color="#FF5F5F5F"
Offset="0" />
<GradientStop Color="#FFADADAD"
Offset="1" />
</RadialGradientBrush>
<!-- Style for when button is pressed -->
<LinearGradientBrush EndPoint="1,0.5"
StartPoint="0,0.5"
x:Key="pdsaButtonPressedStyle">
<GradientStop Color="#FF5F5F5F"
Offset="0" />
<GradientStop Color="#FFADADAD"
Offset="1" />
</LinearGradientBrush>
<!-- Style for normal button -->
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0"
x:Key="pdsaButtonNormalStyle">
<GradientStop Color="#FF5F5F5F"
Offset="0" />
<GradientStop Color="#FFADADAD"
Offset="1" />
</LinearGradientBrush>
<!-- Overall Style for Hover Button -->
<Style TargetType="Border"
x:Key="pdsaButtonBorderStyle">
<Setter Property="Margin"
Value="6" />
<Setter Property="CornerRadius"
Value="5" />
<Setter Property="Background"
Value="{StaticResource pdsaButtonNormalStyle}" />
<Setter Property="BorderBrush"
Value="{StaticResource
pdsaButtonBorderBrushStyle}" />
<Setter Property="BorderThickness"
Value="{StaticResource
pdsaButtonBorderStyleThickness}" />
</Style>
</ResourceDictionary>
You can look at the other two Resource Dictionary files when you download the sample.
Using the Button Control
Once you make a reference to the PDSA.WPF DLL from your WPF application you will see the “PDSAucButton” 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/User Control or on an individual button. The “Gray Style” button shown in Figure 1 is what the default button looks like after setting the above code in your App.xaml and then dragging a button onto a Window.
If you wish to give a specific style to just a single button you can override the default by using the code below:
<my:PDSAucButton HorizontalAlignment="Left"
Margin="6"
Text="Flat Blue Style"
x:Name="btn2"
Click="btn2_Click"
VerticalAlignment="Top">
<my:PDSAucButton.Resources>
<ResourceDictionary Source="FlatBlueButtonStyles.xaml" />
</my:PDSAucButton.Resources>
</my:PDSAucButton>
If you want to override a series of buttons within one specific StackPanel, or within a specific Window or User Control, you simply set the Resources section for that control as shown below:
<StackPanel Orientation="Horizontal"
VerticalAlignment="Top">
<!-- Set Styles for All Buttons.
This overrides the App.xaml styles -->
<StackPanel.Resources>
<ResourceDictionary Source="FlatBlueButtonStyles.xaml" />
</StackPanel.Resources>
<my:PDSAucButton HorizontalAlignment="Left"
Margin="6"
Text="Blue Style"
x:Name="btn11"
Effect="{StaticResource mainDropShadow}"
VerticalAlignment="Top" />
<!-- Can assign a custom set of styles -->
<my:PDSAucButton HorizontalAlignment="Left"
Margin="6"
Text="Flat Blue Style"
x:Name="btn12"
VerticalAlignment="Top" />
</StackPanel>
In the above sample, all buttons within this StackPanel control are styled using the resource dictionary FlatBlueButtonStyles.xaml.
Summary
Creating your own button control can be done in a variety of ways. You can create a new Button control template, apply styles to change just a few things about a Button, create your own Custom control that inherits from Button, or build a User Control as in this blog post. Which method you choose depends a little on how comfortable you are with each method, and how much control you want over the final details of your new Button. The approach outlined in this blog post is simple and easy to understand and almost anyone can use this technique right away. Feel free to add your own animation to this control or maybe add dependency properties to control the BorderBrush, BorderThickness or any other properties 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 “Create your own WPF Buttons” from the drop down list.
All too often I see people using switch/Select Case statements when using a Factory pattern. The problem with this is if you wish to add the ability to instantiate a new class in your Factory, you need to add a new “case” statement, re-compile the code and redeploy that DLL back out to your client machines, or your server(s). Another way to implement a Factory pattern is to use Reflection and Interfaces to dynamically create an instance of a class. This blog post will show you how to use an XML file, an Interface and the Assembly class to dynamically load a list of assemblies and classes to load into an application at runtime.
An XML File of Assembly/Class Names
You will need a data store to place a list of assembly names and class names that you wish to dynamically create. You could use a database table, but I am going to use an XML file that contains the Assembly Name to load and a Class Name to instantiate from that assembly. Any additional information you wish to add, feel free. In the XML listing below you can see a VendorId, VendorName as well as the AssemblyName and ClassName elements.
<Vendors>
<Vendor>
<VendorId>1</VendorId>
<VendorName>Microsoft</VendorName>
<AssemblyName>Vendor.Microsoft</AssemblyName>
<ClassName>Vendor.Microsoft</ClassName>
</Vendor>
<Vendor>
<VendorId>2</VendorId>
<VendorName>Apple</VendorName>
<AssemblyName>Vendor.Apple</AssemblyName>
<ClassName>Vendor.Apple</ClassName>
</Vendor>
<Vendor>
<VendorId>3</VendorId>
<VendorName>Google</VendorName>
<AssemblyName>Vendor.Google</AssemblyName>
<ClassName>Vendor.Google</ClassName>
</Vendor>
</Vendors>
Load the XML File into a Collection Class
When you have an XML file like that shown above, you will typically create a class with properties that match each element in the XML file. Below is a VendorClass that has properties for each element.
public class VendorClass
{
public int VendorId { get; set; }
public string VendorName { get; set; }
public string AssemblyName { get; set; }
public string ClassName { get; set; }
}
Next you create a collection class using the Generic List<VendorClass> class. In this class there is a LoadVendors() method. This method uses the XElement class to load the Vendors.xml file into memory. LINQ to XML is used to iterate over the XML elements and create a collection of VendorClass objects. This resulting collection of objects is added to the List using the AddRange() method.
public class VendorClasses : List<VendorClass>
{
public VendorClasses LoadVendors()
{
// Load XML file into memory
var xElem = XElement.Load(
GetCurrentDirectory() +
ConfigurationManager.AppSettings["VendorFile"]);
// Read Vendors using LINQ to XML
var vendors = from vend in xElem.Descendants("Vendor")
select new VendorClass
{
VendorId =
Convert.ToInt32(vend.Element("VendorId").Value),
VendorName = vend.Element("VendorName").Value,
AssemblyName = vend.Element("AssemblyName").Value,
ClassName = vend.Element("ClassName").Value
};
// Add vendors read in to the List of Vendor classes
this.AddRange(vendors.ToList());
return this;
}
}
To load an XML file you need to find the location on disk. There is a GetCurrentDirectory() method in this class. This method gets the current directory and removes any \bin\Debug from the end of the path if present. This allows you to get the current path whether you are running in Visual Studio or not.
private string GetCurrentDirectory()
{
string path = null;
path = AppDomain.CurrentDomain.BaseDirectory;
if (path.IndexOf("\\bin") > 0)
{
path = path.Substring(0, path.LastIndexOf("\\bin"));
}
if(!path.EndsWith(@"\"))
path += @"\";
return path;
}
Create a Vendor Object Dynamically
Now that you have the list of assembly and class names loaded into a collection you can now create an instance of a specific class. In order to do this you must have an Interface or a base class. I have created an Interface called IVendor. This interface will need to be implemented by each Vendor class that you will be dynamically loading. This interface is located in a separate assembly from all other classes and from the main application. This assembly will need to be referenced by the main application as well as from each assembly where your Vendor classes are located.
public interface IVendor
{
int VendorId { get; set; }
string VendorName { get; set; }
string GetVendorInfo();
}
Below is an example of a vendor class named “Apple” that implements the IVendor interface. The GetVendorInfo() method will simply return a string just so we can verify that the class did get created correctly.
public class Apple : IVendor
{
public int VendorId { get; set; }
public string VendorName { get; set; }
public string GetVendorInfo()
{
return "Apple Corporation";
}
}
Below is another example of a vendor class named “Microsoft” that also implements the IVendor interface.
public class Microsoft : IVendor
{
public int VendorId { get; set; }
public string VendorName { get; set; }
public string GetVendorInfo()
{
return "Microsoft Corporation";
}
}
Each of the above classes are located in separate assemblies as you can see by the AssemblyName element in the XML file. These DLLs do NOT need to be referenced from your project, and in fact, should not be. The Create() method shown below uses the Assembly.LoadFile() method to load the assembly from the current directory. That means that the assembly where each Vendor class is located must be in the current directory for this sample. After the DLL has been loaded, you use the CreateInstance() method on the loaded assembly to load the specific class name. Finally you take the VendorId and the VendorName and place it into the Vendor object’s appropriate properties. This newly created Vendor object is returned from this method.
public class VendorClass
{
// Properties here…
public IVendor Create()
{
Assembly assm;
IVendor vendor = null;
// Load Assembly File
assm = Assembly.LoadFile(
AppDomain.CurrentDomain.BaseDirectory +
AssemblyName + ".dll");
// Create new instance of Class
vendor = (IVendor)assm.CreateInstance(ClassName);
// Set properties in the Provider class from the XML file
vendor.VendorId = VendorId;
vendor.VendorName = VendorName;
return vendor;
}
}
The Sample to Test Activation
To test activating each of these vendor classes I have created a WPF application (Figure 1). The Load Vendors button will call the LoadVendors() method to build the collection of VendorClass objects from the XML file.

Figure 1: A sample to test our activation
Below is the code in the btnLoad_Click event procedure that loads the collection of VendorClass objects.
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
VendorClasses classes = new VendorClasses();
// Load all vendors in the XML file
classes.LoadVendors();
lstVendors.DataContext = classes;
}
Once the VendorClass collection is in the List Box you can click on the list box to fire the SelectionChanged event procedure. In this event procedure you will cast the SelectedItem property of the list box into a VendorClass object. You can then call the Create() method to instantiate the Vendor object by loading the assembly and creating an instance of the Vendor object. After the Vendor object has been created you can call the GetVendorInfo() method to verify that the assembly has been loaded and an instance of the class has been created.
private void lstVendors_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
VendorClass vendor;
IVendor vend;
// Get the currently selected vendor
vendor = (VendorClass)lstVendors.SelectedItem;
// Create an instance of a Vendor class
vend = vendor.Create();
tbVendorName.Text = vend.GetVendorInfo();
}
Summary
In this blog post you learned how to use LINQ to XML to load a collection of classes that contain information about other classes that you will eventually load. You saw how to use the Assembly class to load an assembly from disk and instantiate a class from a string value. An Interface is used to ensure that each class to be instantiated has the same set of methods and properties. Using this technique can eliminate a switch/Select case statement and allow you to dynamically add classes to any application.
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 “XML Activator” from the drop down list.
Earlier this year I blogged on how to use the WPF Tree View to view multiple levels. Since then I have had many requests to do the same in WPF. Luckily, the code is almost identical. Here is a blog post on using the WPF Tree View that has multiple levels.
There are many examples of the WPF Tree View that you will find on the web, however, most of them only show you how to go to two levels. What if you have more than two levels? This is where understanding exactly how the Hierarchical Data Templates works is vital. In this blog post, I am going to break down how these templates work so you can really understand what is going on underneath the hood. To start, let’s look at the typical two-level WPF Tree View that has been hard coded with the values shown below:
<TreeView>
<TreeViewItem Header="Managers">
<TextBlock Text="Michael" />
<TextBlock Text="Paul" />
</TreeViewItem>
<TreeViewItem Header="Supervisors">
<TextBlock Text="John" />
<TextBlock Text="Tim" />
<TextBlock Text="David" />
</TreeViewItem>
</TreeView>
Figure 1 shows you how this tree view looks when you run the WPF application.

Figure 1: A hard-coded, two level Tree View.
Next, let’s create three classes to mimic the hard-coded Tree View shown above. First, you need an Employee class and an EmployeeType class. The Employee class simply has one property called Name. The constructor is created to accept a “name” argument that you can use to set the Name property when you create an Employee object.
public class Employee
{
public Employee(string name)
{
Name = name;
}
public string Name { get; set; }
}
Create an EmployeeType class that has one property called EmpType and contains a generic List<> collection of Employee objects. The property that holds the collection is called Employees.
public class EmployeeType
{
public EmployeeType(string empType)
{
EmpType = empType;
Employees = new List<Employee>();
}
public string EmpType { get; set; }
public List<Employee> Employees { get; set; }
}
Finally we have a collection class called EmployeeTypes created using the generic List<> class. It is in the constructor for this class where you will build the collection of EmployeeTypes and fill it with Employee objects:
public class EmployeeTypes : List<EmployeeType>
{
public EmployeeTypes()
{
EmployeeType type;
type = new EmployeeType("Manager");
type.Employees.Add(new Employee("Michael"));
type.Employees.Add(new Employee("Paul"));
this.Add(type);
type = new EmployeeType("Project Managers");
type.Employees.Add(new Employee("Tim"));
type.Employees.Add(new Employee("John"));
type.Employees.Add(new Employee("David"));
this.Add(type);
}
}
You now have a data hierarchy in memory (Figure 2) which is what the Tree View control expects to receive as its data source.

Figure 2: A hierachial data structure of Employee Types containing a collection of Employee objects.
To connect up this hierarchy of data to your Tree View you create an instance of the EmployeeTypes class in XAML as shown in line 13 of Figure 3. The key assigned to this object is “empTypes”. This key is used as the source of data to the entire Tree View by setting the ItemsSource property as shown in Figure 3, Callout #1.

Figure 3: You need to start from the bottom up when laying out your templates for a Tree View.
The ItemsSource property of the Tree View control is used as the data source in the Hierarchical Data Template with the key of employeeTypeTemplate. In this case there is only one Hierarchical Data Template, so any data you wish to display within that template comes from the collection of Employee Types. The TextBlock control in line 20 uses the EmpType property of the EmployeeType class. You specify the name of the Hierarchical Data Template to use in the ItemTemplate property of the Tree View (Callout #2).
For the second (and last) level of the Tree View control you use a normal <DataTemplate> with the name of employeeTemplate (line 14). The Hierarchical Data Template in lines 17-21 sets its ItemTemplate property to the key name of employeeTemplate (Line 19 connects to Line 14). The source of the data for the <DataTemplate> needs to be a property of the EmployeeTypes collection used in the Hierarchical Data Template. In this case that is the Employees property. In the Employees property there is a “Name” property of the Employee class that is used to display the employee name in the second level of the Tree View (Line 15).
What is important here is that your lowest level in your Tree View is expressed in a <DataTemplate> and should be listed first in your Resources section. The next level up in your Tree View should be a <HierarchicalDataTemplate> which has its ItemTemplate property set to the key name of the <DataTemplate> and the ItemsSource property set to the data you wish to display in the <DataTemplate>. The Tree View control should have its ItemsSource property set to the data you wish to display in the <HierarchicalDataTemplate> and its ItemTemplate property set to the key name of the <HierarchicalDataTemplate> object. It is in this way that you get the Tree View to display all levels of your hierarchical data structure.
Three Levels in a Tree View
Now let’s expand upon this concept and use three levels in our Tree View (Figure 4). This Tree View shows that you now have EmployeeTypes at the top of the tree, followed by a small set of employees that themselves manage employees. This means that the EmployeeType class has a collection of Employee objects. Each Employee class has a collection of Employee objects as well.

Figure 4: When using 3 levels in your TreeView you will have 2 Hierarchical Data Templates and 1 Data Template.
The EmployeeType class has not changed at all from our previous example. However, the Employee class now has one additional property as shown below:
public class Employee
{
public Employee(string name)
{
Name = name;
ManagedEmployees = new List<Employee>();
}
public string Name { get; set; }
public List<Employee> ManagedEmployees { get; set; }
}
The next thing that changes in our code is the EmployeeTypes class. The constructor now needs additional code to create a list of managed employees. Below is the new code.
public class EmployeeTypes : List<EmployeeType>
{
public EmployeeTypes()
{
EmployeeType type;
Employee emp;
Employee managed;
type = new EmployeeType("Manager");
emp = new Employee("Michael");
managed = new Employee("John");
emp.ManagedEmployees.Add(managed);
managed = new Employee("Tim");
emp.ManagedEmployees.Add(managed);
type.Employees.Add(emp);
emp = new Employee("Paul");
managed = new Employee("Michael");
emp.ManagedEmployees.Add(managed);
managed = new Employee("Cindy");
emp.ManagedEmployees.Add(managed);
type.Employees.Add(emp);
this.Add(type);
type = new EmployeeType("Project Managers");
type.Employees.Add(new Employee("Tim"));
type.Employees.Add(new Employee("John"));
type.Employees.Add(new Employee("David"));
this.Add(type);
}
}
Now that you have all of the data built in your classes, you are now ready to hook up this three-level structure to your Tree View. Figure 5 shows the complete XAML needed to hook up your three-level Tree View. You can see in the XAML that there are now two Hierarchical Data Templates and one Data Template. Again you list the Data Template first since that is the lowest level in your Tree View. The next Hierarchical Data Template listed is the next level up from the lowest level, and finally you have a Hierarchical Data Template for the first level in your tree. You need to work your way from the bottom up when creating your Tree View hierarchy. XAML is processed from the top down, so if you attempt to reference a XAML key name that is below where you are referencing it from, you will get a runtime error.

Figure 5: For three levels in a Tree View you will need two Hierarchical Data Templates and one Data Template.
Each Hierarchical Data Template uses the previous template as its ItemTemplate. The ItemsSource of each Hierarchical Data Template is used to feed the data to the previous template. This is probably the most confusing part about working with the Tree View control. You are expecting the content of the current Hierarchical Data Template to use the properties set in the ItemsSource property of that template. But you need to look to the template lower down in the XAML to see the source of the data as shown in Figure 6.

Figure 6: The properties you use within the Content of a template come from the ItemsSource of the next template in the resources section.
Summary
Understanding how to put together your hierarchy in a Tree View is simple once you understand that you need to work from the bottom up. Start with the bottom node in your Tree View and determine what that will look like and where the data will come from. You then build the next Hierarchical Data Template to feed the data to the previous template you created. You keep doing this for each level in your Tree View until you get to the last level. The data for that last Hierarchical Data Template comes from the ItemsSource in the Tree View itself.
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 “WPF TreeView with Multiple Levels” from the drop down list.
In previous blog posts I have discussed how to use XML files to store data in your applications. I showed you how to read those XML files from your project and get XML from a WCF service. One of the problems with reading XML files is when elements or attributes are missing. If you try to read that missing data, then a null value is returned. This can cause a problem if you are trying to load that data into an object and a null is read. This blog post will show you how to create extension methods to detect null values and return valid values to load into your object.
The XML Data
An XML data file called Product.xml is located in the \Xml folder of the Silverlight sample project for this blog post. This XML file contains several rows of product data that will be used in each of the samples for this post. Each row has 4 attributes; namely ProductId, ProductName, IntroductionDate and Price.
<Products>
<Product ProductId="1"
ProductName="Haystack Code Generator for .NET"
IntroductionDate="07/01/2010" Price="799" />
<Product ProductId="2"
ProductName="ASP.Net Jumpstart Samples"
IntroductionDate="05/24/2005" Price="0" />
...
...
</Products>
The Product Class
Just as you create an Entity class to map each column in a table to a property in a class, you should do the same for an XML file too. In this case you will create a Product class with properties for each of the attributes in each element of product data. The following code listing shows the Product class.
public class Product : CommonBase
{
public const string XmlFile = @"Xml/Product.xml";
private string _ProductName;
private int _ProductId;
private DateTime _IntroductionDate;
private decimal _Price;
public string ProductName
{
get { return _ProductName; }
set {
if (_ProductName != value) {
_ProductName = value;
RaisePropertyChanged("ProductName");
}
}
}
public int ProductId
{
get { return _ProductId; }
set {
if (_ProductId != value) {
_ProductId = value;
RaisePropertyChanged("ProductId");
}
}
}
public DateTime IntroductionDate
{
get { return _IntroductionDate; }
set {
if (_IntroductionDate != value) {
_IntroductionDate = value;
RaisePropertyChanged("IntroductionDate");
}
}
}
public decimal Price
{
get { return _Price; }
set {
if (_Price != value) {
_Price = value;
RaisePropertyChanged("Price");
}
}
}
}
NOTE: The CommonBase class that the Product class inherits from simply implements the INotifyPropertyChanged event in order to inform your XAML UI of any property changes. You can see this class in the sample you download for this blog post.
Reading Data
When using LINQ to XML you call the Load method of the XElement class to load the XML file. Once the XML file has been loaded, you write a LINQ query to iterate over the “Product” Descendants in the XML file. The “select” portion of the LINQ query creates a new Product object for each row in the XML file. You retrieve each attribute by passing each attribute name to the Attribute() method and retrieving the data from the “Value” property. The Value property will return a null if there is no data, or will return the string value of the attribute. The Convert class is used to convert the value retrieved into the appropriate data type required by the Product class.
private void LoadProducts()
{
XElement xElem = null;
try
{
xElem = XElement.Load(Product.XmlFile);
// The following will NOT work if you have missing attributes
var products =
from elem in xElem.Descendants("Product")
orderby elem.Attribute("ProductName").Value
select new Product
{
ProductId = Convert.ToInt32(
elem.Attribute("ProductId").Value),
ProductName = Convert.ToString(
elem.Attribute("ProductName").Value),
IntroductionDate = Convert.ToDateTime(
elem.Attribute("IntroductionDate").Value),
Price = Convert.ToDecimal(elem.Attribute("Price").Value)
};
lstData.DataContext = products;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This is where the problem comes in. If you have any missing attributes in any of the rows in the XML file, or if the data in the ProductId or IntroductionDate is not of the appropriate type, then this code will fail! The reason? There is no built-in check to ensure that the correct type of data is contained in the XML file. This is where extension methods can come in real handy.
Using Extension Methods
Instead of using the Convert class to perform type conversions as you just saw, create a set of extension methods attached to the XAttribute class. These extension methods will perform null-checking and ensure that a valid value is passed back instead of an exception being thrown if there is invalid data in your XML file.
private void LoadProducts()
{
var xElem = XElement.Load(Product.XmlFile);
var products =
from elem in xElem.Descendants("Product")
orderby elem.Attribute("ProductName").Value
select new Product
{
ProductId = elem.Attribute("ProductId").GetAsInteger(),
ProductName = elem.Attribute("ProductName").GetAsString(),
IntroductionDate =
elem.Attribute("IntroductionDate").GetAsDateTime(),
Price = elem.Attribute("Price").GetAsDecimal()
};
lstData.DataContext = products;
}
Writing Extension Methods
To create an extension method you will create a class with any name you like. In the code listing below is a class named XmlExtensionMethods. This listing just shows a couple of the available methods such as GetAsString and GetAsInteger. These methods are just like any other method you would write except when you pass in the parameter you prefix the type with the keyword “this”. This lets the compiler know that it should add this method to the class specified in the parameter.
public static class XmlExtensionMethods
{
public static string GetAsString(this XAttribute attr)
{
string ret = string.Empty;
if (attr != null && !string.IsNullOrEmpty(attr.Value))
{
ret = attr.Value;
}
return ret;
}
public static int GetAsInteger(this XAttribute attr)
{
int ret = 0;
int value = 0;
if (attr != null && !string.IsNullOrEmpty(attr.Value))
{
if(int.TryParse(attr.Value, out value))
ret = value;
}
return ret;
}
...
...
}
Each of the methods in the XmlExtensionMethods class should inspect the XAttribute to ensure it is not null and that the value in the attribute is not null. If the value is null, then a default value will be returned such as an empty string or a 0 for a numeric value.
Summary
Extension methods are a great way to simplify your code and provide protection to ensure problems do not occur when reading data. You will probably want to create more extension methods to handle XElement objects as well for when you use element-based XML. Feel free to extend these extension methods to accept a parameter which would be the default value if a null value is detected, or any other parameters you wish.
NOTE: You can download the complete sample code at my website. http://www.pdsa.com/downloads. Choose “Tips & Tricks”, then "Read XML Files using LINQ to XML and Extension Methods" from the drop-down.
Good Luck with your Coding,
Paul D. Sheriff
Many of us have implemented logging in our ASP.NET, Windows Forms and WPF applications, so why shouldn’t you do the same in your Silverlight applications? Well, you should. In this blog post I will show you one approach on how you might perform this logging. The class I will use is called PDSALoggingManager. This class has a method named Log() you use to publish data into a log file in your Silverlight application. A method named LogException() is also available for logging information about any exceptions that happen on the client-side of your Silverlight application. Let’s take a look at the usage of the PDSALoggingManager class.
Logging Data
The simplest way to log information using the PDSALoggingManager class is to call the Log() method with some string data as shown below:
PDSALoggingManager.Instance.Log("Some data to log");
This will add the string passed to the Log() method to an internal StringBuilder object that contains the log information followed by a NewLine character. The Log() method also writes the string to a file located in isolated storage. What is written for each piece of data passed to the Log() method is shown here:
'Informational' log entry written on 5/22/2012 5:51:48 AM, from class: 'SL_Log.MainPage'
Some Data To Log
If you set the LogSystemInfo property on the PDSALoggingManager class prior to calling Log(), then system information is written to the log at the same time as the log data. Below is a sample of the log data with the system information appended to the end.
---------------------------------------------------------
'Informational' log entry written on 5/22/2012 5:51:48 AM, from class: 'SL_Log.MainPage'
Some Data To Log
System Information
DateTime=5/22/2012 5:51:48 AM
Current URL=file:///D:/MyStuff/BlogEntries/2012/
Samples/SL-Log/SL-Log/Bin/Debug/SL_LogTestPage.html
OSVersion=Microsoft Windows NT 6.1.7601 Service Pack 1
OSName=Windows 7
CurrentAssemblyName=PDSA.Silverlight, Version=5.0.0.0,
Culture=neutral, PublicKeyToken=null
MainAssemblyName=SL-Log, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null
AppDomainName=Silverlight AppDomain
UserLanguage=en-US
CompanyName=PDSA, Inc.
ProductName=Silverlight Logging
Description=Silverlight Logging
Title=Silverlight Logging
Copyright=Copyright © 2012 by PDSA, Inc.
ApplicationVersion=1.0.0.0
Stack Trace={LogInfoSample,btnLogInfo_Click}
-----------------------------------------------------------
The system information added to the end of the log comes from another class called PDSASystemInfo. Note that I blogged about this class earlier, so check out my previous blog entry at http://weblogs.asp.net/psheriff/archive/2012/05/20/retrieve-system-information-in-silverlight.aspx for information on this class and how that data was gathered.
Passing Extra Data to Log
You have an additional overload on the Log() method that takes a generic Dictionary<string, string> object you load with key/value pairs of data. Call this version of Log() like the following:
Dictionary<string, string> extra =
new Dictionary<string, string>();
extra.Add("CustomerId", "1");
extra.Add("StateCode", "CA");
PDSALoggingManager.Instance.Log("Some data to log", extra);
Passing a key/value pair passed into the Log() method you can add any amount of extra data you want to your log very simply. When this log entry is written, you end up with an entry that looks like the following:
'Informational' log entry written on 5/22/2012 5:53:47 AM, from class: 'SL_Log.MainPage'
Some data to log
Extra Values Passed In From Application
CustomerId=1
StateCode=CA
The Log Method
Let’s now take a look at the Log() method in the PDSALoggingManager class. When you pass in a single string value, the following version of the Log() method is called.
public void Log(string value)
{
this.Log(value, "Informational", null);
}
This method calls another overload of the Log() method to which you can pass in the “type” of log entry you are writing and a null in place of the Dictionary object. By default, the Log() method uses “Informational” as the log type. You can pass in whatever value you wish for this type.
The 2nd overload of this method is the one that you pass in the Dictionary object to. This method calls the same Log() method as the previous one, but this time passes the extra values from the Dictionary object.
public void Log(string value,
Dictionary<string, string> extraValues)
{
this.Log(value, "Informational", extraValues);
}
Now, let’s look at the version of the Log() method that does the actual work of logging the string data, the type and optionally the extra dictionary values you might pass in. The first thing Log() does is to call a method named GetCallingClassName(). This method will be shown later, but it is used to retrieve the name of the method in your Silverlight application that called the Log() method. Next, it calls a method named Format() that will format the log data into what you saw earlier in this blog. Finally, the entry is written to isolated storage.
public void Log(string value, string logType,
Dictionary<string, string> extraValues)
{
IsolatedStorageFile file = null;
string message = string.Empty;
try
{
// Get the name of the calling method
CallingClassName = GetCallingClassName();
// Format the log entry
message = Format(value, logType, extraValues);
file = IsolatedStorageFile.GetUserStoreForSite();
using (IsolatedStorageFileStream fs = new
IsolatedStorageFileStream(LogFileName,
FileMode.Append, file))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(message);
}
}
}
catch (Exception ex)
{
TheLog.Append("Exception Occurred in Log() method" +
Delimiter + ex.ToString());
}
finally
{
if (file != null)
file.Dispose();
}
}
Format Method
The Format() method is used to put the log entry into a readable format. A StringBuilder object, named sb, is what is used to format the data and concatenate all of the data together. Once all of the data is gathered up, this local StringBuilder object is appended to the ‘TheLog’ property. This property is kept in memory so you can retrieve the log during the running of your application without having to read the data from the isolated storage file. This property is also used to log any exception that occurs within the PDSALoggingManager class itself.
Notice there is another property called Delimiter that is used to separate each line of the log. This property is initialized in the constructor of the PDSAloggingManager class to Environment.NewLine.
protected virtual string Format(string value, string logType,
Dictionary<string, string> extraValues)
{
StringBuilder sb = new StringBuilder(512);
if (string.IsNullOrEmpty(Delimiter))
Delimiter = Environment.NewLine;
if (LogSystemInfo)
sb.Append(new string('-', 200) + Delimiter);
sb.Append("'" + logType + "'");
sb.Append(" log entry written on "
+ DateTime.Now.ToString());
if (!string.IsNullOrEmpty(CallingClassName))
sb.Append(", from class: '" + CallingClassName + "'");
sb.Append(Delimiter);
sb.Append(" " + value + Delimiter);
// Add on Extra Values
sb.Append(FormatKeyValuePairs(extraValues));
if (LogSystemInfo)
{
PDSASystemInfo si = new PDSASystemInfo();
sb.Append(si.GetAllSystemInfo(Delimiter));
sb.Append(Delimiter);
sb.Append(new string('-', 200) + Delimiter);
}
// Append to the main log property
TheLog.Append(sb.ToString());
return sb.ToString();
}
A property called LogSystemInfo is initialized to true in the constructor of this class. If set to true, then system information is gathered from the PDSASystemInfo class and appended to the log. If there are any extra values passed in the Dictionary object, those values are also appended to the log. These are formatted in the FormatKeyValuePairs() method. You can look up this method by downloading the code for this blog entry. See the end of this blog for instructions on how to get this article and the associated code sample.
GetCallingClassName Method
The PDSALoggingManager and PDSASystemInfo classes are located in a DLL named PDSA.Silverlight and is referenced from your Silverlight application. It is, of course, a best practice to put generic classes like this into a separate DLL. However, there is another benefit we derived from doing this. We want to retrieve the name of the method that called the Log() method so we can record where Log() was called from. The StackFrame object is used to retrieve each method in the stack trace. As we grab each method we can check the method to see if it is in the current assembly and if it is, we will ignore it. However, once we find a method that is in a different assembly, we can assume that this is the assembly and method that called the Log() method.
public string GetCallingClassName()
{
int loop = 0;
string ret = string.Empty;
string currentName =
Assembly.GetExecutingAssembly().FullName;
try
{
StackFrame sf = new StackFrame(loop);
while (sf.GetMethod() != null)
{
// Don't get any methods contained in this assembly.
if (sf.GetMethod().DeclaringType.Assembly.FullName !=
currentName)
{
ret = sf.GetMethod().DeclaringType.FullName;
break;
}
loop++;
sf = new System.Diagnostics.StackFrame(loop);
}
}
catch
{
// Do nothing
}
return ret;
}
Logging Exceptions
In addition to logging string data, you might also wish to log exception data. To facilitate this I added a LogException() method. This method will accept an Exception object. By default, this method simply takes the result of the ToString() method on the exception object and passes it to the Log() method. However, you could modify this to retrieve any additional information from the Exception object that you want and pass that to the Log() method.
try
{
decimal ret = 10;
ret = ret / 0;
}
catch (Exception ex)
{
PDSALoggingManager.Instance.LogException(ex);
}
A second overload of the LogException() method allows you to pass in additional information as a generic Dictionary object just like the original Log() method allows.
decimal ret = 10;
try
{
ret = ret / 0;
}
catch (Exception ex)
{
Dictionary<string, string> extra =
new Dictionary<string, string>();
extra.Add("StackTraceFromException", ex.StackTrace);
extra.Add("ret variable", ret.ToString());
PDSALoggingManager.Instance.LogException(ex, extra);
}
Here is the output from logging the exception:
'Exception' log entry written on 5/22/2012 4:10:15 PM,
from class: 'SL_Log.MainPage'
System.DivideByZeroException: Attempted to divide by zero.
at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
at System.Decimal.op_Division(Decimal d1, Decimal d2)
at SL_Log.MainPage.btnLogException_Click(Object sender,
RoutedEventArgs e)
Getting the Log from your User
Once you have logged the data, you might want to get that information. The obvious choice would be to pass the complete log information to a WCF service. I am not presenting that choice in this sample due to space. However, one thing you need to consider is what if your user cannot access the WCF service for some reason? In this case you should have a backup plan. Two options you might consider are one, give the user a button they can click on that will copy the log to the clipboard, and two, another button that they can use to save the log data to a file on their computer. First, here is the code you would write to copy the log to the clipboard.
try
{
Clipboard.SetText(PDSALoggingManager.Instance.ReadLog());
}
catch
{
MessageBox.Show("Can't copy to the Clipboard.");
}
The ReadLog() method returns the data from the TheLog property, or if that is empty, will read the data from the isolated storage file. Next, you could read the log data and pass that to a method that will prompt the user to enter the name and location on their hard drive where to store the log data.
private void btnSaveToFile_Click(object sender,
RoutedEventArgs e)
{
SaveToFile(PDSALoggingManager.Instance.ReadLog());
}
private void SaveToFile(string contents)
{
SaveFileDialog sfd = null;
try
{
sfd = new SaveFileDialog();
sfd.DefaultExt = "txt";
sfd.Filter = "Log Files (*.log)|*.log|
All Files (*.*)|*.*";
sfd.FilterIndex = 1;
bool? result = sfd.ShowDialog();
if (result.HasValue && result == true)
{
using (StreamWriter sw =
new StreamWriter(sfd.OpenFile()))
{
sw.Write(contents);
sw.Close();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
After storing this file on their hard drive, they could then attach that file to an email and send the log data to you.
Summary
Implementing a logging system in your Silverlight application is a great way to keep track of what you user does in your application. It is also extremely useful for tracking down errors. You must still be able to get the log file from the user, but that is fairly easy using either the clipboard or saving to a file and having your user email you the log. Hopefully this simple little class will give you a head-start on creating your own logging system.
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 “Client-Side Logging in Silverlight” from the drop down list.
In a Silverlight application we are building for a client, they wanted an About screen that would display system information such as the current URL, the operating system name and version, the product name and various other information. In the same application, we built a logging system to gather this same information and write that information to a file to help developers troubleshoot issues. We decided to create a Silverlight class that would gather the information shown in Figure 1.
.
Figure 1: A Silverlight system information class to gather information about the user’s environment.
The class where all of this data comes from is named PDSASystemInfo. This class contains a set of properties that get information from the current executing assembly, the Environment class, and a few other classes that give you system information in your Silverlight application. Let’s look at each of these properties in turn.
The Constructor
First off, the constructor for this class retrieves the main assembly for your Silverlight application. This is the first assembly that runs. I assume that the first assembly is where your Silverlight user controls run from, so we need a reference to that assembly in order to retrieve copyright, title, company, and description information. You use the Application.Current.GetType() method to get the Assembly object. From this object you will be able to retrieve the Assembly information you place into your Silverlight application from Visual Studio. A reference to this assembly is placed into a private variable called _CurrentAssm.
public class PDSASystemInfo
{
private Assembly _CurrentAssm = null;
public PDSASystemInfo()
{
_CurrentAssm = Application.Current.GetType().Assembly;
}
…
…
}
Assembly Information Properties
In Visual Studio if you go into the Project Properties of your solution you can click on the Assembly Information button and enter information about your assembly as shown in Figure 2.

Figure 2: The Visual Studio Assembly Information screen is where you enter information about your application.
To retrieve this information at runtime, you use the Application’s main assembly object to call the GetCustomAttributes() method. You pass to this method a type that represents the piece of information you wish to retrieve such as the Company, Product, etc.
public string Company
{
get
{
Type at = typeof(AssemblyCompanyAttribute);
object[] c = _CurrentAssm.GetCustomAttributes(at, false);
AssemblyCompanyAttribute att =
((AssemblyCompanyAttribute)(c[0]));
return att.Company;
}
}
public string Description
{
get
{
Type at = typeof(AssemblyDescriptionAttribute);
object[] c = _CurrentAssm.GetCustomAttributes(at, false);
AssemblyDescriptionAttribute att =
((AssemblyDescriptionAttribute)(c[0]));
return att.Description;
}
}
public string Product
{
get
{
Type at = typeof(AssemblyProductAttribute);
object[] c = _CurrentAssm.GetCustomAttributes(at, false);
AssemblyProductAttribute att =
((AssemblyProductAttribute)(c[0]));
return att.Product;
}
}
public string Title
{
get
{
Type at = typeof(AssemblyTitleAttribute);
object[] c = _CurrentAssm.GetCustomAttributes(at, false);
AssemblyTitleAttribute att =
((AssemblyTitleAttribute)(c[0]));
return att.Title;
}
}
public string Copyright
{
get
{
Type at = typeof(AssemblyCopyrightAttribute);
object[] c = _CurrentAssm.GetCustomAttributes(at, false);
AssemblyCopyrightAttribute att =
((AssemblyCopyrightAttribute)(c[0]));
return att.Copyright;
}
}
public string Version
{
get
{
AssemblyName an = new AssemblyName(_CurrentAssm.FullName);
return an.Version.ToString();
}
}
Operating System Name and Version
To retrieve the operating system name and version, it is the same as in any .NET application. You will use the Environment class and the associated properties on that class.
public string OSVersion
{
get
{
return Environment.OSVersion.ToString();
}
}
public string OSName
{
get
{
string ret = string.Empty;
switch (Environment.OSVersion.Version.Major)
{
case 7:
ret = "Windows 8";
break;
case 6:
if (Environment.OSVersion.Version.Minor == 0)
ret = "Windows Vista";
else if (Environment.OSVersion.Version.Minor == 1)
ret = "Windows 7";
break;
case 5:
if (Environment.OSVersion.Version.Minor == 0)
ret = "Windows 2000";
else if (Environment.OSVersion.Version.Minor == 1)
ret = "Windows XP";
break;
case 4:
ret = "Windows NT";
break;
default:
ret = "Unknown Version";
break;
}
return ret;
}
}
The Current URL
A useful property in your application is finding out what the current URL is that is running your Silverlight user control. This is very easy to get at using the System.Windows.Browser.HtmlPage class. You access the Document.DocumentUri property to retrieve the current URL that is running on the user’s machine.
public string CurrentUrl
{
get
{
try
{
return HtmlPage.Document.DocumentUri.ToString();
}
catch
{
return string.Format(_ERROR_MSG, "Current URL");
}
}
}
Get the Stack Trace
When you need to debug an application, having access to the stack trace is very helpful. Getting the stack trace in a Silverlight client-side user control is accomplished using the StackFrame class. If you get an exception in your application, the Exception object you get has a StackTrace property that will return your stack trace that got you to your exception. However, if you are not in an exception and just want to get the stack trace information, you write code like that shown in the GetStackTrace() method below.
public string GetStackTrace()
{
StringBuilder sb = new StringBuilder(512);
int loop = 0;
string comma = string.Empty;
string nameToMatch = MainAssemblyName;
try
{
StackFrame sf = new StackFrame(loop);
if (sf.GetMethod() != null)
sb.Append("{");
while (sf.GetMethod() != null)
{
// Get methods contained in this assembly only.
if (sf.GetMethod().DeclaringType.Assembly.
FullName.Equals(nameToMatch))
{
sb.Append(comma + sf.GetMethod().Name);
comma = ",";
}
loop++;
sf = new System.Diagnostics.StackFrame(loop);
}
if (sb.Length > 0)
sb.Append("}");
}
catch
{
sb.AppendFormat(_ERROR_MSG, "Stack Trace");
}
return sb.ToString();
}
If you simply loop through all methods contained in the StackFrame you will get a lot of methods that are part of Silverlight and not your application. In this method you simply check to see if the methods are contained only in the main assembly name. This will probably work fine for a simple Silverlight application, but you may need to expand on this method if you have many client-side DLLs and wish to use this method when you are within any of those other DLLs.
Summary
There are a couple of other properties in the class that you can look at when you download the source code. This class will give you a lot of information that you will find useful when logging or creating an About page for your application. There is also a method named GetAllSystemInfo() that can be used to concatenate all of these properties together into one string. This method is great for logging all of these properties into a file. I will cover a client-side logging utility for Silverlight in my next blog post.
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 “Retrieve System Information in Silverlight” from the drop down list.
After my previous blog post, I realized that using SQL strings is not a great way to do things. Sometimes we start blogging too quick and then realize our mistakes after. But, no big deal, live and learn... I am going to now rewrite this application and use some lambda expressions to solve the problems inherit with concatenating strings to SQL statements; namely escaping a single quote and SQL Injection attacks. I am going to use the same search screen shown in Figure 1.

Figure 1: A search screen where the user can select an operation and a value for the searching on multiple fields.
Read the first blog post to see the calls to the WCF service. However, I want to know show the revised code to build a dynamic WHERE clause.
C#
public List<Customer> GetCustomers(string cname, string cnameOperator, string email, string emailOperator)
{
AdventureWorksLTEntities db =
new AdventureWorksLTEntities();
var query = from cust in db.Customers select cust;
if (string.IsNullOrEmpty(cname) == false)
{
switch (cnameOperator.ToLower())
{
case "equal to":
query = query.Where(cust =>
cust.CompanyName.Equals(cname));
break;
case "starts with":
query = query.Where(cust =>
cust.CompanyName.StartsWith(cname));
break;
case "contains":
query = query.Where(cust =>
cust.CompanyName.Contains(cname));
break;
}
}
if (string.IsNullOrEmpty(email) == false)
{
switch (emailOperator.ToLower())
{
case "equal to":
query = query.Where(cust =>
cust.EmailAddress.Equals(email));
break;
case "starts with":
query = query.Where(cust =>
cust.EmailAddress.StartsWith(email));
break;
case "contains":
query = query.Where(cust =>
cust.EmailAddress.Contains(email));
break;
}
}
query = query.OrderBy(cust => cust.CompanyName);
return query.ToList();
}
VB
Public Function GetCustomers(cname As String, _
cnameOperator As String, email As String, _
emailOperator As String) As List(Of Customer) _
Implements ICustomerSearch.GetCustomers
Dim db As New AdventureWorksLTEntities
Dim query = From cust In db.Customers Select cust
If String.IsNullOrEmpty(cname) = False Then
Select Case cnameOperator.ToLower()
Case "equal to"
query = query.Where(Function(cust) _
cust.CompanyName.Equals(cname))
Case "starts with"
query = query.Where(Function(cust) _
cust.CompanyName.StartsWith(cname))
Case "contains"
query = query.Where(Function(cust) _
cust.CompanyName.Contains(cname))
End Select
End If
If String.IsNullOrEmpty(email) = False Then
Select Case emailOperator.ToLower()
Case "equal to"
query = query.Where(Function(cust) _
cust.EmailAddress.Equals(email))
Case "starts with"
query = query.Where(Function(cust) _
cust.EmailAddress.StartsWith(email))
Case "contains"
query = query.Where(Function(cust) _
cust.EmailAddress.Contains(email))
End Select
End If
query = query.OrderBy(Function(cust) cust.CompanyName)
Return query.ToList()
End Function
As you can see in the above code you can simply use the Where() function on your IQueryable query to add WHERE clauses that get submitted to the back end database. It is always a good idea to turn on your SQL Profiler and check out the SQL that gets submitted to the back end database.
Summary
The advantage to this approach is now you are relying on the Entity Framework to handle escaping single quotes and avoiding the SQL injection attacks that you would otherwise have to handle. I hope this shows you something useful that you can use in your applications.
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 “Dynamic Search with LINQ, the Entity Framework and Silverlight – Part 2” from the drop down list.
I have been helping a client with a Silverlight application and one of his requirements was to allow his users to be able to query 1 to 5 fields and use different operators for each field. For example, they can choose to search for a Company Name that “Starts With” a certain value and also search for an Email field that “Contains” another value. You can see an example of this search screen in Figure 1.

Figure 1: A search screen where the user can select an operation and a value for the searching on multiple fields.
To make this search screen work you must pass two values for each search parameter you want to use. You need the operator to use and the value to search for. In this example I pass 4 parameters to a WCF Service. You might modify this method to pass in a collection of objects that contain the different values for each search.
In the code listed below you can see the Click event procedure behind the Search button on the screen. I did not use a View Model for this simple example since the point of this blog has to do with a dynamic LINQ search on the back end, but feel free to add your own View Model class.
C#
private CustomerSearchClient _Client = null;
private void btnGetCustomers_Click(object sender,
RoutedEventArgs e)
{
_Client = new CustomerSearchClient();
_Client.GetCustomersCompleted += new
EventHandler<GetCustomersCompletedEventArgs>
(_Client_GetCustomersCompleted);
_Client.GetCustomersAsync(txtCompanyName.Text,
((ComboBoxItem)cboCompanyOperator.SelectedItem)
.Content.ToString(),
txtEmail.Text,
((ComboBoxItem)cboEmailOperator.SelectedItem)
.Content.ToString());
}
void _Client_GetCustomersCompleted(object sender,
GetCustomersCompletedEventArgs e)
{
lstCustomers.DataContext = e.Result;
_Client.CloseAsync();
}
Visual Basic
Private WithEvents _Client As CustomerSearchClient
Private Sub btnGetCustomers_Click(sender As System.Object, _
e As System.Windows.RoutedEventArgs) _
Handles btnGetCustomers.Click
_Client = New CustomerSearchClient()
_Client.GetCustomersAsync(txtCompanyName.Text, _
DirectCast(cboCompanyOperator.SelectedItem, _
ComboBoxItem).Content.ToString(), _
txtEmail.Text, _
DirectCast(cboEmailOperator.SelectedItem, _
ComboBoxItem).Content.ToString())
End Sub
Private Sub _Client_GetCustomersCompleted(sender As Object, _
e As _
CustomerServiceReference.GetCustomersCompletedEventArgs) _
Handles _Client.GetCustomersCompleted
lstCustomers.DataContext = e.Result
_Client.CloseAsync()
End Sub
In the code above you create an instance of a WCF Service reference that calls the method named GetCustomers(). This method takes the company name value, the operator for how to search for the company name, the email value and the operator for how to search for the email. These four values are simply taken from the appropriate controls on this Silverlight user control.
Building the Dynamic LINQ Search
To dynamically build a LINQ search from the 4 values passed into the WCF Service you add an ADO.NET Entity Data Model to query against. In this sample I used the AdventureWorksLT database and added the Customer table to my Entity Data Model. I named this Entity Data Model AdvWorks. I then built a WCF Service named CustomerSearch and added the GetCustomers() method with the 4 parameters. You will need to add a using/Imports to the System.Data.Objects namespace in order to use the ObjectQuery class.
The ObjectQuery class allows you to use your Entity Framework context classes within a string to express your query. The complete code for the GetCustomers() method is listed further below, but let me give you just a simple little sample of how this works. Take the example below:
C#
AdventureWorksLTEntities db =
new AdventureWorksLTEntities();
ObjectQuery<Customer> query = null;
sql = "SELECT VALUE cust FROM
AdventureWorksLTEntities.Customers As cust ";
sql += " ORDER BY cust.CompanyName";
query = db.CreateQuery<Customer>(sql);
Visual Basic
Dim db As New AdventureWorksLTEntities
Dim query As ObjectQuery(Of Customer)
sql = "SELECT VALUE cust FROM
AdventureWorksLTEntities.Customers As cust "
sql &= " ORDER BY cust.CompanyName"
query = db.CreateQuery(Of Customer)(sql)
In the above code you use the AdventureWorksLTEntities class which is created by the Entity Framework when you add a Data Model to the AdventureWorksLT database. A Customers collection object is created within this class to hold a collection of Customer objects. You must use these names within your string object. You also need to use the keyword “VALUE” followed by an alias name, as I used “cust” in the above example. If you will need to reference any specific column names within a WHERE clause or an ORDER BY clause you will reference those column names using this alias.
Once you have created the SQL string you use your AdventureWorksLTEntities object, the variable db in the code above, and call the CreateQuery() method passing in the SQL string you created. This will build a collection of Customer objects by executing this query against the database. You can view the query that is expressed by turning on the SQL Profiler and tracing any T-SQL calls to the database.
The complete GetCustomers() method builds the SQL statement dynamically by checking if the company name parameter or the email parameter is filled in. If they are filled in then an appropriate WHERE clause is added to the SELECT statement. You can now look at the complete GetCustomers() method below:
C#
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
public class CustomerSearch : ICustomerSearch
{
public List<Customer> GetCustomers(string cname,
string cnameOperator,
string email,
string emailOperator)
{
AdventureWorksLTEntities db =
new AdventureWorksLTEntities();
string join = " WHERE ";
string sql = null;
ObjectQuery<Customer> query = null;
sql = "SELECT VALUE cust FROM
AdventureWorksLTEntities.Customers As cust ";
if (string.IsNullOrEmpty(cname) == false)
{
sql += join + " cust.CompanyName " +
BuildWhere(cnameOperator, cname);
join = " AND ";
}
if (string.IsNullOrEmpty(email) == false)
{
sql += join + " cust.EmailAddress " +
BuildWhere(emailOperator, email);
join = " AND ";
}
sql += " ORDER BY cust.CompanyName";
query = db.CreateQuery<Customer>(sql);
return query.ToList();
}
public string BuildWhere(string operatorValue, string value)
{
string where = string.Empty;
switch (operatorValue.ToLower())
{
case "equal to":
where = " = '" + value + "'";
break;
case "starts with":
where = " LIKE '" + value + "%'";
break;
case "contains":
where = " LIKE '%" + value + "%'";
break;
}
return where;
}
}
Visual Basic
Imports System.Data.Objects
Public Class CustomerSearch
Implements ICustomerSearch
Public Function GetCustomers(cname As String, _
cnameOperator As String, _
email As String, _
emailOperator As String) As List(Of Customer) _
Implements ICustomerSearch.GetCustomers
Dim db As New AdventureWorksLTEntities
Dim join As String = " WHERE "
Dim sql As String
Dim query As ObjectQuery(Of Customer)
sql = "SELECT VALUE cust FROM
AdventureWorksLTEntities.Customers As cust "
If String.IsNullOrEmpty(cname) = False Then
sql &= join & " cust.CompanyName " & _
BuildWhere(cnameOperator, cname)
join = " AND "
End If
If String.IsNullOrEmpty(email) = False Then
sql &= join & " cust.EmailAddress " & _
BuildWhere(emailOperator, email)
join = " AND "
End If
sql &= " ORDER BY cust.CompanyName"
query = db.CreateQuery(Of Customer)(sql)
Return query.ToList()
End Function
Public Function BuildWhere(operatorValue As String, _
value As String) As String
Dim where As String = String.Empty
Select Case operatorValue.ToLower()
Case "equal to"
where = " = '" & value & "'"
Case "starts with"
where = " LIKE '" & value & "%'"
Case "contains"
where = " LIKE '%" & value & "%'"
End Select
Return where
End Function
End Class
To build the WHERE clause you notice that I pass in the operator such as “Equal To”, “Starts With”, or “Contains” to a method called BuildWhere(). This method builds the expression for the WHERE clause. If the value of the operator is “Equal To”, then the equal sign (=) followed by the exact value typed into the text box wrapped into single quotes is returned. If the operator passed in is “Starts With”, then a LIKE operator followed by a single quote, the value typed into the text box, and a percent sign (%) and a closing single quote is returned. It the operator is “Contains”, then a percent sign is wrapped on both sides of the value typed into the text box with a LIKE operator.
All of this will build a SELECT statement that might look like one the following:
SELECT VALUE cust
FROM AdventureWorksLTEntities.Customers As cust
WHERE cust.CompanyName LIKE 'a%'
ORDER BY cust.CompanyName
or
SELECT VALUE cust
FROM AdventureWorksLTEntities.Customers As cust
WHERE cust.CompanyName LIKE '%a%'
AND cust.EmailAddress LIKE 'o%'
ORDER BY cust.CompanyName
or
SELECT VALUE cust
FROM AdventureWorksLTEntities.Customers As cust
WHERE cust.EmailAddress LIKE 'a%'
ORDER BY cust.CompanyName
Summary
While there are other approaches to this problem, I really like this one, because it helps me control the SQL that the Entity Framework submits to the back end database. When using LINQ, sometimes the SQL that the Entity Framework can be pretty convoluted. Using the CreateQuery() method I can sometimes craft the SQL a little closer to what will be eventually submitted to the back end and this can really improve the performance in a lot of cases. I hope you will find this little trick helpful.
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 “Dynamic Search with LINQ, the Entity Framework and Silverlight” from the drop down list.
There are many examples of the Silverlight Tree View that you will find on the web, however, most of them only show you how to go to two levels. What if you have more than two levels? This is where understanding exactly how the Hierarchical Data Templates works is vital. In this blog post, I am going to break down how these templates work so you can really understand what is going on underneath the hood. To start, let’s look at the typical two-level Silverlight Tree View that has been hard coded with the values shown below:
<sdk:TreeView>
<sdk:TreeViewItem Header="Managers">
<TextBlock Text="Michael" />
<TextBlock Text="Paul" />
</sdk:TreeViewItem>
<sdk:TreeViewItem Header="Supervisors">
<TextBlock Text="John" />
<TextBlock Text="Tim" />
<TextBlock Text="David" />
</sdk:TreeViewItem>
</sdk:TreeView>
Figure 1 shows you how this tree view looks when you run the Silverlight application.

Figure 1: A hard-coded, two level Tree View.
Next, let’s create three classes to mimic the hard-coded Tree View shown above. First, you need an Employee class and an EmployeeType class. The Employee class simply has one property called Name. The constructor is created to accept a “name” argument that you can use to set the Name property when you create an Employee object.
public class Employee
{
public Employee(string name)
{
Name = name;
}
public string Name { get; set; }
}
Finally you create an EmployeeType class. This class has one property called EmpType and contains a generic List<> collection of Employee objects. The property that holds the collection is called Employees.
public class EmployeeType
{
public EmployeeType(string empType)
{
EmpType = empType;
Employees = new List<Employee>();
}
public string EmpType { get; set; }
public List<Employee> Employees { get; set; }
}
Finally we have a collection class called EmployeeTypes created using the generic List<> class. It is in the constructor for this class where you will build the collection of EmployeeTypes and fill it with Employee objects:
public class EmployeeTypes : List<EmployeeType>
{
public EmployeeTypes()
{
EmployeeType type;
type = new EmployeeType("Manager");
type.Employees.Add(new Employee("Michael"));
type.Employees.Add(new Employee("Paul"));
this.Add(type);
type = new EmployeeType("Project Managers");
type.Employees.Add(new Employee("Tim"));
type.Employees.Add(new Employee("John"));
type.Employees.Add(new Employee("David"));
this.Add(type);
}
}
You now have a data hierarchy in memory (Figure 2) which is what the Tree View control expects to receive as its data source.

Figure 2: A hierachial data structure of Employee Types containing a collection of Employee objects.
To connect up this hierarchy of data to your Tree View you create an instance of the EmployeeTypes class in XAML as shown in line 13 of Figure 3. The key assigned to this object is “empTypes”. This key is used as the source of data to the entire Tree View by setting the ItemsSource property as shown in Figure 3, Callout #1.

Figure 3: You need to start from the bottom up when laying out your templates for a Tree View.
The ItemsSource property of the Tree View control is used as the data source in the Hierarchical Data Template with the key of employeeTypeTemplate. In this case there is only one Hierarchical Data Template, so any data you wish to display within that template comes from the collection of Employee Types. The TextBlock control in line 20 uses the EmpType property of the EmployeeType class. You specify the name of the Hierarchical Data Template to use in the ItemTemplate property of the Tree View (Callout #2).
For the second (and last) level of the Tree View control you use a normal <DataTemplate> with the name of employeeTemplate (line 14). The Hierarchical Data Template in lines 17-21 sets its ItemTemplate property to the key name of employeeTemplate (Line 19 connects to Line 14). The source of the data for the <DataTemplate> needs to be a property of the EmployeeTypes collection used in the Hierarchical Data Template. In this case that is the Employees property. In the Employees property there is a “Name” property of the Employee class that is used to display the employee name in the second level of the Tree View (Line 15).
What is important here is that your lowest level in your Tree View is expressed in a <DataTemplate> and should be listed first in your Resources section. The next level up in your Tree View should be a <HierarchicalDataTemplate> which has its ItemTemplate property set to the key name of the <DataTemplate> and the ItemsSource property set to the data you wish to display in the <DataTemplate>. The Tree View control should have its ItemsSource property set to the data you wish to display in the <HierarchicalDataTemplate> and its ItemTemplate property set to the key name of the <HierarchicalDataTemplate> object. It is in this way that you get the Tree View to display all levels of your hierarchical data structure.
Three Levels in a Tree View
Now let’s expand upon this concept and use three levels in our Tree View (Figure 4). This Tree View shows that you now have EmployeeTypes at the top of the tree, followed by a small set of employees that themselves manage employees. This means that the EmployeeType class has a collection of Employee objects. Each Employee class has a collection of Employee objects as well.

Figure 4: When using 3 levels in your TreeView you will have 2 Hierarchical Data Templates and 1 Data Template.
The EmployeeType class has not changed at all from our previous example. However, the Employee class now has one additional property as shown below:
public class Employee
{
public Employee(string name)
{
Name = name;
ManagedEmployees = new List<Employee>();
}
public string Name { get; set; }
public List<Employee> ManagedEmployees { get; set; }
}
The next thing that changes in our code is the EmployeeTypes class. The constructor now needs additional code to create a list of managed employees. Below is the new code.
public class EmployeeTypes : List<EmployeeType>
{
public EmployeeTypes()
{
EmployeeType type;
Employee emp;
Employee managed;
type = new EmployeeType("Manager");
emp = new Employee("Michael");
managed = new Employee("John");
emp.ManagedEmployees.Add(managed);
managed = new Employee("Tim");
emp.ManagedEmployees.Add(managed);
type.Employees.Add(emp);
emp = new Employee("Paul");
managed = new Employee("Michael");
emp.ManagedEmployees.Add(managed);
managed = new Employee("Sara");
emp.ManagedEmployees.Add(managed);
type.Employees.Add(emp);
this.Add(type);
type = new EmployeeType("Project Managers");
type.Employees.Add(new Employee("Tim"));
type.Employees.Add(new Employee("John"));
type.Employees.Add(new Employee("David"));
this.Add(type);
}
}
Now that you have all of the data built in your classes, you are now ready to hook up this three-level structure to your Tree View. Figure 5 shows the complete XAML needed to hook up your three-level Tree View. You can see in the XAML that there are now two Hierarchical Data Templates and one Data Template. Again you list the Data Template first since that is the lowest level in your Tree View. The next Hierarchical Data Template listed is the next level up from the lowest level, and finally you have a Hierarchical Data Template for the first level in your tree. You need to work your way from the bottom up when creating your Tree View hierarchy. XAML is processed from the top down, so if you attempt to reference a XAML key name that is below where you are referencing it from, you will get a runtime error.

Figure 5: For three levels in a Tree View you will need two Hierarchical Data Templates and one Data Template.
Each Hierarchical Data Template uses the previous template as its ItemTemplate. The ItemsSource of each Hierarchical Data Template is used to feed the data to the previous template. This is probably the most confusing part about working with the Tree View control. You are expecting the content of the current Hierarchical Data Template to use the properties set in the ItemsSource property of that template. But you need to look to the template lower down in the XAML to see the source of the data as shown in Figure 6.

Figure 6: The properties you use within the Content of a template come from the ItemsSource of the next template in the resources section.
Summary
Understanding how to put together your hierarchy in a Tree View is simple once you understand that you need to work from the bottom up. Start with the bottom node in your Tree View and determine what that will look like and where the data will come from. You then build the next Hierarchical Data Template to feed the data to the previous template you created. You keep doing this for each level in your Tree View until you get to the last level. The data for that last Hierarchical Data Template comes from the ItemsSource in the Tree View itself.
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 “Silverlight TreeView with Multiple Levels” from the drop down list.
More Posts
« Previous page -
Next page »