Everybody likes buttons—everybody needs buttons—so it's not surprising that the premier of buttons and other visual controls in Microsoft® Silverlight™ 2.0 Beta 1 was the subject of headlines. Not everybody's Silverlight application will need to manipulate the DOM, or use local storage, or fetch data from a web service. But if you're building such an application, you may be more excited about these and other critical, but less visible, technologies in Beta 1. In this article we'll get right into nine Silverlight 2 Beta 1 technologies that belong in every developer's bag of tricks. Then we'll zoom in a little closer with a demo application that's built on a sampling of these features.
1. HTML DOM Integration
For Silverlight applications that are deployed as a control or element of a larger Ajax application, tight integration between Silverlight and the HTML DOM means no bump between Silverlight and its hosting page, and a better user experience. The number one use case for DOM integration before Beta 1 may well have been gaining access to HTML controls, but now that we've got Silverlight native equivalents we can move on to enhance page integration.
In Beta 1, you can do virtually everything related to the DOM from managed code. You can:
The key to making this work is the HtmlPage class in System.Windows.Browser. HtmlPage has a Document member that you can use to get access to DOM elements using functions such as GetElementById(). Once you've got the element, you can set its attributes or hook in events using the HtmlElement.AttachEvent() API.
2. JSON Serialization
Silverlight 2 Beta 1 applications can use the new .NET 3.5 DataContractJsonSerializer to serialize .NET objects to JSON. In our example project, below, we'll use this class to serialize our object for local storage. Since the representation is JSON, you can also use this technology to deserialize JSON strings from a web server or web service into C# objects on the client.
As we'll see in the sample, you need to set DataContract and DataMember attributes to mark a class as serializable and call out the members to be serialized (and optionally set the serialization name). These attributes give you control over how your object is serialized, allowing you, for example, to safely serialize a class that contains both an encrypted string (serialize) and its decrypted representation (don't serialize).
3. Styles and Templates
In the Silverlight control model there's complete separation between control functionality and control appearance. The only thing that's intrinsic about a button is its functionality; that is, it fires a click event when clicked. The appearance of the button and how that appearance changes for various states can be lightly or fully customized using Styles and ControlTemplates. It's important that these customizations are expressed and applied in XAML, not in code, keeping them in the realm of the designer and not the developer.
Styles are set through Style resources that are targeted at specific control types and allow you to control the visible properties of that type:
<Style TargetType="TextBlock" x:Key="TextBlockStyle">
< Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="5"/>
</Style>
You can then apply the style ("TextBlockStyle") to TextBlocks within the application.
Templates provide much deeper customization, giving you control over the XAML that defines the control's visible content. We can't go into any depth on control templates here, but Scott Guthrie's blog entry on the topic is a great place to learn more.
4. Local Storage
Isolated Storage gives your Silverlight application access to storage resources on the client. It's "isolated" because the store is partitioned per application, meaning no other applications can access files in your storage. On the other hand, your application (application defined by its URL) always gets the same storage, even if it's run in a different browser.
Prior to Beta 1, Silverlight local storage was cleared along with the browser cache. In Beta 1, local storage is independent of the cache, but the virtual file system presented by Isolated Storage still lives in the user's file system and can be deleted at any time. That makes local storage suitable for application settings, for caching, and for other locally-relevant but expendable data.
Beta 1 also lowers the per-domain quota of local storage to 100 KB (down from 1 MB). However, you can now ask the user for permission to increase your quota (the user can still say no) using the TryIncreaseQuotaTo() API.
5. Databinding
Silverlight now supports two-way, one-way, and one-time databinding between visible controls and classes in code that represent application logic. One-way and one-time databinding are for read-only controls. Two way databinding lets the user make changes that automatically update classes in the model. You can also bind visual controls to static XAML resources, and indirectly to other visual controls, but I'll focus on making the binding in C# code here.
In XAML, you mark a control property for binding like so:
<TextBox x:Name="FontFamily" Text="{Binding FontFamily, Mode=TwoWay}"
Style="{StaticResource TextBoxStyle}" Grid.Row="0" Grid.Column="1"/>
This statement links the Text property of the FontFamily TextBox to an object's FontFamily member.
To be used for binding, an object must implement INotifyPropertyChanged:
public class Prefs : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
And then you can bind an instance of Prefs to the textbox by setting the textbox's DataContext to that instance:
FontFamily.DataContext = prefs;
We'll explore this a little further in the sample application later in the article.
6. Generics
One of the best parts of Silverlight 2 development is access to a substantial subset of the .NET 3.5 Framework and the CLR. The CLR supports generics, classes and methods that are written to work with types that aren't known until runtime. Generics allow you to write flexible classes and methods without losing the benefits of strong typing.
The most obvious application of generics is generic collection classes, where a common collection class (e.g., List) can be used to hold items of different types. The Silverlight .NET framework includes a full complement of generic collection classes that you can use in your applications.
7. ItemsControls
An ItemsControl is a UI element that displays a list of data objects. Doesn't sound like much, but the power of the ItemsControl is the flexibility that this simple mission provides. The ItemsControl can display the items of any enumerable collection, and it can display it in any fashion you like, all set up through the declarative magic of templates.
Generally, you'll use an ItemsControl by setting its ItemsSource property to a collection of objects that you manage in code. A simple way to control how each item looks is to set the ItemsControl's DisplayMemberPath to the item property to display. But for complete customization, you can instead set the ItemControl's ItemTemplate property to a DataTemplate resource. The DataTemplate functions much like a ControlTemplate, but instead of being applied to a control the DataTemplate is instead bound to each item in the collection for display:
<DataTemplate x:Key="ItemsControlDataTemplate">
<TextBlock Text="{Binding TextMember}"/>
</DataTemplate >
In addition to templating each data item, you can also supply an ItemsPanelTemplate which defines the item layout in the ItemControl.
8. Layout Management
Along with its new controls, Beta 1 provides flexible layout management derived from the layout management system of WPF. To position controls within your application, you use one of three types of layout controls:
In the sample application below, we used a Grid panel as the container for the TextBoxes and other controls.
9. WebClient
Silverlight applications can consume data from REST web services using the WebClient object. In fact, if you want to get media as a stream or need to process plain text or xml data, WebClient is your best choice as it can get anything you need from a web server. The coolest thing about WebClient is that it can operate asynchronously, pulling down data in the background without blocking the UI thread. When the download is complete, WebClient fires an event to let you know that the data is available.
Putting It in Practice
Now it is time to see some of these technologies in action. I built an example "HTML font preferences" application that lets you make some minor tweaks to the DOM of its containing page, and then uses isolated storage to persist those tweaks between browser runs. Along the way, this example also makes use of JSON serialization, control styles, grid layouts, and two-way databinding.
![]() | |
| Figure 1. The sample Silverlight application sits at the top of an HTML page and lets you make changes to the body font that persist between runs. |
The font prefs application is hosted at the top of a web page, the rest of which is filled with sample text and images. It includes two text box controls and a check box that let you specify some font attributes. When you click the "Apply" button, those font style attributes are applied to the HTML body. When you click "Save," the font family, font size, and font weight preferences are serialized and saved to isolated storage. From that point on, the page uses the specified styles whenever it's loaded.
![]() | |
| Figure 2. The font prefs application up close. |
With a Silverlight project, Visual Studio automatically generates a test HTML page to host your Silverlight control for debugging. I used a copy of that generated HTML as a starting point for my page. Then I moved the container for the Silverlight control to the top of the page and added some test content to the body. Finally, I added the HTML host page to the project and set up Visual Studio to launch that page rather than the one it generates.
For our sample, we want to store the user's preferences for font family, font size, and font weight. We'll get those from the user using three controls, two TextBoxes and one CheckBox. We define these controls, along with the Apply and Save buttons, in the application's main XAML file, Page.xaml. Here is the first TextBlock and TextBox pair, used for Font Family:
<TextBlock x:Name="FontFamilyLabel" Text="Font Family" Style="{StaticResource
TextBlockStyle}" Grid.Row="0" Grid.Column="0"/>
<TextBox x:Name="FontFamily" Text="{Binding FontFamily, Mode=TwoWay}"
Style="{StaticResource TextBoxStyle}" Grid.Row="0" Grid.Column="1"/>
There are a few items to note in this little snippet. First, all of these items are contained within a grid layout panel, and we've set the position for our controls using the Grid.Row and Grid.Column attributes. Second, we've applied separate style resources to the TextBlock and TextBox. These styles set FontSize and Margin properties, and are defined at the application level (in app.xaml) so they can be made available to all controls.
<Application.Resources>
<Style TargetType="TextBox" x:Key="TextBoxStyle">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="5"/>
</Style>
<Style TargetType="TextBlock" x:Key="TextBlockStyle">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Application.Resources>
Finally, we've prepared the TextBox for databinding by specifying that its Text property will be bound to the FontFamily property of the object we'll use for a data context. We've specified two-way binding because we need the user to be able to modify our preferences object (the default binding mode is OneWay).
Now we need to build that preferences object. This will be an instance of a class that has the data properties we need (FontFamily, FontSize, and Bold) and which implements INotifyPropertyChanged. Here is the Prefs class, with only one member property shown for clarity:
public class Prefs : INotifyPropertyChanged
{
public string FontFamily
{
Get { return _fontFamily; }
set
{
_fontFamily = value;
OnPropertyChanged("FontFamily");
}
}
private string _fontFamily = "Times";
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
If (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When FontFamily is set, it fires a PropertyChanged event which notifies bound controls of the change.
Now we have to hook up the prefs object with the controls. This is done by setting the DataContext for these controls to our prefs instance. Because DataContexts are inherited, we can set the DataContext of the containing grid instead of setting it for each control:
LayoutRoot.DataContext = prefs;
This binds all of the controls within the LayoutRoot grid to our prefs object.
When the user clicks the Apply button, we want to take the font style information in our prefs object and apply it to the DOM of the containing HTML page. The HtmlPage object makes this pretty simple. Here is the ApplyPrefs function that is called when the button is clicked (from Page.xaml):
private void ApplyPrefs()
{
HtmlElement body = HtmlPage.Document.GetElementsByTagName("body")[0];
body.SetStyleAttribute("fontFamily", prefs.FontFamily);
body.SetStyleAttribute("fontSize", prefs.FontSize);
body.SetStyleAttribute("fontWeight", prefs.Bold ? "bold" : "normal");
}
We find the body element using GetElementsByTagName and then apply the styles to the element using SetStyleAttribute.
When the user clicks the Save button, we'll use what we've learned about JSON serialization and local storage to persist these preferences to a file on the client. Recall that we can't serialize the Prefs calls using the data contract serializer until we set the data contract attributes. The Prefs class itself gets a DataContract attribute, and each property that we want to serialize gets a DataMember attribute:
[DataContract]
public class Prefs : INotifyPropertyChanged
{
[DataMember]
public string FontFamily
Now we're ready to take a look at the function we use to actually store the prefs object.
private void StorePrefs()
{
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream stream = file.CreateFile(storageLocation);
DataContractJsonSerializer serializer = new
DataContractJsonSerializer(typeof(Prefs));
serializer.WriteObject(stream, prefs);
stream.Flush();
stream.Close();
}
Here, storageLocation is a string, the name of the file we want to create in the isolated storage file system. We create the file, and then use the DataContractJsonSerializer to serialize the object as JSON to the file stream.
Finally, we'll add a little more to Page_Loaded so that the HTML page gets the last saved font style every time you load the page:
void Page_Loaded(object sender, RoutedEventArgs e)
{
LoadPrefs();
ApplyPrefs();
LayoutRoot.DataContext = prefs;
}
LoadPrefs() is the counterpart to StorePrefs, a member function that deserializes the prefs object from local storage.
The nine technologies we've seen here may not make many headlines, but they help to make Silverlight an increasingly rich and functional client platform. Some, like layout panels, will be used in every Silverlight app. Others, like WebClient, may be less frequently used, but when you need it, there's no substitute. Don't forget these features as you build your next Silverlight application.
* This article was commissioned by and prepared for Microsoft Corporation. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.