I've been working on the middleware and the UI for Wally McClure's Azure "TimedTweet" application that sends scheduled twitter tweets, along
with David Silverlight (no, they didn't name it for him) and a couple of other developers. It's given me
a chance to really get into some of the new features that Silverlight 3 offers.
In Silverlight 3, if you choose the new Silverlight Navigation Application, you get
navigation functionality "out of the box". What I have done here is
to add an additional page, "Profile" that has a DataForm which is bound
to a UserSettings Class that enables users to enter their Username, Password,
Email and Phone and have them stored in Isolated Storage so that these settings
will be read automatically each time the application is loaded.
Silverlight 3 introduces a set of new controls that are specifically aimed at making
the creation of data-centric RIAs easier. These include DataGrid, DataForm, DataPager,
FieldLabel, DescriptionViewer, ErrorSummary, and ChildWindow. The RIA Services May 2009 Preview is out.
I started out with a UserSettings class:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SLUserSettings
{
[Bindable(true, BindingDirection.TwoWay)]
public class UserSettings
{
[Required
(ErrorMessage="UserName is required.")]
[Bindable(true, BindingDirection.TwoWay)]
public string UserName
{
get;
set;
}
[Required(ErrorMessage
= "Password is required.")]
[Bindable(true, BindingDirection.TwoWay)]
public string Password { get; set; }
[Required(ErrorMessage
= "Email is required.")]
[RegularExpression( @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
[Bindable(true, BindingDirection.TwoWay)]
public string Email { get; set; }
[Required(ErrorMessage
= "Phone Number is required.")]
[Bindable(true, BindingDirection.TwoWay)]
public string Phone { get; set; }
public UserSettings()
{
}
public UserSettings(string userName, string password, string email, string phone)
{
this.UserName = userName;
this.Password = password;
this.Email = email;
this.Phone = phone;
}
}
}
Because of the System.ComponentModel.DataAnnotations namespace, we are able to apply
Validation Attributes directly on the public fields of our class, and even include
appropriate error messages. The Silverlight controls such as DataForm pick these
up automatically; they even take care of displaying the validation errors. The
developer doesn't have to write a single line of code (although you can if you
want to). In this case I've made all the fields [Required] and for the Email,
I've provided a [RegularExpression] validator as well. Note also that each field
carries the [Bindable(true, BindingDirection
.TwoWay)] attribute. This will enable my Dataform to handle not only automatic display
of each field name and value, but also for it to be enabled to edit and save
the form data. I don't have to create any form fields at all. That is a whole
bunch of code that I never enjoyed writing, and now I don't have to do it anymore!
Let's switch over to the App.xaml.cs class and see how that got wired up:
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml;
namespace SLUserSettings
{
public partial class App : Application
{
public const string FormDataDirectory = "UserSettings";
public const string UserSettingsFileName = "settings.xml";
public static bool NeedsProfileData = false;
public static UserSettings MyUserSettings = new UserSettings("", "", "", "");
public static string UserName;
public static string Password;
public static string Email;
public static string Phone;
public App()
{
this.Startup += this.Application_Startup;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
LoadUserSettings();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
}
public static void LoadUserSettings()
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
string filePath = System.IO.Path.Combine(FormDataDirectory, UserSettingsFileName);
//Check to see if file exists before proceeding
if (store.FileExists(filePath))
{
using (XmlReader xr = XmlReader.Create(
store.OpenFile(filePath,
FileMode.Open, FileAccess.Read)))
{
DataContractSerializer
ser = new DataContractSerializer(typeof(UserSettings));
MyUserSettings
= (UserSettings)ser.ReadObject(xr);
UserName
= MyUserSettings.UserName;
Password
= MyUserSettings.Password;
Email
= MyUserSettings.Email;
Phone
= MyUserSettings.Phone;
xr.Close();
NeedsProfileData
= false;
}
if (UserName.Length == 0)
{
NeedsProfileData
= true;
}
}
else
{
NeedsProfileData
= true;
}
}
}
public static void SaveUserSettings()
{
if(MyUserSettings.UserName.Length==0) NeedsProfileData = true;
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.CreateDirectory(FormDataDirectory);
IsolatedStorageFileStream
fileStream = store.CreateFile(System.IO.Path.Combine(FormDataDirectory, UserSettingsFileName));
DataContractSerializer
ser = new DataContractSerializer(typeof(UserSettings));
ser.WriteObject(fileStream,
MyUserSettings);
fileStream.Close();
}
}
catch
{
throw;
}
}
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (!System.Diagnostics.Debugger.IsAttached)
{
// e.Handled = true;
ChildWindow
ErrorWin = new ErrorWindow(e.ExceptionObject);
ErrorWin.Show();
}
}
}
}
The logic mostly just attempts to load the UserSettings from IsolatedStorage, using
the DataContractSerializer to give us an instance of the UserSettings class,
and if it isn't there or the data is not valid, it sets the boolean "NeedsProfileData"
field. This is checked later in the application to direct the user to the Profile
Page:
void
MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (App.NeedsProfileData)
{
this.Frame.Navigate(new Uri("/Views/Profile.xaml", UriKind.Relative));
return;
}
}
Now let's have a look at how the DataForm on Profile.xaml works. Here is the Xaml.
As you can see, it is quite simple:
<
navigation:Page xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm" x:Class="SLUserSettings.Views.Profile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
Title="Profile Page">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<TextBlock Text="Profile" Style="{StaticResource HeaderTextStyle}"/>
<StackPanel Style="{StaticResource ContentTextPanelStyle}" Orientation="Vertical">
<TextBlock Text="Configure User Settings" Style="{StaticResource ContentTextStyle}"/>
<my:DataForm Header="User Settings" Width="400" Height="350" x:Name="Form1" DeletingItem="Form1_DeletingItem" ItemEditEnded="Form1_ItemEditEnded">
</my:DataForm>
</StackPanel>
</StackPanel>
</Grid>
</navigation:Page>
And here is all that is needed in the codebehind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;
namespace SLUserSettings.Views
{
public partial class Profile : Page
{
public Profile()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<UserSettings>
settings = new List<UserSettings>();
settings.Add(App.MyUserSettings);
this.Form1.ItemsSource = settings;
}
private void Form1_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
{
App.SaveUserSettings();
MessageBox.Show("Settings Saved.");
}
private void Form1_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
App.MyUserSettings.Email
= "";
App.MyUserSettings.UserName
= "";
App.MyUserSettings.Phone
= "";
App.MyUserSettings.Password
= "";
App.SaveUserSettings();
App.NeedsProfileData
= true;
}
}
}
You can download the Silverlight 3 Visual Studio Solution here.