As WinForms and WebForms developers, we are used to building user interfaces (UIs) that look like this:
Notice the rectangular header area and the rectangular link area and the rectangular data entry area.
With Silverlight it is (relatively) easy to give the UI less of a boxy look:
So the above UI may not be amazing, but it should give you some ideas about the kinds of things you can do to make your Silverlight applications look a little less square.
To make it easier to try this yourself, all of the source code for this User Control is included at the bottom of this post.
NOTE: This example uses a medium gray and white for the two background colors and a shade of red for the labels. You can change this to any color scheme you would like for your application by adjusting the color resources defined at the top of the code.
Designing the Layout
Even though the goal is to have a non-boxy style user interface, this effect is achieved using a set of grids. The grids are laid out as follows:
The user control is comprised of one primary grid, called LayoutRoot in the sample code. It has two rows as shown with two red rectangles above. The top row contains the header information and the bottom row contains the content information.
The top row of the LayoutRoot grid contains another grid, called HeaderGrid in the sample code. It contains two columns as shown with two blue rectangles above. The first column contains the logo and the second column contains the application title.
The background of the HeaderGrid is set to the background color defined for the application. In this example, the color is a medium gray.
NOTE: You cannot easily use a StackPanel here instead of a Grid because the background color needs to stretch across the entire width of the control. With a StackPanel, the background only covers the controls within the StackPanel. If you use a StackPanel, ensure your controls are resized to the full width of the user control.
The bottom row of the LayoutRoot grid contains another grid, called ContentGrid in the sample code. It contains three columns as shown with the two green rectangles above along with the red vertical line between them.
The background of the ContentGrid is where the magic takes place. It contains the radial gradient which displays the arc.
[For an overview of Silverlight gradients, see this prior post.]
NOTE: If you define a background for any of the controls within the ContentGrid, the ContentGrid background will not appear properly.
Defining the Gradient
The gradient used to achieve the arc depicted in the design is as follows:
<Grid.Background>
<RadialGradientBrush>
<RadialGradientBrush.RelativeTransform >
<TransformGroup>
<ScaleTransform
CenterX="0.5"
ScaleY="2" ScaleX="2.5"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="White" Offset="0.95"/>
<GradientStop
Color="{StaticResource BackgroundColor}"
Offset=".98"/>
</RadialGradientBrush>
</Grid.Background>
To better understand this XAML, let's take it step by step. The following sets of screen shots were taken using Expression Blend 3.
Add a simple radial gradient to the ContentGrid background with code like this:
<RadialGradientBrush>
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="{StaticResource BackgroundColor}"
Offset="1"/>
</RadialGradientBrush>
The Silverlight control appears like this:
Notice that the center point of the bottom grid is white and radiates out to a medium gray. While this may also be an interesting effect, it is not what is needed for this example.
For this example, the majority of the background should be white. So the first change is to the gradient Offset values:
<RadialGradientBrush>
<GradientStop Color="White" Offset=".95"/>
<GradientStop Color="{StaticResource BackgroundColor}"
Offset=".98"/>
</RadialGradientBrush> The result is as follows:
By setting the white Offset to .95, the white center point does not begin the gradient effect until 95% of the way through the gradient radius. This leaves the majority of the background white.
Setting the Offset values closer to each other, the actual gradient effect is very small as shown in the red circled area above.
For a wider gradient with more space between the white and the gray, adjust the Offset values until you get the desired result. For example, with Offsets of .70 and .98, the gradient between the white and the gray is much wider:
Once you get the desired gradient effect, you can resize the ellipse. In this example, only the top of the gradient ellipse is desired. To resize the gradient to get the desired effect, use the gradient RelativeTransform property:
<RadialGradientBrush.RelativeTransform >
<TransformGroup>
<ScaleTransform CenterX="0.5" ScaleY="2" ScaleX="2.5"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
The ScaleY defines the height of the ellipse. By default, the ellipse is one unit tall. If you want only the top half of the ellipse, you can set the ScaleY=2. This doubles the size so you only get the top half of the ellipse. The result is as follows:
Adjust the ScaleX property to change the width of the ellipse and flatten the arc. Setting ScaleX to 2.5 results in the following:
That flattened the arc, but now the ellipse is no longer centered. To fix that, adjust the CenterX property. Setting CenterX to .5 results in desired layout:
Now that it is centered, adjust the ScaleX and ScaleY values until you get the desired arc. Try out the other transformation features and see what looks you can achieve.
Enjoy!
Full Source Code
<UserControl x:Class="SLVB.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<Color x:Key="BackgroundColor">#959595</Color>
<Color x:Key="ForegroundColor">#E5E5E5</Color>
<Color x:Key="LabelColor">#95002C</Color>
<SolidColorBrush x:Key="BackgroundBrush"
Color="{StaticResource BackgroundColor}"/>
<SolidColorBrush x:Key="ForegroundBrush"
Color="{StaticResource ForegroundColor}"/>
<SolidColorBrush x:Key="LabelBrush"
Color="{StaticResource LabelColor}"/>
<Style x:Key="HeaderStyle" TargetType="dataInput:Label">
<Setter Property="FontFamily" Value="Arial" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground"
Value="{StaticResource LabelBrush}" />
</Style>
<Style x:Key="LabelStyle" TargetType="dataInput:Label">
<Setter Property="Foreground"
Value="{StaticResource LabelBrush}" />
</Style>
<Style x:Key="LinkStyle" TargetType="HyperlinkButton">
<Setter Property="Foreground"
Value="{StaticResource LabelBrush}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="8,4,8,4"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- Header -->
<RowDefinition Height="auto"/>
<!-- Content -->
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header -->
<Grid x:Name="HeaderGrid" Grid.Row="0"
Background="{StaticResource BackgroundBrush}">
<Grid.ColumnDefinitions>
<!-- Icon -->
<ColumnDefinition Width="auto"></ColumnDefinition>
<!-- Text -->
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="Images/Logo.png"
Grid.Row="0" Grid.Column="0"
Margin="5"
HorizontalAlignment="Left"
Width="100"/>
<TextBlock x:Name="ApplicationTextTextBlock"
Grid.Row="0" Grid.Column="1"
Foreground="{StaticResource ForegroundBrush}"
FontSize="32"
TextAlignment="Center"
Margin="0,0,10,5"
VerticalAlignment="Center"
Text="Customer Management">
</TextBlock>
</Grid>
<!-- Content -->
<Grid x:Name="ContentGrid" Grid.Row="1">
<Grid.Background>
<RadialGradientBrush>
<RadialGradientBrush.RelativeTransform >
<TransformGroup>
<ScaleTransform CenterX="0.5"
ScaleY="2" ScaleX="2.5"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="White" Offset="0.95"/>
<GradientStop
Color="{StaticResource BackgroundColor}"
Offset=".98"/>
</RadialGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<!-- Links -->
<ColumnDefinition Width="auto"></ColumnDefinition>
<!-- Divider -->
<ColumnDefinition Width="auto"></ColumnDefinition>
<!-- Content -->
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel x:Name="LinksStackPanel" Grid.Column="0">
<Rectangle x:Name="Divider0"
Height="40"/>
<HyperlinkButton x:Name="CustomerLink"
Content="Customer Info"
Style="{StaticResource LinkStyle}"/>
<HyperlinkButton x:Name="AddressLink"
Content="Addresses"
Style="{StaticResource LinkStyle}"/>
<HyperlinkButton x:Name="EmailAddressLink"
Content="Email Addresses"
Style="{StaticResource LinkStyle}"/>
</StackPanel>
<Line Grid.Column="1" Fill="White" Stretch="Fill"
Stroke="{StaticResource LabelBrush}"
StrokeThickness="2.5"
X1="0" Y1="0" X2="0" Y2="1"/>
<Canvas Grid.Column="2">
<dataInput:Label Canvas.Left="17" Canvas.Top="19"
Content="Address"
Style="{StaticResource HeaderStyle}" />
<Path Fill="White" Stretch="Fill"
Stroke="{StaticResource LabelBrush}"
Height="2.5" Width="370"
Canvas.Left="17" Canvas.Top="46"
Data="M17,46 L611.5,46"/>
<dataInput:Label Canvas.Left="33" Canvas.Top="57"
Content="Street:"
Style="{StaticResource LabelStyle}"/>
<TextBox x:Name="Street1TextBox"
Canvas.Left="102" Canvas.Top="53" Text=""
TextWrapping="NoWrap" Width="250"/>
<TextBox x:Name="Street2TextBox"
Canvas.Left="102" Canvas.Top="81" Text=""
TextWrapping="NoWrap" Width="250"/>
<dataInput:Label Canvas.Left="34" Canvas.Top="125"
Content="City, State:"
Style="{StaticResource LabelStyle}"/>
<TextBox x:Name="CityTextBox"
Canvas.Left="102" Canvas.Top="121"
Text="" TextWrapping="NoWrap" Width="200"/>
<TextBox x:Name="StateTextBox"
Canvas.Left="306" Canvas.Top="121"
Text="" TextWrapping="NoWrap" Width="45"/>
<dataInput:Label Canvas.Left="34" Canvas.Top="158"
Content="Zip code:"
Style="{StaticResource LabelStyle}"/>
<dataInput:Label Canvas.Left="34" Canvas.Top="192"
Content="Country:"
Style="{StaticResource LabelStyle}"/>
<TextBox x:Name="CountryTextBox"
Canvas.Left="102" Canvas.Top="186"
Text="" TextWrapping="NoWrap" Width="250"/>
<TextBox x:Name="ZipTextBox"
Canvas.Left="102" Canvas.Top="153"
Text="" TextWrapping="NoWrap" Width="250"/>
<Button x:Name="OKButton" Width="75"
Canvas.Left="238" Canvas.Top="230"
Content="OK" />
<Button x:Name="CancelButton" Width="75"
Canvas.Left="317" Canvas.Top="230"
Content="Cancel" />
</Canvas>
</Grid>
</Grid>
</UserControl>
I just needed to add a simple image to my Silverlight application to include a company logo. Three hours later …
This post provides information on the image formats supported by Silverlight so that you don't run into the same issues and waste as much time as I did today.
[NOTE: To anyone on the Silverlight team that may read this: Why not have Visual Studio (or even Expression Blend) tell me I was making this mistake instead of letting me waste so much time!!]
First, I followed the *very* simple instructions I found everywhere:
1) Add the image to the Silverlight project.
I put mine in an Images folder in the project.
2) Set the Build Action for the image to "Resource".
3) Add the image tag to the XAML.
Easy peasy. Then why didn't it work? No image appeared. I read/tried everything I could think of but no luck. Three hours later my partner suggested I try another file… AND IT WORKED.
The problem was the image format!
- bmp: NOT SUPPORTED IN SILVERLIGHT
No warning, no error, it just does not display the image
- jpg: It works, but does not support a transparent color
- gif: It supports a transparent color, but NOT SUPPORTED IN SILVERLIGHT
- png: It works AND it supports a transparent color
As soon as I replaced my logo.bmp with a logo.png, it worked!
[For help setting up a transparent color, see this Silverlight Tip of the Day.]
So here again are the steps for adding an image to a Silverlight application:
1) Add the image to the Silverlight project.
BE SURE that the file is either a .jpg or .png format image.
NOTE: The above screen shot shows the image under my Silverlight VB project, but the same is true for a C# Silverlight project.
2) Set the Build Action for the image to "Resource".
3) Add the image tag to the XAML.
<StackPanel Background="BlanchedAlmond">
<Image Source="Images/Logo.png" Width="100"
HorizontalAlignment="Left"
Margin="2"/>
<data:DataGrid x:Name="CustomerList"
ItemsSource="{Binding Data, ElementName=CustomerSource}">
</data:DataGrid>
</StackPanel>
NOTE: Since I set up the file as a resource, I set the source property of the Image tag directly to the location of the image in the Silverlight project.
And the result:
Enjoy!
I mentioned in this prior post that RIA Services did not know how to handle properties that have an Enum data type. The problem is not really that RIA Services does not know how to handle the properties; rather it does not have any knowledge of the Enum itself.
Take this example.
In C#:
public enum CustomerTypeOption
{
Consumer,
Corporation,
Education,
Government
}
private CustomerTypeOption _CustomerType;
public CustomerTypeOption CustomerType {
get { return _CustomerType; }
set { _CustomerType = value; }
}
In VB:
Public Enum CustomerTypeOption
Consumer
Corporation
Education
Government
End Enum
Private _CustomerType As CustomerTypeOption
Public Property CustomerType() As CustomerTypeOption
Get
Return _CustomerType
End Get
Set(ByVal value As CustomerTypeOption)
_CustomerType = value
End Set
End Property
This code resides in a Customer class like the one shown in this prior post. If you create a CustomerService domain services class, Silverlight will generate a proxy class containing all of the Customer class properties. However, the generated code contains a syntax error because it does not understand the CustomerTypeOption type.
This problem can be solved by sharing the Enum with Silverlight following the technique detailed in this prior post and shown below.
Let's go through the steps in detail:
1. Add a new file to your business object component and call it Customer.shared.vb (if your component is VB.NET) or Customer.shared.cs (if you are using C#).
2. Add the partial keyword to the class name in the file as shown in the code below.
3. Cut the Enum definition from the Customer class and paste it into this partial class.
In C#:
public partial class Customer
{
public enum CustomerTypeOption
{
Consumer,
Corporation,
Education,
Government
}
}
In VB:
Partial Public Class Customer
Public Enum CustomerTypeOption
Consumer
Corporation
Education
Government
End Enum
End Class
4. Right click on the Web project linked to your Silverlight project and select Add | Existing Item.
5. In the Add Existing Item dialog, select the file and select Add As Link from the Add button drop down menu.
This links the file between your business object class library project and the Web project linked to your Silverlight project.
6. Build the solution.
Silverlight should now have a copy of the Customer.shared.vb or Customer.shared.cs file under the Generated_Code node. Since the Enum is now defined in the Silverlight project, your CustomerType property in the generated code should compile without errors.
Use this technique any time you want to share an Enum between your business object class library project and your Silverlight project.
Enjoy!
So I have read in dozens of places that you can easily share a source code file between your business objects and your Silverlight project. So I thought I would try it. No go.
[To begin with an overview of Silverlight, RIA, and your business objects, start here.]
Supposedly, if you created a file in your business object of the form myName.shared.vb or myName.shared.cs the compiler will automatically generate the file in your Silverlight project as well. This allows you to share the same code between the business object and Silverlight.
Many hours of trying, Bing'ing, and reading later … still no go.
Then I re-read the RIA Services Overview document that is provided with the RIA download. Reading it carefully, it starts to become clear that there are TWO things that a file needs before it will be shared with your Silverlight project:
- It must be named appropriately: myName.shared.vb or myName.shared.cs
- It must exist in the project that is linked to the Silverlight project.
This second item is the more challenging item because in most cases, your Silverlight project is linked to a Web project.
As far as I know, it is not possible to re-point the link from a Web project to a business object class library component because Silverlight cannot reference a standard class library. It can only reference a Silverlight class library or a Web project.
So I did the following:
- Named the file using the .shared.vb/.shared.cs syntax.
- Used the Add Link feature of Visual Studio projects to manually define a link between the shared file and the Web project.
The second step required the following:
1. Right click on the Web project and select Add | Existing Item.
2. In the Add Existing Item dialog, select the file and select Add As Link from the Add button drop down menu.
This adds the file as a link to the Web project.
Now that the shared file is in the Web project, when you compile Silverlight will generate the copy. You can confirm this by following these steps:
1. Select the Silverlight project.
2. Click the Show All Files button in the Solution Explorer toolbar.
3. Open the Generated_Code node.
The shared file should exist under this node.
Use this technique any time you have code in your business objects that you want to share "as is" with the Silverlight project.
[For a more detailed example of using this technique to share a file, see this post.]
Enjoy!
There may be times when you want RIA Services to ignore a property in your business object. This may be the case if you know you won't ever need to access the property from your Silverlight UI or if you can't make RIA services understand the property properly.
[To begin with an overview of Silverlight, RIA, and your business objects, start here.]
My case is the second scenario. I have yet to figure out how to make RIA Services understand my Enum property. Until I do, I want my code to compile, so I need RIA to ignore any property in my business object that is of an Enum type.
[EDIT 11/10/09: Found a solution to this. Blogged it here.]
I assumed that the DataAnnotations feature would have an attribute to mark a property to be ignored. But no. There is no attribute to do this in the DataAnnotations namespace.
Rather, the property is in the System.Web.DomainServices names. Here is the code example.
NOTE: Be sure to set a reference to System.Web.DomainServices.
In C#:
public enum CustomerTypeOption
{
Consumer,
Corporation,
Education,
Government
}
private CustomerTypeOption _CustomerType;
[Exclude()]
public CustomerTypeOption CustomerType {
get { return _CustomerType; }
set { _CustomerType = value; }
}
In VB:
Public Enum CustomerTypeOption
Consumer
Corporation
Education
Government
End Enum
Private _CustomerType As CustomerTypeOption
<Exclude()> _
Public Property CustomerType() As CustomerTypeOption
Get
Return _CustomerType
End Get
Set(ByVal value As CustomerTypeOption)
_CustomerType = value
End Set
End Property
This code resides in a Customer class like the one show here. The Exclude attribute causes RIA Services to exclude this property from its generated code.
Use this attribute any time you want a property excluded from the code generated in the Silverlight project. This property is then inaccessible from your Silverlight code.
I would have never figured this out without the help of Colin Blair from the Microsoft Silverlight forums, who found it on Hatim's Blog post. Thanks Colin and Hatim!
Enjoy!
This post details first how to build a list containing the data to display in a WinForms TreeView control. Then it demonstrates how to use recursion to populate the TreeView control from the list.
[For information on populating a TreeView control from XML, see this link.]
First, create a class that will store the data for the TreeView.
In C#:
public class TreeViewItem
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Text { get; set; }
}
In VB:
Public Class TreeViewItem
Public Id As Integer
Public ParentId As Integer
Public Text As String
End Class
The C# code uses auto-implemented properties to short-cut the code. The VB code is just me being lazy tonight. It is using Public fields instead of Public Properties as it should. (In VS 2010, VB will have auto—implemented properties as well.)
The class defines an Id associated with the item and a ParentId defining the Id of the parent item (that is the item under which this item will appear in the TreeView). It also has a Text property that contains the text of the TreeView node.
In the WinForm containing the TreeView control, add the code to build the list as shown below.
In C#:
List<TreeViewItem> treeViewList = new List<TreeViewItem>();
treeViewList.Add(new TreeViewItem() {
ParentID = 0, ID = 1, Text = "Parent node" });
treeViewList.Add(new TreeViewItem() {
ParentID = 1, ID = 2, Text = "First child node" });
treeViewList.Add(new TreeViewItem() {
ParentID = 1, ID = 3, Text = "Second child node" });
treeViewList.Add(new TreeViewItem() {
ParentID = 3, ID = 4, Text = "Child of second child node" });
treeViewList.Add(new TreeViewItem() {
ParentID = 3, ID = 5, Text = "Child of second child node" });
PopulateTreeView(0, null);
In VB:
Private treeViewList As New List(Of TreeViewItem)
treeViewList.Add(New TreeViewItem() With { _
.ParentId = 0, .Id = 1, .Text = "Parent node"})
treeViewList.Add(New TreeViewItem() With { _
.ParentId = 1, .Id = 2, .Text = "First child node"})
treeViewList.Add(New TreeViewItem() With { _
.ParentId = 1, .Id = 3, .Text = "Second child node"})
treeViewList.Add(New TreeViewItem() With { _
.ParentId = 3, .Id = 4, .Text = "Child of second child node"})
treeViewList.Add(New TreeViewItem() With { _
.ParentId = 3, .Id = 5, .Text = "Child of second child node"})
PopulateTreeView(0, Nothing)
This code defines a generic List that contains the set of TreeViewItem instances. The Add method of the list sets the data into the list. It then calls the PopulateTreeView method (shown below).
The PopulateTreeView method uses recursion to populate the TreeView from the list.
In C#:
private void PopulateTreeView(int parentId, TreeNode parentNode)
{
var filteredItems = treeViewList.Where(item =>
item.ParentID == parentId);
TreeNode childNode;
foreach (var i in filteredItems.ToList())
{
if (parentNode == null)
childNode = treeView1.Nodes.Add(i.Text);
else
childNode = parentNode.Nodes.Add(i.Text);
PopulateTreeView(i.ID, childNode);
}
}
In VB:
Private Sub PopulateTreeView(ByVal parentId As Integer, _
ByVal parentNode As TreeNode)
Dim filteredItems = treeViewList.Where(Function(item) _
item.ParentId = parentId)
Dim childNode As TreeNode
For Each i In filteredItems.ToList()
If parentNode Is Nothing Then
childNode = TreeView1.Nodes.Add(i.Text)
Else
childNode = parentNode.Nodes.Add(i.Text)
End If
PopulateTreeView(i.Id, childNode)
Next
End Sub
The PopulateTreeView method has two parameters: parentId and parentNode. The parentId is the Id value associated with the parent node. The code will find all items in the list with the defined parent Id. The parentNode is the TreeView node under which the items are added.
The filteredItems variable contains the results of a lambda expression finding all of the items in the list with the passed in parentId.
The code then loops through those items and adds the nodes to the parent node.
It then calls itself, making the method recursive. The method call passes in the node's Id and the node itself. This will cause the method to load all of its child nodes.
When you run the code, the TreeView should appear as follows:
Enjoy!
One of my friends sends an email message to me telling me about their great new job and letting me know that they have a new email address. I dutifully update my Outlook Address book and fix the address.
Five unanswered emails later, I realize that I have been sending email to the WRONG email address. Even though I fixed my Outlook Address book, the Outlook Most Recently Used (MRU) list still has the OLD email address. What's to be done?
Then Beth Massi shared with me an Outlook tip that I want to pass along: You can delete items from the MRU in Outlook!
Say I am typing in John's name:
If I am not watching closely, I will send it to the wrong address again (nowhere.com).
To prevent this mistake:
- Highlight the incorrect address.
- Press the Del key.
The unwanted MRU entry is then deleted.
Enjoy!
With RIA Services, it is easy to use your own business objects in your Silverlight application… once you have the basic plumbing in place. However, there are quite a few steps required to set up that plumbing. This post details the process of hooking up your business objects, RIA Services, and Silverlight.
So … you follow best practices and build business objects for all of the entities involved in your application. Or maybe you generate those business objects with something like Entity Framework (EF), Linq to SQL or other similar tool. In any case, you now want to use those business objects from a Silverlight application.
Seems like it should be easy, right? Just define a reference from your Silverlight application to your business object component and proceed just like with your WinForms or ASP.NET application. But no.
Silverlight does not allow you to set a reference to a non-Silverlight component. There are a number of ways you can deal with this:
- Use a Silverlight class library project type and build your business objects in there. Not a good solution if you are using those same business objects with other user interfaces.
- Build a WCF service that provides your business objects to your Silverlight application. You can find out more about this option here.
- Use Rich Internet Application (RIA) Services. This is a new Microsoft technology that is currently out in preview.
This post demonstrates option #3: Using RIA Services. The example presented in this post uses business objects you build yourself. These "home made" business objects are often referred to as POCO, or plain old CLR objects. Use the techniques presented in this post any time you start a new Silverlight application and want to access your POCOs from that application.
[To use RIA with Entity Framework, there is video walkthrough here.]
The basic idea behind RIA Services is to use an ASP.NET application between your business object component and your Silverlight application to provide the communication between the two as shown below.
Starting right to left, you build your business objects in a Class Library component. This example uses a Customer class to define a customer and a Customers class to provide the list of customers.
The ASP.NET application has a standard project reference to the POCO class library. The ASP.NET application includes a Domain Service class, called CustomerService in this example, that calls the desired methods in the POCO class library.
The Silverlight application has an RIA Services link to the ASP.NET Web application. When the Silverlight application is compiled, it generates a client-side copy of the entity referenced by the ASP.NET application. In this case, it generates a Customer class. It also generates a entity context (CustomerContext in this case) that can be used for Silverlight data binding.
The remainder of this post walks through this process one step at a time.
Prerequisites
Before you can begin, there are some prerequisite steps:
- If you don't have it, download and install Silverlight 3 using the information provided here.
- Download and install RIA Services using the information provided here. (You may have already done this if you followed all of the instructions in step #1)
- Create a new Visual Studio Solution.
- Create a new Class Library project in that solution and build your business objects. This example uses the Customer and Customers classes defined below.
In C#:
public class Customer
{
public int CustomerId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string EmailAddress { get; set; }
}
public class Customers
{
public static List<Customer> Retrieve()
{
List<Customer> custList = new List<Customer>
{new Customer()
{ CustomerId = 1,
FirstName="Bilbo",
LastName = "Baggins",
EmailAddress = "bb@hob.me"},
new Customer()
{ CustomerId = 2,
FirstName="Frodo",
LastName = "Baggins",
EmailAddress = "fb@hob.me"},
new Customer()
{ CustomerId = 3,
FirstName="Samwise",
LastName = "Gamgee",
EmailAddress = "sg@hob.me"},
new Customer()
{ CustomerId = 4,
FirstName="Rosie",
LastName = "Cotton",
EmailAddress = "rc@hob.me"}};
return custList;
}
}
In VB:
Public Class Customer
Private _CustomerId As Integer
Public Property CustomerId() As Integer
Get
Return _CustomerId
End Get
Set(ByVal value As Integer)
_CustomerId = value
End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property
Private _EmailAddress As String
Public Property EmailAddress () As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
_EmailAddress = value
End Set
End Property
End Class
Public Class Customers
Public Shared Function Retrieve() As List(Of Customer)
Dim custList As New List(Of Customer)
custList.Add(New Customer With {.CustomerId = 1, _
.LastName = "Baggins", _
.FirstName = "Bilbo", _
.EmailAddress = "bb@hob.me"})
custList.Add(New Customer With {.CustomerId = 2, _
.LastName = "Baggins", _
.FirstName = "Frodo", _
.EmailAddress = "fb@hob.me"})
custList.Add(New Customer With {.CustomerId = 3, _
.LastName = "Gamgee", _
.FirstName = "Samwise", _
.EmailAddress = "sg@hob.me"})
custList.Add(New Customer With {.CustomerId = 4, _
.LastName = "Cotton", _
.FirstName = "Rosie", _
.EmailAddress = "rc@hob.me"})
Return custList
End Function
End Class
The C# code here uses auto-implemented properties to shorten the property syntax. The VB code uses the full property syntax.
In a real application, the Retrieve method would collect the data from the database. This example uses hard-coded values to make it easier for you to try this code without having to set up data access.
Decorating your Business Objects
Once you have the prerequisites complete, you need to add some attributes to your business objects so they are recognized by RIA Services when it generates the Silverlight entity.
1. Open the solution containing your business objects.
2. Open the Class Library project properties window for the project containing your business objects.
3. Add a reference to System.ComponentModel.DataAnnotations.
I had two of these. Be sure to pick the one from the Microsoft SDKs\RIA Services directory.
This namespace contains the attributes you need to annotate your business objects for RIA Services.
4. Add the Key attribute on the property that represents the unique key of the business object.
In C#:
[System.ComponentModel.DataAnnotations.Key()]
public int CustomerId { get; set; }
In VB:
Private _CustomerId As Integer
<System.ComponentModel.DataAnnotations.Key()> _
Public Property CustomerId() As Integer
Get
Return _CustomerId
End Get
Set(ByVal value As Integer)
_CustomerId = value
End Set
End Property
In this example, CustomerId is the unique key, so it is marked with the Key attribute.
5. Optionally, add RIA attributes for validation as desired. (More on this in a future post.)
Adding the Silverlight Project
Now that the business objects are ready, you can add the Silverlight project to your solution. This will automatically generate the associated ASP.NET project as well.
1. Add a new Silverlight Application to your solution.
You can create your Silverlight project in VB or in C#.
2. Be sure to enable RIA Services.
3. Click OK.
Visual Studio creates both the Silverlight project and the associated ASP.NET project. At runtime, the ASP.NET project launches the Silverlight project. The ASP.NET project also provides access to your business objects through RIA services.
NOTE: If you already have a Silverlight project that you wish to use with RIA Services, you can enable .NET RIA Services in the Silverlight project properties window:
Adding the Domain Service Classes
The ASP.NET application provides the communication between your business objects and the Silverlight application. This is accomplished with RIA Services by creating a set of Domain Service classes in the ASP.NET application.
1. Add a Domain Service class to the ASP.NET application.
2. Be sure to enable client access.
Notice that the bottom of this dialog is empty. If you used Entity Framework to generate your entities, they would appear in this dialog. But since this example uses POCOs, there won't be any entities displayed.
3. Click OK.
Visual Studio creates the basic structure of your Domain Service class as shown below.
In C#:
namespace SLCSharp.Web
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Ria;
using System.Web.Ria.Data;
using System.Web.DomainServices;
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public class CustomerService : DomainService
{
}
}
In VB:
Option Strict Off
Option Explicit On
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
Imports System.Linq
Imports System.Web.DomainServices
Imports System.Web.Ria
Imports System.Web.Ria.Data
'TODO: Create methods containing your application logic.
<EnableClientAccess()> _
Public Class CustomerService
Inherits DomainService
End Class
NOTE: IMMEDIATELY change the Option Strict Off to Option Strict ON OR remove the lines entirely if you have them both on by default in your Compile options.
You can also remove any import statements for namespaces you already import through the References tab of the Properties window.
Accessing Your Business Objects From the Domain Service Class
The code to access your business objects goes into the Domain Service class created above. You can think of the Domain Service class as a wrapper around your business object members that provides a way for Silverlight to access the object properties and methods.
Any time you need to access any properties or call any methods on your business objects, you need to write a wrapper for that access in the Domain Service class.
Normally, you create one Domain Service class for each entity. So if your business objects included Customer/Customers, Purchase/Purchases, Invoice/Invoices, for example, you would create a CustomerService, PurchaseService, and InvoiceService class.
1. In your ASP.NET project containing your Domain Service class, set a reference to your business object component.
My sample project has two sets of business objects, one in VB and one in C#. You can access business objects in either language.
2. In the Domain Service class, import the namespace for the business object component.
In C#:
using BoCSharp;
In VB:
Imports BoVB
Or if you are using VB, import this namespace using the References tab of the Properties window:
3. Add the code that wraps the functionality of your business object.
For the purposes of this post, the code retrieves the set of customers.
In C#:
public IEnumerable<Customer> GetCustomers()
{
return Customers.Retrieve();
}
In VB:
Public Function GetCustomers() As IEnumerable(Of Customer)
Return Customers.Retrieve()
End Function
Note that this must be a method and not a property. If you attempt to use a property here, the compiler won't generate the required code.
The GetCustomers method simply calls the static/shared Retrieve method of the Customers class (defined at the beginning of this post).
Accessing the Domain Service Class from Silverlight
Finally! You are ready to use your business object from Silverlight by way of the Domain Service class.
1. Open the Silverlight Application project.
2. This example uses a grid to display the customer data, so set a reference to System.windows.Controls.Data.
3. To use the RIA Service controls, set a reference to System.Windows.Ria.Controls.
3. Open the MainPage.xaml file that was created for you in the Silverlight Application project.
4. Set up the XML namespaces.
<UserControl x:Class="SLCSharp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:d=http://schemas.microsoft.com/expression/blend/2008
xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
xmlns:domain="clr-namespace:SLCSharp.Web"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
NOTE: If you copy and paste these namespaces into your application, be sure that you change the values shown in red. The x:Class is the name of the associated code behind class file. the xmlns:domain is the name of the associated ASP.Net application namespace.
These namespaces are as follows:
- xmlns - Default for Silverlight and WPF.
- xmlns:x – XAML-defined language elements for Silverlight and WPF, required for even basic features such as mapping a XAML file to its code behind file using x:Class.
- xmlns:data – XAML data controls, such as the DataGrid.
- xmlns:riaControls – RIA Service controls, such as the DomainDataSource
- xmlns:domain – Defines the associated ASP.NET namespace, required when using RIA services.
5. Add code to display a bound grid.
<Grid x:Name="LayoutRoot">
<riaControls:DomainDataSource x:Name="CustomerSource"
QueryName="GetCustomers" AutoLoad="True">
<riaControls:DomainDataSource.DomainContext>
<domain:CustomerContext/>
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<data:DataGrid x:Name="CustomerList"
ItemsSource="{Binding Data, ElementName=CustomerSource}">
</data:DataGrid>
</Grid>
This code sets up an RIA Services DomainDataSource defining the source of the data for this UserControl. It defines a name (CustomerSource) and a QueryName (GetCustomers). The query name must match the name of the method to call in the Domain Service class.
It also sets a DomainContext, which associates the DomainDataSource with the Domain Service. This must match the name of the DomainContext in the generated code. Intellisense should help you define the correct name.
The DataGrid element uses the ItemsSource attribute to define the binding source. In this case, it is CustomerSource. This name must match the name given to the DomainDataSource.
6. Run it.
If everything works, it should appear as follows:
Now you can use your mad skills with styles and other Silverlight features to make this look nice.
Wow! That seemed like a lot of work.
The good news is that once you set this up, adding more features is easy:
- Add appropriate wrappers to the ASP.Net project using the Domain Service classes.
- Use the wrappers as needed from your Silverlight application.
Enjoy!
Gradients are a good way to make your application more visually interesting. They turn a flat solid color into something more natural and appealing to the eye by creating an illusion of light and shadow.
For example, compare this image which uses flat colors:
to this image that uses gradient colors:
For some great uses of gradient effects in user interface designs, see this link.
If you are not familiar with the terminology, a gradient is basically a blending of colors with an even graduation from one set of color values to another set of color values. Notice in the second image above, the white gradually blends into blue from the upper left corner to the lower right corner.
In Silverlight and WPF, you can set colors for just about anything, and wherever you can set a color you can often set a gradient. These examples color the background of the Canvas, but you could also paint Buttons or Rectangles or whatever.
In XAML, there are two primary types of gradients: Linear and Radial.
Linear Gradient
A linear gradient defines the graduation of colors along a line. The example below shows a linear gradient following a line from white at the top to blue at the bottom:

The XAML code required to set the linear gradient shown above is as follows:
<Canvas>
<Canvas.Background>
<LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="LightSteelBlue" Offset="1"/>
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
This specific code sets the gradient for a Canvas background, but you could use it anywhere a gradient is allowed.
The first properties you need to consider when defining your gradient are the StartPoint and EndPoint properties. These properties define where the gradient line begins and where it ends.
Think of the canvas as a rectangle with the (0,0) point in the upper left corner and the (1,1) point in the lower right corner as shown below:
If you set the StartPoint to 0,0 and the EndPoint to 1,1, the gradient will start in the upper left and end in the lower right as shown in the Address dialog example shown at the top of this post. (This is the default value, so if you don't set the StartPoint and EndPoint properties, this is the gradient you will get.)
By setting the StartPoint to .5,0 and EndPoint to .5,1, the gradient line begins in the middle top of the rectangle and ends in the middle bottom, creating the affect shown above.
If you want the gradient to appear from left to right, set the StartPoint to 0,.5 and the EndPoint to 1,.5. For right to left, set the StartPoint to 1,.5 and the EndPoint to 0,.5.
You can use any point in the rectangle as the StartPoint and EndPoint to create different effects. For example, you could set the StartPoint to .2,.2 and the EndPoint to .8,.8 to create this effect:
Notice the upper left corner is completely white and the lower right corner is completely blue. The gradient does not begin until .2,.2 and then ends at .8,.8.
The other important property to use when working with a linear gradient is the GradientStop. It defines the colors and where they start. The first GradientStop in this example is set to white and starts at an Offset of 0. The second GradientStop is set to LightSteelBlue at an Offset of 1.
Think of the Offset as the point on the gradient line where 0 is the origin of the line and 1 is the termination of the line. Note that if your StartPoint and EndPoint properties are not at a point of origin (such as .2,.2 and .8,.8), the Offset will still begin at the origin and end at the termination of the gradient line.
You can use any number of GradientStops (with a minimum of two) to include multiple colors in your gradient. For example, this gradient changes from White, to LightSteelBlue, to SteelBlue, back to LightSteelBlue and then White:
<Canvas>
<Canvas.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1" >
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="LightSteelBlue" Offset=".2"/>
<GradientStop Color="SteelBlue" Offset=".4"/>
<GradientStop Color="LightSteelBlue" Offset=".8"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
And the effect is as follows:
Radial Gradient
A radial gradient defines the graduation of colors that radiate outward from an origin. The example below shows a radial gradient radiating out from the center:
The XAML code required to set a radial gradient is as follows:
<Canvas>
<Canvas.Background>
<RadialGradientBrush GradientOrigin=".5,.5">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="LightSteelBlue" Offset="1"/>
</RadialGradientBrush>
</Canvas.Background> This specific code sets the gradient for a Canvas background, but you could use it anywhere a gradient is allowed.
The first property you need to consider when defining your radial gradient is the GradientOrigin. It defines where the gradient begins.
Think of the canvas as a rectangle with the (0,0) point in the upper left corner and the (1,1) point in the lower right corner as shown below:
In this example, the gradient radiates out from the center point, which is .5,.5. (This is the default value, so if you don't set the GradientOrigin property, this is the gradient origin you will get.)
The other important property to use when working with a radial gradient is the GradientStop. It defines the colors and where they start. The first GradientStop in this example is set to white and starts at an Offset of 0. The second GradientStop is set to LightSteelBlue at an Offset of 1.
This GradientStop property in the radial gradient is similar to the linear gradient. The primary difference is that the gradient line begins at the GradientOrigin and radiates outward.
You can use any number of GradientStops (with a minimum of two) to include multiple colors in your gradient. For example, this gradient has an origin of .2,.2. It then changes from White, to LightSteelBlue, to SteelBlue, back to LightSteelBlue and then White:
<Canvas>
<Canvas.Background>
<RadialGradientBrush GradientOrigin=".2,.2">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="LightSteelBlue" Offset=".2"/>
<GradientStop Color="SteelBlue" Offset=".4"/>
<GradientStop Color="LightSteelBlue" Offset=".8"/>
<GradientStop Color="White" Offset="1"/>
</RadialGradientBrush>
</Canvas.Background>
</Canvas>
And the effect is as follows:
Feel free to experiment with both types of gradients to produce interesting visual effects.
Enjoy!
If you are building a WinForms application and want to style your text, you may want to use the RichTextBox control instead of the standard TextBox control. This post provides some of the common techniques required to style text in a RichTextBox control.
To try out the examples in this post, build a new WinForms application. If desired, change the name of the form to "RichTextBoxSampleWin". Add a RichTextBox and two buttons to the form.
Setting Fonts/Colors
To set the fonts and colors of a RichTextBox, always select the text to style first, then set its font and color.
To try out setting fonts and colors, add this code to the Load event for the form:
In C#:
richTextBox1.Text = "That that is, is. That that is not, is not. "
+ "Is that it? It is.";
// Set the style of the text
richTextBox1.SelectionLength = richTextBox1.Text.Length;
richTextBox1.SelectionFont = new Font("Arial", 12, FontStyle.Bold);
richTextBox1.SelectionColor = Color.DarkSlateBlue;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
In VB:
richTextBox1.Text = "That that is, is. That that is not, is not. " & _
"Is that it? It is."
' Set the style of the text
richTextBox1.SelectionLength = richTextBox1.Text.Length
richTextBox1.SelectionFont = New Font("Arial", 12, FontStyle.Bold)
richTextBox1.SelectionColor = Color.DarkSlateBlue
richTextBox1.SelectionStart = richTextBox1.Text.Length
The first line of code sets text into the RichTextBox.
The most important step here is to select the text that you want to style. Use the SelectionStart property to set the location in the text string to start the selection. If you don't specify a selection start, it is assumed to start at the first character of the text. Use the SelectionLength property to specify the length of the selection. In this case, the entire length of text is selected to be styled.
NOTE: Instead of setting the SelectionStart and SelectionLength properties, you can use the Select method to set both the selection start and selection length at one time.
The next two lines set the font and color for the selected text. The last line resets the SelectionStart to the end of the text. This allows the user to type in further text.
If you run the application at this point, it should look like this:
If the user types in more text, it will follow the given style.
Setting Multiple Fonts/Colors
There are two basic ways to set multiple fonts and colors within the text:
- Add the text by appending to the Text property and then use SelectionStart and SelectionLength to select the appended text. Then style the selected text. This is similar to the approach used above.
- Set the styles and then use the AppendText property.
To try out technique #1, add the following code to the button click event for one of the buttons on the form:
In C#:
// Add another set of text in a different style
string addedText = Environment.NewLine + Environment.NewLine
+ "This famous quote was from what movie?";
int len = richTextBox1.Text.Length;
richTextBox1.Text += addedText;
richTextBox1.SelectionStart = len;
richTextBox1.SelectionLength = addedText.Length;
richTextBox1.SelectionFont = new Font("Verdana", 10,
FontStyle.Regular);
richTextBox1.SelectionColor = Color.DarkSlateGray;
In VB:
Dim addedText As String = Environment.NewLine & Environment.NewLine & _
"This famous quote was from what movie?"
Dim len As Integer = richTextBox1.Text.Length
richTextBox1.Text &= addedText
richTextBox1.SelectionStart = len
richTextBox1.SelectionLength = addedText.Length
richTextBox1.SelectionFont = New Font("Verdana", 10, FontStyle.Regular)
richTextBox1.SelectionColor = Color.DarkSlateGray
This code first stores the length of the current text. This will be the selection start value. The additional text is then appended to the Text property of the RichTextBox. The SelectionStart and SelectionLength are then set as in the prior example. Finally, the font and color are set.
NOTE: You must set the SelectionStart and SelectionLength after you append the text.
To try out technique #2, replace the code in the button click event with this code instead:
In C#:
// Add another set of text in a different style
string addedText = Environment.NewLine + Environment.NewLine
+ "This famous quote was from what movie?";
richTextBox1.SelectionFont = new Font("Verdana", 10,
FontStyle.Regular);
richTextBox1.SelectionColor = Color.DarkSlateGray;
richTextBox1.AppendText(addedText);
In VB:
' Add another set of text in a different style
Dim addedText As String = Environment.NewLine & Environment.NewLine & _
"This famous quote was from what movie?"
richTextBox1.SelectionFont = New Font("Verdana", 10, FontStyle.Regular)
richTextBox1.SelectionColor = Color.DarkSlateGray
richTextBox1.AppendText(addedText)
Notice how much shorter this option is. This technique sets the SelectionFont and SelectionColor and then uses the AppendText method to add the styled text to the RichTextBox.
Regardless of which technique you choose, the result will appear as follows:
Highlighting Words
Another common requirement when working with a RichTextBox is to highlight multiple occurrences of a specific word.
To try out this technique, add this code to the other button click event:
In C#:
string wordToFind = "is";
int startIndex = 0;
while (startIndex > -1)
{
startIndex = richTextBox1.Find(wordToFind, startIndex + 1,
richTextBox1.Text.Length,
RichTextBoxFinds.WholeWord);
if (startIndex > -1 )
{
richTextBox1.Select(startIndex, wordToFind.Length);
richTextBox1.SelectionFont = new Font("Verdana", 12,
FontStyle.Bold | FontStyle.Italic);
richTextBox1.SelectionColor = Color.Red;
}
}
In VB:
Dim wordToFind As String = "is"
Dim startIndex As Integer = 0
Do While startIndex > -1
startIndex = richTextBox1.Find(wordToFind, startIndex + 1, _
richTextBox1.Text.Length, _
RichTextBoxFinds.WholeWord)
If startIndex > -1 Then
richTextBox1.Select(startIndex, wordToFind.Length)
richTextBox1.SelectionFont = New Font("Verdana", 12, _
FontStyle.Bold Or FontStyle.Italic)
richTextBox1.SelectionColor = Color.Red
End If
Loop
The code first defines the word to find. If this code is contained in a method instead of an event, the word to find could be passed in as a parameter.
The code then loops through the text in the RichTextBox using the Find method of the RichTextBox to find the next occurrence. The Find method will return –1 when it does not find any matches.
If a match is found, the code uses the Select method to select the found text. It then sets the font and color. This example also demonstrates how to bold AND italicize the text by Or'ing the style flags.
The result appears as follows:
Enjoy!
Way back when we were using Visual Studio 2005, there was a Clipboard Ring tab in the Toolbox. I loved that tab!
When I am doing a bunch of copy/pastes, I sometimes find that I hit Ctrl+C when I meant to hit Ctrl+V, and poof! my Clipboard was kindly wiped out for me.
With VS 2005, I could just open that Clipboard Ring tab of the Toolbox and there it would be ... the last several sets of Clipboard contents just waiting for me to reuse.
I was bummed when I did not see this feature in VS 2008. But behold, there are alternatives.
Accessing the Internal Clipboard Ring
Visual Studio still keeps the last sets of items you copied to the Clipboard. I heard the number was 20: that it keeps the contents from the last 20 times you used the copy function. But I did not test out that limit.
You can no longer see these last 20 clipboard items, but you can access them using keystrokes:
Ctrl+Shift+V
Keep holding down the Ctrl+Shift and pressing V to loop through the last set of clipboard items until you find the one you want.
This has saved me much frustration, knowing I can get back to my prior Clipboard contents.
Using the Toolbox
When you are using a code editor, the Toolbox displays a General tab. You can copy and paste or drag and drop code to this tab. This allows you to readily access these bits of code any time you are in a code editor.
Say I have a standard Try/Catch block that I want to use in several methods. If I plan to reuse this code often, I could create a code snippet as described here. But in this case, I just want to use this code today while I am doing some editing.
So I write the code I want, and drag it onto the General tab of the toolbox. It looks like this:
NOTE: I dragged some VB code, but this works the same in C#. It even allows me to have both VB and C# code on this tab at the same time.
If I hover over the entry, I can see the complete set of code as follows:
I can also rename the entry if I want to give it a name I can more readily recognize:
Note, however, that if the entry is renamed, hovering over the entry displays the name and not the set of code that the name represents.
I can then copy and paste from the toolbox, or just drag and drop the entry into the code editor.
If I want to manage a large set of these, I can add additional tabs to the toolbox and organize these pieces of code however I want.
If you are presenting or training, this technique is good for keeping longer code pieces so that your attendees don't need to watch you type quite so much.
Enjoy!
Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development and execution of unit tests. It is, of course, easy for your tests to access the public properties and methods of your classes. But what about those private members?
[To begin with an overview of unit testing, start here.]
[To expose internal (C#) or friend (VB) members, see this post.]
You may have properties or methods in your class defined to be private. This post details how to test those methods using the unit testing features in Visual Studio.
NOTE: There are some testing philosophies that recommend you never test your private members; rather you test the public/internal/friend members and assume that those will provide coverage for all of your private members as well. Regardless of your point of view on this topic, this post provides the how-to so you can decide for yourself whether you want to or not.
Let's say your Customer class has a CustomerId property that looks like this:
In C#:
private int? _CustomerId;
public int? CustomerId
{
get { return _CustomerId; }
private set
{
if (_CustomerId == null || !_CustomerId.Equals(value))
{
string propertyName = "CustomerId";
if (!_CustomerId.Equals(value))
{
_CustomerId = value;
}
}
}
}
In VB:
Private _CustomerId As Integer?
Public Property CustomerId() As Integer?
Get
Return _CustomerId
End Get
Private Set(ByVal value As Integer?)
If _CustomerId Is Nothing OrElse _
Not _CustomerId.Equals(value) Then
Dim propertyName As String = "CustomerId"
If Not _CustomerId.Equals(value) Then
_CustomerId = value
End If
End If
End Set
End Property
Notice in both cases, the setter is private. The expectation is that the business object will set this value. However, you can you unit test it if you cannot set it?
The answer is PrivateObject.
PrivateObject is a class that allows test code to call class members that are not public. You PrivateObject to access private members of your class in your unit tests.
NOTE: You can also use PrivateObject for other non-public class members.
The following demonstrates how to use PrivateObject.
In C#:
/// <summary>
///A test for CustomerId
///</summary>
[TestMethod()]
public void CustomerIdTest()
{
Customer target;
int? expected;
int? actual;
// Null to Value
target = new Customer();
expected = 42;
PrivateObject po = new PrivateObject(target);
po.SetProperty("CustomerId", expected);
actual = target.CustomerId;
Assert.AreEqual(expected, actual, "Values are not equal");
}
In VB:
'''<summary>
'''A test for CustomerId
'''</summary>
<TestMethod()> _
Public Sub CustomerIdTest()
Dim target As Customer
Dim expected As Integer?
Dim actual As Integer?
' Null to value
target = New Customer
expected = 42
Dim po As PrivateObject = New PrivateObject(target)
po.SetProperty("CustomerId", expected)
actual = target.CustomerId
Assert.AreEqual(expected, actual, "Values are not equal")
End Sub
NOTE: This is only part of the code you would need to fully test the CustomerId property. Only one testing scenario was covered to instead focus on the PrivateObject syntax.
The code creates a new instance of PrivateObject, passing the instance of the class that has the private member you wish to access.
PrivateObject has methods such as GetProperty, SetProperty, GetField, SetField, and Invoke. These methods provide access to the private or other non-public members of your class.
Use PrivateObject whenever you need to access a private member in your class from your unit testing code.
Enjoy!
Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development and execution of unit tests. It is, of course, easy for your tests to access the public properties and methods of your classes. But what about those internal/friend members?
[To begin with an overview of unit testing, start here.]
[To expose private members, see this post.]
You may have properties or methods in your class defined to be internal (C#) or friend (VB). This post details how to test those methods using the unit testing features in Visual Studio.
Well, it is easy. You just need to use this line of code:
In C#:
[assembly: InternalsVisibleTo("BoTest")]
In VB:
<Assembly: InternalsVisibleTo("BoTest")>
Where BoTest is the name of your unit testing project.
The hard part is finding where you put this line of code. It should reside in your AssemblyInfo file.
This file is relatively easy to find in your C# project; not so easy to find in your VB project. Here are the steps for both.
In C#:
- Find the project you want to test in Solution Explorer.
- Open the Properties node.
- Double-click on the AssemblyInfo.cs file.
- Set the InternalsVisibleTo attribute into the AssemblyInfo.cs file.
- Rebuild the project.
In VB:
- Find the project you want to test in Solution Explorer.
- Click on the Show All Files button in the Solution Explorer toolbar.
- Open the My Project node
- Double-click on the AssemblyInfo.vb file.
- Set the InternalsVisibleTo attribute into the AssemblyInfo.vb file.
Use this technique whenever you have internal/friend members in a class that you wish to access with your unit testing code.
Enjoy!
Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development and execution of unit tests. This post provides suggestions for testing your property getters and setters.
[To begin with an overview of unit testing, start here.]
Let's say your Customer class has a LastName property that looks like this:
In C#:
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == null || _LastName != value)
{
string propertyName = "LastName";
// Perform any validation here
if (_LastName != value)
{
_LastName = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
(Someone just mentioned how all of those ending braces look like a flock of sea gulls! LOL!)
In VB:
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
If _LastName Is Nothing OrElse _
_LastName IsNot value Then
Dim propertyName As String = "LastName"
' Perform any validation
If _LastName IsNot value Then
_LastName = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
NOTE: The SetEntityState method used in this code is coming from the business object base class provided in this prior post.
The key step for developing a good unit test is to define the test scenarios.
Looking at the requirements, the following testing scenarios are required for the LastName property:
- Initial null value; set to null value (should perform validation but not set the dirty flag)
- Initial null value; set to valid string (should perform validation and set the dirty flag)
- Initial null value; set to empty string (should perform validation and set the dirty flag)
- Initial string value; set to null value (should perform validation and set the dirty flag)
- Initial string value, set to different string value (should perform validation and set the dirty flag)
- Initial string value, set to same string value (should not perform validation and not set the dirty flag)
- Initial string value, set to empty value (should perform validation and set the dirty flag)
And if you have validation code, you will have more scenarios to test valid and invalid values. But this is enough to give you the general idea.
If you are building the unit test from existing code, you can generate the basic structure of the unit test for the property following the techniques detailed in this prior post.
But the test generation only gives you the basic template for testing your properties. This post focuses on how to update that template to perform the required unit testing.
Visual Studio will generate the following unit test template for the LastName property that was shown at the beginning of this post:
In C#:
/// <summary>
///A test for LastName
///</summary>
[TestMethod()]
public void LastNameTest()
{
Customer target = new Customer(); // TODO: Initialize to an appropriate value
string expected = string.Empty; // TODO: Initialize to an appropriate value
string actual;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}
In VB:
'''<summary>
'''A test for LastName
'''</summary>
<TestMethod()> _
Public Sub LastNameTest()
Dim target As Customer = New Customer ' TODO: Initialize to an appropriate value
Dim expected As String = String.Empty ' TODO: Initialize to an appropriate value
Dim actual As String
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual)
Assert.Inconclusive("Verify the correctness of this test method.")
End Sub
To cover the defined testing scenarios, this code needs to be enhanced as follows:
In C#:
/// <summary>
///A test for LastName
///</summary>
[TestMethod()]
public void LastNameTest()
{
Customer target;
string expected;
string actual;
// Null to Null
target = new Customer();
expected = null;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(false, target.IsDirty,
"Object not marked as dirty");
// Null to value
target = new Customer();
expected = "Johnson";
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(true, target.IsDirty, _
"Object not marked as dirty");
// Null to Empty
target = new Customer();
expected = string.Empty;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(true, target.IsDirty,
"Object not marked as dirty"); // Value to Null
target = new Customer() {LastName = "Johnson"};
target.SetEntityState(BoBase.EntityStateType.Unchanged);
expected = null;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(true, target.IsDirty,
"Object not marked as dirty");
// Value to new Value
target = new Customer() {LastName = "Johnson"};
target.SetEntityState(BoBase.EntityStateType.Unchanged);
expected = "Jones";
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(true, target.IsDirty,
"Object not marked as dirty");
// Value to same Value
target = new Customer() {LastName = "Johnson"};
target.SetEntityState(BoBase.EntityStateType.Unchanged);
expected = "Johnson";
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(false, target.IsDirty,
"Object not marked as dirty");
// Value to Empty
target = new Customer() {LastName = "Johnson"};
target.SetEntityState(BoBase.EntityStateType.Unchanged);
expected = string.Empty;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual, "Values are not equal");
Assert.AreEqual(true, target.IsDirty,
"Object not marked as dirty");
} In VB:
'''<summary>
'''A test for LastName
'''</summary>
<TestMethod()> _
Public Sub LastNameTest()
Dim target As Customer
Dim expected As String
Dim actual As String
' Null to Null
target = New Customer
expected = Nothing
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(False, target.IsDirty, _
"Object not marked as dirty")
' Null to value
target = New Customer
expected = "Johnson"
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(True, target.IsDirty, _
"Object not marked as dirty")
' Null to Empty
target = New Customer
expected = String.Empty
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(True, target.IsDirty, _
"Object not marked as dirty") ' Value to Null
target = New Customer With {.LastName = "Johnson"}
target.SetEntityState(BOBase.EntityStateType.Unchanged)
expected = Nothing
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(True, target.IsDirty, _
"Object not marked as dirty")
' Value to new Value
target = New Customer With {.LastName = "Johnson"}
target.SetEntityState(BOBase.EntityStateType.Unchanged)
expected = "Jones"
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(True, target.IsDirty, _
"Object not marked as dirty")
' Value to same Value
target = New Customer With {.LastName = "Johnson"}
target.SetEntityState(BOBase.EntityStateType.Unchanged)
expected = "Johnson"
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(False, target.IsDirty, _
"Object not marked as dirty")
' Value to Empty
target = New Customer With {.LastName = "Johnson"}
expected = String.Empty
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual, "Values are not equal")
Assert.AreEqual(True, target.IsDirty, _
"Object not marked as dirty")
End Sub NOTE: The SetEntityState method and IsDirty property used in this code are coming from the business object base class provided in this prior post.
NOTE: You may get an error when calling SetEntityState because in the business object base class is defined to be protected internal (protected friend in VB), not public. If so, you need to use the technique presented here to allow your tests to access internal/friend properties and methods.
WOW! That is a LOT of test code! Some experts have said that every line of code needs at least 3 - 5 lines of test code.
We have 17 lines of C# code and 15 lines of VB code in our property procedure. If I counted correctly, there are 51 lines of C# test code and 50 lines of VB test code. So that is about 3x the number of code lines.
Let's walk through what this test code is doing, following through with the testing scenarios provided earlier in this post.
Scenario 1: The code creates a new instance of the Customer class. By default, a new instance sets any string values to null/nothing. So the test code simply sets the property to null/nothing, gets the value, confirms that the value is as expected, and ensures that it did not get marked as dirty.
Scenario 2: The code again creates a new instance of the Customer class. By default a new instance sets any string values to null/nothing. So the test code sets the property to a value, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.
Scenario 3: The code again creates a new instance of the Customer class. By default a new instance sets any string values to null/nothing. So the test code sets the property to an empty string, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.
Now for the harder scenarios. There is no easy way to set a non-null initial value for a property. One option is to set the private backing variable to the desired initial value. But with the backing variable being private, there is extra code to write to make it work.
Another option is to set the property using the setter. But this marks the object as dirty, adversely interfering with the test. So if you use this technique, you then need to call SetEntityState to clear the entity state and ensure it is not marked as dirty. The sample code used this technique.
Scenario 4: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to a null, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.
Scenario 5: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to another valid value, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.
Scenario 6: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to the same value, gets the value, confirms that the value is as expected, and ensures that the object was not marked as dirty.
Scenario 7: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to an empty string, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.
If the field was validated, such as required field validation or a maximum length check, additional scenarios and associated test code would be required.
So for every business object property in your application, define the appropriate set of test scenarios and build the test code to support each scenario.
Or build a test base class that performs the basic set of tests for each of your properties. But that is left for a future post.
Enjoy!
Visual Studio 2008 (Team System ONLY) provides a really nice set of tools for viewing the code coverage of your unit tests.
NOTE: While Visual Studio 2008 Professional Edition has tools for building, executing, and debugging unit tests, it does NOT include the code coverage tools.
[To begin with an overview of unit testing, start here.]
Code coverage illustrates how much of the code was executed (or covered) by the unit tests.
NOTE: This post assumes you have already generated or created at least one unit test.
To view the code coverage for your unit tests:
1) Enable code coverage in the test run configuration file.
When you create your first unit test, Visual Studio adds a LocalTestRun.testrunconfig file to your solution under the Solution Items folder. Double-click on this file to open it:
Select Code Coverage from the left list:
Select the files on the right that you wish to view code coverage. Don't pick your test projects; rather pick the projects containing the code that is being tested. Then click Apply and Close.
2) Run your unit tests. The Test Results dialog appears.
See this prior post for more information on executing unit tests.
Your results should appear similar to the following:

3) If the tests pass, click the Show Code Coverage Results button in the far upper right corner of the Test Results dialog. The Code Coverage Results dialog appears.
This dialog displays all of the code in all of the files you selected for code coverage instrumentation. Drill down as needed to find the desired code.
In this case, the unit test is testing the getter and setter for the LastName. Notice that the covered percentage for both the getter and setter is 100%. That means that the unit test is executing every line of code in the getter and setter.
4) Double click on any item in the Code Coverage dialog to open the associated code window.
Notice how the code is color-coded. Blue shows code that was executed by the unit test. Red shows code that was not executed.
Use this feature to ensure you have unit tests for each unit of your code.
Enjoy!
EDITED 10/27/09: While Visual Studio 2008 Professional Edition has tools for building, executing, and debugging unit tests, it does NOT include the code coverage tools. In the original version of this post, I incorrectly stated that code coverage tools were available in the Professional Edition. This post has been corrected.
Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development and execution of unit tests.
[To begin with an overview of unit testing, start here.]
This prior post demonstrates how to build a unit test using the "Create Unit Tests..." feature of the Code Editor. This post demonstrates how to execute a unit test.
NOTE: This post assumes you have already generated or created at least one unit test.
To execute one or more unit tests:
1) Open your solution in Visual Studio.
The solution should include both the project(s) to test and the test project(s).
2) Select Test | Windows | Test View from the menu to view the Test View window. This window contains every method marked with the TestMethod attribute.
The solution used for the screenshot above has two LastNameTest unit tests: One for the VB example and one for the C# example. If you coded along from the prior unit testing post, you will only have one for whichever language you selected. If you have been creating unit tests for one of your projects, you may have hundreds of tests in this list.
To execute unit tests:
1) Select one or more tests from the list of tests in the Test View window.
You can add columns and sort the list or use the filter feature to make it easier to locate and select the desired tests to execute.
2) Click the Run Selection button in the upper left corner of the Test View window or right-click on any selected test and select "Run Selection" from the context menu.
The selected test(s) will then execute, displaying their status in the Test Results window:
When the test is complete, the Test Results window will look something like this:
Both of the tests shown in the above screenshot are marked as Inconclusive because the generated unit testing code used the Assert class Inconclusive method. The generated unit testing template is designed to prevent a false positive. It generates an inconclusive result until you update the unit test with correct valid and invalid values and remove the Inconclusive method call.
To see more information on the result of the test, double-click on a test result.
To run the test again, use the Run or Debug buttons at the top of the Test Results window.
If any of the tests don't pass, they are marked as Failed in the Test Results window as shown below.
Double click on any of the failed tests to view the test results:
The Error Stack Trace at the bottom of this window gives you further information on the source of the failure. Click on any link in the stack trace to jump to the associated location in the code.
You can also debug the code as the test is executing if you "Debug Selection" option instead of the "Run Selection" option.
After you update the unit tests with valid code and the tests pass, the Test Results will appear as follows:
Notice the color-coded green passing indicator.
Enjoy!
If you have Visual Studio 2008 or later and have the Professional Edition or better (NOT the Express Editions), you have some very nice unit testing tools within your Visual Studio environment. These tools help you write, execute, and track your unit tests and code coverage. This post provides an introduction to using these tools.
If you are new to unit testing, the idea is to test the smallest possible units of your code. In most cases, the smallest units of code are the property procedures and methods of your classes.
For more general information about the purpose of unit testing, see this link.
NOTE: Unit testing is not meant to replace integration testing, system testing, or user testing.
In some development methodologies, such as Test Driven Development (TDD), unit tests are written before the code is written. You capture the basic requirements in the unit test and then write the code. The code is complete when the unit tests pass. Visual Studio 2010 has features to assist with this "unit test first" approach.
But this introduction demonstrates how to write unit tests for existing code. This technique is helpful if you follow a code-first approach or if you received code from another source or have old code that did not originally have unit tests.
Build the Code
The business object tested in this post is a simplified Customer class that uses the business object base class defined in this post.
The Customer class is shown below.
In C#:
public class Customer: BoBase
{
private int? _CustomerId;
public int? CustomerId
{
get { return _CustomerId; }
internal set
{
if (_CustomerId == null || !_CustomerId.Equals(value))
{
string propertyName = "CustomerId";
// Perform any validation here
if (!_CustomerId.Equals(value))
{
_CustomerId = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == null || _LastName != value)
{
string propertyName = "LastName";
// Perform any validation here
if (_LastName != value)
{
_LastName = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (_FirstName == null || _FirstName != value)
{
string propertyName = "FirstName";
// Perform any validation here
if (_FirstName != value)
{
_FirstName = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _EmailAddress;
public string EmailAddress
{
get { return _EmailAddress; }
set
{
if (_EmailAddress == null || _EmailAddress != value)
{
string propertyName = "EmailAddress";
// Perform any validation here
if (_EmailAddress != value)
{
_EmailAddress = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
}
In VB:
Public Class Customer
Inherits BOBase
Private _CustomerId As Integer?
Public Property CustomerId() As Integer?
Get
Return _CustomerId
End Get
Friend Set(ByVal value As Integer?)
If _CustomerId Is Nothing OrElse _
Not _CustomerId.Equals(value) Then
Dim propertyName As String = "CustomerId"
' Perform any validation
If Not _CustomerId.Equals(value) Then
_CustomerId = value
SetEntityState(EntityStateType.Modified, propertyName)
End If
End If
End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
If _FirstName Is Nothing OrElse _
_FirstName IsNot value Then
Dim propertyName As String = "FirstName"
' Perform any validation
If _FirstName IsNot value Then
_FirstName = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
If _LastName Is Nothing OrElse _
_LastName IsNot value Then
Dim propertyName As String = "LastName"
' Perform any validation
If _LastName IsNot value Then
_LastName = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
Private _EmailAddress As String
Public Property EmailAddress() As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
If _EmailAddress Is Nothing OrElse _
_EmailAddress IsNot value Then
Dim propertyName As String = "EmailAddress"
' Perform any validation
If _EmailAddress IsNot value Then
_EmailAddress = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
End Class
The first if statement in each of the property procedures for the Customer class checks whether the current value is null or was changed. If the property is changed, you want to revalidate it, set it as modified, and generate a PropertyChanged event (which is handled in the SetEntityState method).
NOTE: C# uses a != operator to judge whether the value was changed. VB cannot use the <> operator because VB propagates null values. So if either value is null (nothing) the result is false. (See this msdn entry for more information.) To prevent this problem, IsNot is used in the VB code to determine if the value was changed.
The null check allows for possible validation of required fields (that is, fields that cannot be null or empty). If you know you will never need a null validation check, you can leave the null check off the first if statement.
The propertyName variable defines a name that you can use in any validation error messages and it is the name used in the PropertyChanged event.
You can then perform any necessary validation. In this simple example, no validation was added. If you want to see an example of some validation, check out this post.
Finally, if the value was changed, the backing variable is set to the new value and the SetEntityState method is called to mark the business object as "dirty" and to generate the PropertyChanged event.
Define the Testing Scenarios
So let's start with the test of the LastName property. Looking at the requirements, the following testing scenarios are required:
- Initial null value; set to null value (should perform validation but not set the dirty flag)
- Initial null value; set to valid string (should perform validation and set the dirty flag)
- Initial null value; set to empty string (should perform validation and set the dirty flag)
- Initial string value; set to null value (should perform validation and set the dirty flag)
- Initial string value, set to different string value (should perform validation and set the dirty flag)
- Initial string value, set to same string value (should not perform validation and not set the dirty flag)
- Initial string value, set to empty value (should perform validation and set the dirty flag)
And if you have validation code, you will have more scenarios to test valid and invalid values. But this is enough to give you the general idea.
So now let's generate the unit test for the LastName property using the tools provided in Visual Studio (Professional Edition or above).
Generate the Unit Test
The following are the steps for generating the unit test for a particular property procedure or method:
1) Open the Code Editor for the code you want to test.
2) Right-click and select Create Unit Tests... from the context menu.
The following dialog will appear:
3) Select the properties, methods, or constructors you wish to test.
If you were in a specific property procedure or method, that procedure or method is automatically checked in this dialog. You can select to generate tests for any properties, methods, or constructors using this dialog.
Notice the Output project combobox on this dialog. Using this combobox, you can create a new C# unit test, new VB unit test, or select any existing unit test project if you already have some in your solution. This allows you to add unit tests to existing unit testing projects at any point in the development process.
4) Click OK.
5) If you are creating a new C# or VB project, enter the project name and click Create.
6) The test project is then added to your Solution Explorer.
If you created a VB test project, the result is the same, but with a CustomerTest.vb file within the BoTest project.
View the Generated Code
Visual Studio automatically creates an first cut of your unit testing code.
In C#:
Double-click on CustomerTest.cs to view the code.
/// <summary>
///A test for LastName
///</summary>
[TestMethod()]
public void LastNameTest()
{
Customer target = new Customer(); // TODO: Initialize to an appropriate value
string expected = string.Empty; // TODO: Initialize to an appropriate value
string actual;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}
In VB:
Double-click on CustomerTest.vb to view the code.
'''<summary>
'''A test for LastName
'''</summary>
<TestMethod()> _
Public Sub LastNameTest()
Dim target As Customer = New Customer ' TODO: Initialize to an appropriate value
Dim expected As String = String.Empty ' TODO: Initialize to an appropriate value
Dim actual As String
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual)
Assert.Inconclusive("Verify the correctness of this test method.")
End Sub
The TestMethod attribute defines this method as a unit test. It is then picked up by the other unit testing tools within Visual Studio.
The generated code includes some ToDo notes, defining where you should change the code.
The Assert class is a key aspect of your unit tests. It provides many methods that you can use to assert whether your test produced expected results.
The generated code uses two of the Assert class methods. The AreEqual method determines whether the expected and actual values are equal. If not, it fails the test.
The Inconclusive method always causes the test to fail. This is added to all generated code to ensure that the test will fail until you modify the test with valid values.
At this point, you can try to execute this unit test, or update it first and then execute it.
See the following posts for more information:
Enjoy!
If you are using XML in a WinForms application you may find the need to display the XML data in a DataGridView.
Let's take this XML:
<states>
<state name="California">
<abbreviation>CA</abbreviation>
<year>1850</year>
<governor>Schwarzenegger</governor>
</state>
<state name="Wisconsin">
<abbreviation>WI</abbreviation>
<year>1848</year>
<governor>Doyle</governor>
</state>
</states>
Displaying XML in a DataGridView sounds easy, but if you just set the DataGridView DataSource to the XML data, you will get something like this:
Notice how it has lots of yuck in it: attribute details, node properties, and so on for every node in the XML. So how do you get something more like this:
The trick is to use anonymous types.
The code is provided here in VB and C# and then described in detail below.
NOTE: Be sure to set a reference to System.Core and System.Xml.Linq
In C#:
XElement statesXml = XElement.Parse("<states>" +
"<state name='California'>" +
"<abbreviation>CA</abbreviation>" +
"<year>1850</year>" +
"<governor>Schwarzenegger</governor>" +
"</state>" +
"<state name='Wisconsin'>" +
"<abbreviation>WI</abbreviation>" +
"<year>1848</year>" +
"<governor>Doyle</governor>" +
"</state>" +
"</states>");
var query = from st in statesXml.Descendants("state")
select new
{
Name = st.Attribute("name").Value,
Abbrev = st.Element("abbreviation").Value,
Year = st.Element("year").Value,
Governor = st.Element("governor").Value
};
DataGridView1.DataSource = query.ToList();
In VB:
Dim statesXml As XElement = _
<states>
<state name="California">
<abbreviation>CA</abbreviation>
<year>1850</year>
<governor>Schwarzenegger</governor>
</state>
<state name="Wisconsin">
<abbreviation>WI</abbreviation>
<year>1848</year>
<governor>Doyle</governor>
</state>
</states>
Dim query = From st In statesXml...<state> _
Select New With { _
.Name = st.@name, _
.Abbrev = st.<abbreviation>.Value, _
.Year = st.<year>.Value, _
.Governor = st.<governor>.Value}
DataGridView1.DataSource = query.ToList
The first part of this code builds the XML. The C# code uses the XElement.Parse function to build the XML; VB uses XML literals. This part of the code is not necessary if you are reading the XML from another source, such as a file.
The second part of the code leverages Linq to XML to process the set of state XML elements. For each element, it uses the Select New syntax to create an anonymous type. The syntax defines an unnamed (anonymous) type with properties Name, Abbrev, Year, and Governor.
[To view an overview of anonymous types, start here.]
The last line converts the results of the query to a generic list and assigns it to the DataSource property of the DataGridView. Visual Studio uses the anonymous type property names as the text for the column titles and populates the rows with each state element.
Use this technique any time you have XML that you want to display in a DataGridView.
Enjoy!
Lambda expressions can be assigned to a delegate variable. The lambda expression is then executed when the delegate is called.
[To begin with an overview of lambda expressions, start here.]
In the following example, a lambda expression is assigned to a variable (f).
In C#:
Func<int,string> f = x => (x + x).ToString();
Debug.WriteLine(f(5));
Debug.WriteLine(f(10));
In VB:
Dim f = Function(x as Integer) (x + x).ToString()
Debug.WriteLine(f(5))
Debug.WriteLine(f(10))
The lambda expression in this example is a Func<int, string> [Func(Of Integer, String) in VB] meaning it takes one integer input parameter and returns a string. The lambda expression simply takes the input value, adds it to itself, and returns the result as a string.
[For more information on Func delegates, see this post.]
When the expression is defined, it is not executed. It is not executed until it is called.
The first Debug.WriteLine statement calls the lambda expression, passing in a 5. The result displays "10" in the debug window.
The second Debug.WriteLine statement calls the lambda expression again, passing in a 10. The result displays "20" in the debug window.
Lambda expressions are executed when they are called, not when they are constructed. This is important to consider, especially when the lambda expression contains local variables.
Local variables used in a lambda expression are “captured” or “lifted”. The variable value used is the value at execution time. The variable lifetime extends to the lifetime of the delegate.
The following code updates the original example to use a local variable.
In C#:
int y = 0;
Func<int,string> f = x => (x + y).ToString();
y = 10;
Debug.WriteLine(f(5));
In VB:
Dim y As Integer = 0
Dim f = Function(x) (x + y).ToString()
y = 10
Debug.WriteLine(f(5))
The lambda expression in this example takes the input value, adds it to the current value of the local variable (y), and returns the result as a string.
The Debug.WriteLine statement in this example calls the lambda expression, passing in a 5. At the point of executing the Debug statement, y is 10, so the result displays "15" in the debug window.
For another example of using lambda expressions with local variables, see this post.
Enjoy!
The same several questions often come up in the forums regarding the basics of building a user control with VB.NET or C#. The goal of this post is to answer those questions.
There are three basic types of WinForms user controls that you can create:
- Extended control
- Composite control
- Custom control
Each of these are discussed below.
Extended Control
Use an extended control whenever you want to extend the behavior of one particular control. Say for example that you want to build your own TextBox that only allows alphabetic characters. Or you want to build your own Button that has specific behaviors.
The best thing about extended controls is that you automatically get all of the intrinsic behavior of the original control. All you need to do is add any extended functionality that you want for the control.
To create this type of user control, follow these steps.
In C#:
- Right-click on the project and select Add | User Control.
- Name the user control and click Add.
- Do NOT put anything on the design surface of the user control.
- Open the code behind file (UserControl1.cs).
- Modify the code to inherit from the control you are extending.
public partial class UserControl1 : TextBox
This example extends a TextBox control. Replace TextBox above with whatever WinForm control you want to extend.
NOTE: After changing the Inherits statement, you may get an error on a line that begins with this.AutoScaleMode. If so, just delete that line. It is no longer needed now that you are inheriting from a control other than UserControl.
In VB:
- Right-click on the project and select Add | User Control.
- Name the user control and click Add.
- Do NOT put anything on the design surface of the user control.
- Select the project in the Solution Explorer and click the Show All Files button in the Solution Explorer toolbar.
- Click on the plus sign to the left of the user control in Solution Explorer to view the nodes below it.
- Double-click on the designer file to open it. (UserControl1.Designer.vb)
- Modify the code to inherit from the control you are extending.
Partial Class UserControl1
Inherits TextBox
This example extends a TextBox control. Replace TextBox above with whatever WinForm control you want to extend.
NOTE: After changing the Inherits statement, you may get an error on a line that begins with Me.AutoScaleMode. If so, just delete that line. It is no longer needed now that you are inheriting from a control other than UserControl.
Then add any additional behavior or functionality that you want in the extended control.
The MSDN documentation provides an example of building an extended control using VB.NET here and C# here.
Composite Control
A composite controls is so named because it is composed of multiple controls. Use this type of user control any time you want to build a control that combines a set of other controls. For example, you want to build a search control that contains a Textbox for entry of the text to search, Button to click to perform the search, and ListBox for the results of the search.
The best thing about composite controls is that you can easily add any controls to the composite control using the design surface.
However, because it is a composite control that inherits from UserControl, no properties, methods, or events from any of the underlying controls are exposed. This means that when you use the user control, you cannot set the Text property of the TextBox or respond to Button click events without writing some code.
You basically have to write code to expose any property, method, or event that you need.
To create this type of user control, follow these steps:
- Right-click on the project and select Add | User Control.
- Name the user control and click Add.
- Put the controls you want onto the design surface.
- Expose any properties, methods, or events that you need.
An example of generating events from a user control is provided in this post.
The MSDN documentation provides an example of building a composite control using VB.NET here and C# here.
Custom Control
A custom control is a control you create from the ground up including all control drawing and any unique behavior.
To build a custom control, inherit from Control to get the basic behavior of a WinForms control. Then use the OnPaint event to render your custom user interface.
The MSDN documentation provides an example of building a simple custom control here.
Enjoy!
More Posts
Next page »