November 2009 - Posts
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!