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.