Introduction to DataBinding in Silverlight
DataBinding is a link between a data source and a user interface. The data source provides data to the user interface. The data in the user interface may be changed by the user which will be updated to the source and could be saved back to the database.
The data source could be business objects, collections, database tables or any other form of data that support data binding.
In this chapter, we will use a simple class called "Address" with the following properties:
1. Name
2. Address1
3. Address2
4. City
5. State
6. Zip code
Let us create a Silverlight control which accepts user's name and address. You can create a xaml control which has the following TextBlock elements:
1. Name
2. Address1
3. Address2
4. City
5. State
6. Zip code
Here is the XAML which defines a grid and places the appropraite controls for our sample:
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Name" Grid.Row="0" Grid.Column="0"></TextBlock>
<TextBlock Text="Address 1" Grid.Row="1" Grid.Column="0"></TextBlock>
<TextBlock Text="Address 2" Grid.Row="2" Grid.Column="0"></TextBlock>
<TextBlock Text="City" Grid.Row="3" Grid.Column="0"></TextBlock>
<TextBlock Text="State" Grid.Row="4" Grid.Column="0"></TextBlock>
<TextBlock Text="Zipcode" Grid.Row="5" Grid.Column="0"></TextBlock>
<TextBox x:Name="txtName" Text="{Binding Name, Mode=TwoWay}" Grid.Row="0" Grid.Column="1"></TextBox>
<TextBox x:Name="txtAddress1" Text="{Binding Address1, Mode=TwoWay}" Grid.Row="1" Grid.Column="1"></TextBox>
<TextBox x:Name="txtAddress2" Text="{Binding Address2, Mode=TwoWay}" Grid.Row="2" Grid.Column="1"></TextBox>
<TextBox x:Name="txtCity" Text="{Binding City, Mode=TwoWay}" Grid.Row="3" Grid.Column="1"></TextBox>
<TextBox x:Name="txtState" Text="{Binding State, Mode=TwoWay}" Grid.Row="4" Grid.Column="1"></TextBox>
<TextBox x:Name="txtZipcode" Text="{Binding Zipcode, Mode=TwoWay}" Grid.Row="5" Grid.Column="1"></TextBox>
<Button Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Width="50" Content="Save" x:Name="btnSave" Click="btnSave_Click"></Button>
</Grid>
The above XAML defines a grid with 7 rows and 2 columns. The controls are places in appropriate rows and columns using the Grid.Row and Grid.Column property of individual controls. When placed in a web page, our Silverlight control will look like this:
Now create a class called "Address" which has properties representing various fields we need. See the sample code for the "Address" class:
public class Address
{
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
}
Go to the code behind file of the xaml class and create an instance of the Address class as shown below:
Address address;
In the constructor of the XAML page class, initialize the Address class and bind it to the UI elements as shown below:
address = new Address();
txtName.DataContext = address;
txtAddress1.DataContext = address;
txtAddress2.DataContext = address;
txtCity.DataContext = address;
txtState.DataContext = address;
txtZipcode.DataContext = address;
In the above code, we are setting the DataContext property of each UI control to our "Address" object. But how do the control know which property of the address object to be used ? This is handled in the XAML. Take a look at the "txtAddress1" control. You can see that the property "Text" is set as shown below:
Text="{Binding Address1, Mode=TwoWay}"
The above line defines that we are using data binding for the "Text" property of this control, and it will use the property "Address1" of whatever object it will be bound to. Also, it states that the Mode is "TwoWay" which means the value will be read from the object and set to the "Text" property and also when the value is changed in the textbox control, it is saved back to the data source. In our case, we are binding our Address object to the textbox control. When loaded, it will display the default value from our object in the textbox. When the value is changed by the user, the datasource object will be updated with the new value from text box.
Typically, when we add a new address, the object will be initialized to empty values and the textboxes will be empty. When we edit an existing address, the object will have values populated from database and it will be displayed in the UI controls using the databinding. When values are modified by the user, the datasource object will be automatically modified. All we have to do is, save the modified datasource object in to the database.
Silverlight Tutorial Part 5: Using the ListBox and DataBinding to Display List Data
This is part five of eight tutorials that walk through how to build a simple Digg client application using Silverlight 2. These tutorials are intended to be read in-order, and help explain some of the core programming concepts of Silverlight. Bookmark my Silverlight 2 Reference page for more of Silverlight posts and content.
<Download Code> Click here to download a completed version of this Digg client sample. </Download Code>
Displaying our Digg Stories using the ListBox and DataBinding
Previously we've been using the DataGrid control to display our Digg stories. This works great when we want to display the content in a column format. For our Digg application, though, we probably want to tweak the appearance a little more and have it look less like a DataGrid of stories and more like a List of them. The good news is that this easy - and it doesn't require us to change any of our application code to accomplish this.
We'll start by replacing our DataGrid control with a <ListBox> control. We'll keep the control name the same as before ("StoriesList"):
When we run our application again and search for stories, the ListBox will display the following results:
You might be wondering - why is each item "DiggSample.DiggStory"? The reason for this is because we are binding DiggStory objects to the ListBox (and the default behavior is to call ToString() on them). If we want to display the "Title" property of the DiggStory object instead, we can set the "DisplayMemberPath" property on the ListBox:
When we do this the Title will be what is displayed in the ListBox:
If we want to show more than one value at a time, or customize the layout of each item more, we can override the ListBox control's ItemTemplate and supply a custom DataTemplate. Within this DataTemplate we can customize how each DiggStory object is displayed.
For example, we could display both the DiggStory Title and NumDiggs value using a DataTemplate like below.
We can databind any public properties we want from our DiggStory object within the DataTemplate. Notice above how we are using the {Binding Path=PropertyName} syntax to accomplish this with the two TextBlock controls.
With the above DataTemplate in place, our ListBox will now display its items like below:
Let's then go one step further and change our DataTemplate to the one below. This DataTemplate uses two StackPanels - one to stack row items horizontally, and one to stack some textblocks together vertically:
The above DataTemplate causes our ListBox to display items like the screen-shot below:
when we define the following Style rules in our App.xaml (note how we are using a LinearGradientBrush to get the nice yellow gradient background on the DiggPanel):
One important thing to notice about our ListBox - even though we have customized what the items in it look like, it still automatically provides support for hover and item selection semantics. This is true both when using the mouse and when using the keyboard (up/down arrow keys, home/end, etc):
The ListBox also supports full flow resizing - and will provide automatic scrolling of our custom content when necessary (notice how the horizontal scroll bar appears as the window gets smaller):
Next Steps
We've now switched our data visualization to be List based, and cleaned up the content listing of it.
Let's now complete the last bits of the functionality behavior in the application - and implement a master/details workflow which allows end-users to drill into the specifics of a story when they select an article from the list. To-do that let's jump to our next tutorial: Using User Controls to Implement Master/Detail Scenarios.
Element Data Binding
Element data binding allows you to bind element properties to each other. In previous versions of Silverlight, this would require more work on the code side because the element would fire its changed method and have that update the necessary elements. Silverlight 3 simplifies this process by performing the task directly in XAML.
In this tutorial, we will show you how to use element data binding for a variety of scenarios.
Element Binding
Element binding is performed in the same manner as Data Binding with one addition: the ElementNameproperty. ElementName defines the name of the binding source element.
The following code snippet shows the basic syntax for element binding. The TextBlock is databound toelement's Value property.
<TextBlock Text="{Binding ElementName=element, Path=Value}" />
Slider and TextBlock Scenario
The TextBlock control can maintain the value of the Slider control. This can be used to inform the user of the selected value. When the user moves the slider, the textblock is refreshed with its value.
<StackPanel Orientation="Horizontal" Margin="5">
<Slider x:Name="slider1" Minimum="1" Maximum="100" Width="100" Margin="5" />
<TextBlock Text="{Binding ElementName=slider1, Path=Value}" Width="100" Margin="5" />
</StackPanel>
Slider and TextBox Scenario
The TextBox and Slider controls can manipulate each other using TwoWay binding. This is useful when you want your users to have multiple ways to enter numerical data.
<StackPanel Orientation="Horizontal" Margin="5">
<Slider x:Name="slider2" Minimum="1" Maximum="100" Width="100" Margin="5" />
<TextBox Text="{Binding ElementName=slider2, Path=Value, Mode=TwoWay}" />
</StackPanel>
Sliders and Image Scenario
Slider controls can be used to manipulate a variety of element properties. The following examples demonstrate how a slider control can resize the image and manipulate its rotation.
<Grid HorizontalAlignment="Left" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Slider x:Name="slider3" Minimum="160" Maximum="640" Value="200" Width="100"
<Image Grid.Row="1"
Width="{Binding ElementName=slider3, Path=Value}"
Height="{Binding ElementName=slider3, Path=Value}"
Source="Autumn Leaves.jpg" />
</Grid>
<Grid HorizontalAlignment="Left" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Slider x:Name="slider4" Minimum="0" Maximum="360" Width="100"
Value="{Binding RotationX, Mode=TwoWay, ElementName=projection}" />
<Image Grid.Row="1" Width="160" Height="120" Source="Autumn Leaves.jpg">
<Image.Projection>
<PlaneProjection x:Name="projection" />
</Image.Projection>
</Image>
</Grid>
Selection Scenario
Selection events are useful for element binding with the TextBlock control to display the currently selected item.
<StackPanel Margin="5" >
<controls:Calendar x:Name="cal" />
<TextBlock
Text="{Binding ElementName=cal, Path=SelectedDate}"
Margin="5" HorizontalAlignment="Center" />
</StackPanel>
<StackPanel Margin="5" Width="150">
<ComboBox x:Name="cb">
<sys:String>Apple</sys:String>
<sys:String>Banana</sys:String>
<sys:String>Orange</sys:String>
</ComboBox>
<TextBlock
Text="{Binding ElementName=cb, Path=SelectedItem}"
Margin="5" HorizontalAlignment="Center" />
</StackPanel>
<StackPanel Margin="5" Orientation="Horizontal">
<ListBox x:Name="lb">
<sys:String>Apple</sys:String>
<sys:String>Banana</sys:String>
<sys:String>Orange</sys:String>
</ListBox>
<TextBlock Text="{Binding ElementName=lb, Path=SelectedItem}" Margin="5" />
</StackPanel>
Conclusion
Element Binding extends Silverlight's capabilities by providing advanced binding among XAML elements. The feature also reduces the amount of code required to connect the elements. As shown in this tutorial, there are several scenarios in which element binding can perform powerful binding in minimal XAML code.
Accessing Data Using Silverlight
In this lesson of the Silverlight tutorial, you will learn...
1.Work with XML data in Silverlight
2.Store data to and retrieve data from isolated storage using Silverlight
This lesson will introduce the how to store and retrieve data using Silverlight.
Storing Data in Code
When working with data programmatically, data is typically stored in memory for later reference, while other operations are carried out, or while the data stored in memory is manipulated. Data may be entered by a user, retrieved from a data source, or created programmatically.
Variables
The most basic means of storing data in memory is through the use of variables. All programming languages support some form of variables. A variable is a named location in memory used to store data. The specifics of how data is stored and managed in memory is particular to a programming language. Strongly-typed programming languages are very strict about what type of data can be stored in a variable. Weakly-typed programming languages are not strict about what type of data can be stored in a variable. Strongly-typed programming languages are generally more efficient than weakly-typed programming languages.
Silverlight may be coded using several languages. Some of the languages used in Silverlight, such as JavaScript, are weakly-typed, while others, such as C#, are strongly-typed. A developer must thoroughly understand how to work with variables in the language that they choose for working with Silverlight. The code snippet below illustrates declaration and initialization of a simple integer type variable in C# named x.
// simple variable. int x = 1;
Collections
Most programming languages support not only storing a single piece of data in a named location in memory but also support storing multiple pieces of data in a named location in memory. A collection is a named location in memory, similar to a variable, that is structured for storing multiple pieces of data. Depending upon the programming language used, multiple types of collections may be supported. For example, C# supports simple arrays, ArrayLists, Stacks, Queues, and HashTables. Each type of collection supported is structured for storing and retrieving data in a different fashion.
Collections are located in the System.Collections namespace. The code snippet below illustrates declaration and initialization of a standard string array named customerNames.
// store customer information in an ArrayList. string[]
customerNames = new string[3]; customerNames[0] = "Shannon Horn";
customerNames[1] = "Benny Madrid"; customerNames[2] = "Edwin Dewees";
Generics
Collections are versatile and simplify data storage in code by making it easier to store and transport multiple data items and objects. However, standard collections have some drawbacks as well. A standard collection, including an ArrayList, Stack, Queue, and HashTable, stores data internally as a simple object. By storing data as a simple object, a standard collection can be used to store any type of data. In a nutshell, in C#, a standard collection is a weakly-typed construct that exists in a strongly-typed language. The internal storage design of a standard collection affects performance and type safety negatively.
When a data item is stored in a standard collection, it must be converted to a simple object type. When a data item stored in a standard collection is removed from the collection, it must be converted from a simple object type to the destination type. The process of converting data to and from a simple object degrades performance. Additionally, a simple object can be converted to any more complex type. However, there is no guarantee that the data stored as a simple object will be correctly represented when removed from the standard collection and converted to a more complex type. For example, a complex object that represents data about a customer may be stored in a collection and then removed from the collection and converted to a string. The code written to perform the operation should compile but will, more than likely, cause errors to occur at runtime. hence, type safety is lost.
In the .NET Framework, generic collections are located in the System.Collections.Generic namespace. A generic collection is a strongly-typed collection and requires that the data type to be used for storage be specified at the time the collection is instantiated.
// a generic collection for storing customer names as
strings. List<string> customerNames = new List<string>(3);
customerNames.Add("Shannon Horn"); customerNames.Add("Benny Madrid");
customerNames.Add("Edwin Dewees");
In the code snippet above, the customerNames generic collection will only store string values however the collection could be configured to store and manage any valid .NET type.
Working with XML
The Extensible Markup Language (XML) was released by the World Wide Web Consortium (W3C - http://www.w3.org) in 1999 as a standardized means of storing and transporting data over the Web. XML has proliferated Web development technologies and virtually all software development platforms support some form of XML interaction. The .NET Framework contains a gamut of classes for working with XML data in the System.Xml namespace.
Silverlight contains a subset of XML functionality in the System.Xml namespace. XML data can be read using the XmlReader class and XML data can be written using the XmlWriter class. Additionally, Silverlight includes a class used to specify configuration settings to be used when writing XML data, the XmlWriterSettings class. If configuration settings are not specified using the XmlWriterSettings class, default configuration settings are used. In the code listing below, the XmlReader class, the XmlWriter class, and the XmlWriterSettings class are used to read in a well-formed XML string, parse it, and write the contents of it to a TextBlock.
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.Xml;
using System.Text; using System.IO; namespace ADXmlReader { public partial class
Page : UserControl { public Page() { InitializeComponent(); } private void
UserControl_Loaded(object sender, RoutedEventArgs e) { // store names as XML.
string names = "<?xml version='1.0' encoding='utf-8' ?><Names><Name>Shannon
Horn</Name><Name>Benny Madrid</Name><Name>Edwin Dewees</Name></Names>"; //
create a reader. XmlReader reader = XmlReader.Create(new StringReader(names));
XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true;
settings.ConformanceLevel = ConformanceLevel.Auto; StringBuilder output = new
StringBuilder(); XmlWriter writer = XmlWriter.Create(output, settings); //
display the names. while (reader.Read()) { if (reader.NodeType ==
XmlNodeType.Text) { writer.WriteString(reader.Value + Environment.NewLine); } }
reader.Close(); writer.Close(); tbNames.Text = output.ToString(); } } }
The results of the code listing above are shown in the figure below.
Language Integrated Query (LINQ)
A major addition to the .NET Framework in version 3.5 is Language Integrated Query (LINQ). Most seasoned developers have mastered or are adequately familiar with the Structured Query Language (SQL). SQL is used to query relational database data. However, in many cases, SQL queries that pull data from a relational database schema are abstracted away from business logic and middle-tier code.
Data may also be stored in formats other than a relational database such as an XML file or a consumed Web service. In each data storage scenario, typically, a specialized language is used to retrieve and query the contained data. Furthermore, data is generally represented at the business logic and code level through objects, arrays, and collections. Developers regularly have to search these constructs by using tailor-made loops.
Many programmers have long requested a language for querying data stored in programming constructs and object oriented mechanisms. SQL is a stable and well-entrenched industry standard. It would be an insurmountable task to attempt to extend SQL so that it could be used to query programming constructs and other data sources. However, Microsoft was determined to make things easier for programmers by creating a standard for querying data stored in multiple data storage mechanisms and coding constructs. The result of their efforts was a new query language that targets data stored in objects and collections, Language Integrated Query (LINQ). LINQ was also extended to be able to query relational data stored in databases, XML data, and other data sources. However, data queried by using LINQ must be stored as objects. If data is queried from a relational data source using LINQ, it must first be represented using an object model. (see footnote)
LINQ is capable of querying any object programmatically that implements the IEnumerable interface. LINQ will present an entirely new programming paradigm to experienced .NET developers but the new functionality and benefits thereof should be quickly enjoyed and adapted by most. To summarize, the primary benefits of using LINQ are a single, consistent language for querying data across any type of data source and a means of doing so that is type safe and supported by the most popular .NET Framework programming languages.
LINQ has grown into an extensive query language. Additionally, in order to make LINQ relevant and a valid solution into the future, Microsoft designed LINQ to be extensible so that it can be extended by Microsoft or third party vendors to support additional data sources. Comprehensive coverage of LINQ is beyond the scope of this course. However, as an example, we will create a simple LINQ query example here using Silverlight. Silverlight supports LINQ using classes in the System.Linq namespace.
The first step in working with LINQ is to identify a data source. In the example created here, we store a list of names in a simple string array. The second step in working with LINQ is to create the LINQ query. A LINQ query uses very similar concepts and vocabulary as an SQL query, however the clauses are presented in a different order. Finally, the third step in working with LINQ is to execute the query. A LINQ query is executed using a foreach loop in C#. The code snippet below illustrates a simple LINQ query against a list of names in a string array. The LINQ query below uses the where clause to filter out all names except those that begin with the letter "E".
// obtain the data source. // list of names. string[]
namesList = new string[3] { "Shannon Horn","Benny Madrid","Edwin Dewees"}; //
create the query. var names = from name in namesList where name.Substring(0, 1)
== "E" select name; // execute the query. foreach (string name in names) {
tbOutput.Text = name; }
The results of the code snippet above are shown in the figure below.
For more information about Language Integrated Query (LINQ), visit the MSDN article entitled Language Integrated Query (LINQ) located at http://msdn.microsoft.com/en-us/library/bb397926.aspx.
Isolated Storage
Due to the security constraints placed upon a Silverlight application (the "sandbox" that it operates in), a Silverlight application cannot write directly to or read directly from the file system on the client's machine. In an effort to allow developers to store some data local to the client, Microsoft designed Silverlight to read data from and write data to a virtual file system called Isolated Storage. Isolated storage is stored inside a User's Application Data directory:
Location of Isolated Storage in Vista
C:\Users\AppData\LocalLow\Microsoft\Silverlight\is
Location of Isolated Storage in Windows XP
C:\Documents and Settings\Local Settings\Application Data\Microsoft\Silverlight\is
Silverlight isolated storage is currently limited to a 100 KB capacity and the classes used to work with isolated storage are located in the System.IO.IsolatedStorage namespace. Isolated Storage is Non-Volatile, and is not cleared by actions such as when the user clears the browser cache or deletes cookies. The code snippet below illustrates saving a user's login credentials to isolated storage in a file named UserCredentials.txt.
// remember the user's credentials for next time. if
(chkRememberMe.IsChecked == true) { using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication()) { using
(IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("UserCredentials.txt", FileMode.Create, isoStore)) {
using (StreamWriter writer = new StreamWriter(isoStream)) {
writer.Write(user.UserName + "|" + user.PasswordHash); } } } }
The code snippet above illustrates using the IsolatedStorageFile class and the IsolatedStorageFileStream class to work with isolated storage. The code snippet below illustrates using the same classes to determine if the UserCredentials.txt file exists in isolated storage and, if it does exist, reads the contents of the file.
// determine if the user's credentials exist in isolated
storage. using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication()) { using
(IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("UserCredentials.txt", FileMode.Open, isoStore)) {
using (StreamReader reader = new StreamReader(isoStream)) { // read the
credentials. string[] sb = reader.ReadLine().Split('|'); // do we have
credentials? if (sb.Length > 0) { // if the credentials exist, parse them out
and authenticate them. user.UserName = sb[0]; user.Password = sb[1]; //
authenticate. svc.AuthenticateUserAsync(user.UserName, user.Password); } } } }
Data Binding
Much of a database oriented application involves reading data from a database and presenting that data to the user. One way to handle filling User Interface elements with data from a database is to simply assign values through code. The code snippet below shows how this might be done...
// assume we have an "Athlete" class as follows: public class
AthleteDisplayInfo { public int AthleteId { get; set; } public string FirstName
{get; set;} public string LastName { get; set; } } // we then retrieve an
athlete from the database AthleteDisplayInfo athlete = e.Result; // we can then
fill UI elements, such as textboxes txtFirstName.Text = athlete.FirstName;
txtLastName.Text = athlete.LastName; // and also get back the values after the
user updates... athlete.FirstName = txtFirstName.Text; athlete.LastName =
txtLastName.Text;
The method shown above is quite adequate for filling UI elements with data values, but it does not allow for separation of User Interface from Business Logic Classes. Without separating our user interface from our business logic code, it will be more difficult to test the application, and have teams of designers and developers work cooperatively on the same project.
This is where data binding comes in. Using data binding, we can declaratively assign values to UI elements instead of using code. Furthermore, the data binding will automatically synchronize changes to the data source to the UI elements.To implement data binding, we use a special syntax in XAML, inside any attribute value: "{Binding PropertyName, Mode=OneWay}" - where "PropertyName" is the property of a data source class, and Mode is either OneTime, OneWay, or TwoWay.
Consider this example:
<TextBox x:Name="txtLastName" Text="{Binding LastName,
Mode=OneWay}" />
The XAML above will automatically fill the "Text" property of txtLastName when databinding occurs. We can tell UI elements what their binding source is by using the DataContext property. The DataContext property can be assigned to a Container Control, such as a Canvas or Grid, and all child controls within that container will receive their bound data from that DataContext. For example, if txtLastName from the example above exists within a Canvas container named "LayoutRoot", then we can assign a business logic class to LayoutRoot.DataContext:
LayoutRoot.DataContext = athlete;
The assignment to DataContext above would cause txtLastName to show the value of athlete.LastName in its Text property.
Data Binding Modes
When you are specifying the Mode for data binding, you have three choices:
OneTime: Updates the target property when the binding is created.
OneWay: Updates the target property when the binding is created. Changes to the source object can also propogate to the target.
TwoWay: Updates either the target or the source object when either change. When the binding is created, the target property is updated from the source.
If you have a read-only UI element where the source data does not change, you might consider using OneTime mode data binding. If you have a read-only UI element, and the user may be selecting different records at times (causing the source data to change), you might consider using OneWay databinding. TwoWay databinding is handy in Master/Details scenarios, where a DataGrid can be linked to controls in a "Detail" section of the screen.
Accessing Data Using Silverlight Conclusion
Lab: Accessing Data In Silverlight
In this lab, you will extend the athlete management application login dialog by providing the user the option to save their credentials and automatically login on future visits. The user credentials will be stored in isolated storage.
Store User Credentials in Isolated Storage
30 45
In this exercise, you will store user credentials in isolated storage.
1.Let's improve the login dialog by adding a checkbox to the canvas so that users can select an option to automatically log them in on follow-up visits. We'll implement this by storing the user's login credentials in isolated storage. Bear in mind that isolated storage is not guaranteed to be persistent. Isolated storage presents a virtual file system to the developer by using cookies. If a user deletes the associated cookies, they will remove their login information and will be required to login again on following visits (just as with any Web site).
2.We will need to add references to the System.IO and System.IO.IsolatedStorage namespace. Add this to the top of the Page.xaml.cs code file:
using System.IO.IsolatedStorage; using System.IO;
3.The AuthenticateUserCompleted event is the place we'll want to store the user's information in isolated storage if they select the checkbox for us to do so. That way we don't forget to store the credentials away at a later point. When writing to isolated storage, you can gain access to the isolated storage mechanism through the System.IO.IsolatedStorage.IsolatedStorageFile class. Once an instance of the file class is created, a stream must be created for reading from and writing to the file. Finally, a file stream is used to actually write into the stream. The example version of the updated AuthenticateUserCompleted event is shown below:
void svc_AuthenticateUserCompleted(object sender, AthleteManager.AthleteService.AuthenticateUserCompletedEventArgs e) { if (e.Result) { ucLoginStatus1.IsLoggedIn = true; // remember the user's credentials for next time. if (chkRememberMe.IsChecked == true) { using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) { using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("UserCredentials.txt", FileMode.Create, isoStore)) { using (StreamWriter writer = new StreamWriter(isoStream)) { writer.Write(txtUserName.Text + "|" + txtPassword.Password); } } } } } else { ucLoginStatus1.IsLoggedIn = false; } }
4.Next, we'll need to read the user's credentials from isolated storage on follow up visits, if the information is stored in isolated storage. We'll want to completely subvert the login dialog in this scenario, if we can, so we'll add code to the code behind class constructor to check for the user's credentials in isolated storage.
5.The process of reading from isolated storage is almost exactly the opposite to the process of writing to isolated storage. The updated example code behind constructor is shown below:
public Page() { InitializeComponent(); svc.AuthenticateUserCompleted += new EventHandler<AthleteManager.AthleteService.AuthenticateUserCompletedEventArgs>(svc_AuthenticateUserCompleted); // determine if the user's credentials exist in isolated storage. using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) { using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("UserCredentials.txt", FileMode.Open, isoStore)) { using (StreamReader reader = new StreamReader(isoStream)) { // read the credentials. string[] sb = reader.ReadLine().Split('|'); // if the credentials exist, parse them out and authenticate them. string username = sb[0]; string password = sb[1]; // authenticate. svc.AuthenticateUserAsync(username, password); } } } btnLogin.Click += new RoutedEventHandler(btnLogin_Click); }
Add Controls to Display Athlete Information
45 60
In this exercise, you will add controls to the athlete management application design area to display athlete information.
1.Let's enhance our web service so that it can read Athlete information from the database. First, add a few namespace imports to the top of AthleteService.cs in the web project:
using System.Data.SqlClient; using System.Configuration; using System.Data;
2.We need a Connection object to the database. Add this declaration just inside the AthleteService class:
SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["athleteDB"].ConnectionString);
3.Next we add a new web service method to AthleteService.cs to retrieve the athlete information:
[WebMethod] public List<Athlete> ReadAthletes() { List<Athlete> athletes = new List<Athlete>(); cn.Open(); using (SqlCommand cmd = new SqlCommand("select * from Athletes", cn)) { cmd.CommandType = CommandType.Text; SqlDataReader rdr = cmd.ExecuteReader(); if (rdr.HasRows) { Athlete athlete; while (rdr.Read()) { athlete = new Athlete(); athlete.AthleteId = int.Parse(rdr["AthleteId"].ToString()); athlete.SportId = int.Parse(rdr["SportId"].ToString()); athlete.FirstName = rdr["FirstName"].ToString(); athlete.LastName = rdr["LastName"].ToString(); athlete.Address = rdr["Address"].ToString(); athlete.City = rdr["City"].ToString(); athlete.State = rdr["State"].ToString(); athlete.Zip = rdr["Zip"].ToString(); athletes.Add(athlete); } } } cn.Close(); cn.Dispose(); return athletes; }
4.Since we have added a method to the Web Service, we need to refresh the Service Reference from the Silverlight application. In the Silverlight project, expand the Service References node and then right-click the AthleteService control and select "Update Service Reference." Then, wire up the event handler for the call to ReadAthletes on the web service. Place this code in the Page constructor, just after the event wire-up for AuthenticateUserCompleted:
svc.ReadAthletesCompleted += new EventHandler<AthleteManager.AthleteService.ReadAthletesCompletedEventArgs>(svc_ReadAthletesCompleted);
5.The next step of the process is to display the athletes that are in the database in a datagrid. The datagrid is a control that is included in the Silverlight SDK. It might be easiest to set the visibility of the login dialog to Collapsed while designing the DataGrid and data display. Add code to the svc_AuthenticateUserCompleted event handler to hide the login dialog if the user has successfully authenticated, and call the ReadAthletesAsync method of the web service:
canvasLogin.Visibility = Visibility.Collapsed; svc.ReadAthletesAsync();
6.Drag a DataGrid from the toolbox to the Silverlight XAML. Assign the DataGrid a name and set the AutoGenerateColumns property to True.
<my:DataGrid x:Name="grdAthletes" AutoGenerateColumns="True"></my:DataGrid>
7.In the ReadAthletesCompleted callback event handler, write code to set the results of the method as the DataGrid ItemSource. We are returning an array of athlete objects from the ReadAthletes method.
void svc_ReadAthletesCompleted(object sender, AthleteManager.AthleteService.ReadAthletesCompletedEventArgs e) { grdAthletes.ItemsSource = e.Result; }
8.If you find that the DataGrid is not displaying data correctly, ensure that you specify Height and Width property values for the DataGrid. Test the Silverlight control to ensure that the DataGrid is displaying data correctly.
9.Modify the Silverlight control by adding additional controls for displaying athlete information, and buttons for Save, Add New and Delete. Use Expression Blend to design this data entry form. First create a new Canvas named canvasMain and be sure to place all of the following controls inside the Canvas (we will later show/hide this Canvas as necessary). The figures below illustrate the controls added to canvasMain and the resulting appearance.
1.txtFirstName: A TextBox for first name.
2.txtLastName: A TextBox for last name.
3.txtAddress: A TextBox for address.
4.txtCity: A TextBox for city.
5.txtState: A TextBox for state.
6.txtZip: A TextBox for zip.
7.btnSave: A button to save the current record.
8.btnAddNew: A button for entering "new record" mode.
9.btnDelete: A button for deleting the current record.
10.Complete the Silverlight control by adding additional controls to the control for displaying athlete information.
11.Add Data Binding markup syntax to the textboxes in XAML so that they show the value of the fields when databound:
<TextBox Height="20" x:Name="txtFirstName" Width="137" Canvas.Left="89" Canvas.Top="255" Text="{Binding FirstName, Mode=TwoWay}" TextWrapping="Wrap" /> <TextBox Height="20" x:Name="txtLastName" Width="137" Text="{Binding LastName, Mode=TwoWay}" TextWrapping="Wrap" Canvas.Top="255" Canvas.Left="240"/> <TextBox Height="20" x:Name="txtAddress" Width="285" Text="{Binding Address, Mode=TwoWay}" TextWrapping="Wrap" Canvas.Left="89" Canvas.Top="288"/> <TextBox Height="20" x:Name="txtCity" Width="99" Text="{Binding City, Mode=TwoWay}" TextWrapping="Wrap" Canvas.Left="89" Canvas.Top="320"/> <TextBox Height="20" x:Name="txtState" Width="29.539" Text="{Binding State, Mode=TwoWay}" TextWrapping="Wrap" Canvas.Left="240" Canvas.Top="320"/> <TextBox Height="20" x:Name="txtZip" Width="60" Text="{Binding Zip, Mode=TwoWay}" TextWrapping="Wrap" Canvas.Top="320" Canvas.Left="314"/>
12.Ensure that the new controls only display when the user has successfully logged into the application. You can do this by adding code to the svc_AuthenticateUserCompleted event:
canvasMain.Visibility = Visibility.Visible;
13.Add an event handler to the DataGrid SelectionChange event. In the event handler, set the DataContext to data bind the values displayed in the controls to the athlete information for the currently selected athlete in the DataGrid list. The example code is shown below.
void grdAthletes_SelectionChanged(object sender, SelectionChangedEventArgs e) { AthleteService.Athlete athlete = (AthleteService.Athlete)grdAthletes.SelectedItem; LayoutRoot.DataContext = athlete; }
14.Run the application. You should be able to browse the available records.
15.Next we'll add update capabilities to the Save, Delete and Add New buttons. Inside the AthleteService.cs Web Service class, add the following two Web Methods:
[WebMethod] public void SaveAthlete(Athlete athlete) { string sqlText = string.Empty; if (athlete.AthleteId > 0) sqlText = "update Athletes set FirstName=@FirstName, LastName=@LastName, Address=@Address, City=@City, State=@State, Zip=@Zip where AthleteId = @AthleteId"; else sqlText = "insert into Athletes (FirstName, LastName, Address, City, State, Zip) values (@FirstName, @LastName, @Address, @City, @State, @Zip)"; cn.Open(); using (SqlCommand cmd = new SqlCommand(sqlText, cn)) { cmd.Parameters.Add(new SqlParameter("@FirstName", athlete.FirstName)); cmd.Parameters.Add(new SqlParameter("@LastName", athlete.LastName)); cmd.Parameters.Add(new SqlParameter("@Address", athlete.Address)); cmd.Parameters.Add(new SqlParameter("@City", athlete.City)); cmd.Parameters.Add(new SqlParameter("@State", athlete.State)); cmd.Parameters.Add(new SqlParameter("@Zip", athlete.Zip)); cmd.Parameters.Add(new SqlParameter("@AthleteId", athlete.AthleteId)); cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery(); } cn.Close(); cn.Dispose(); } [WebMethod] public void DeleteAthlete(Athlete athlete) { string sqlText = "delete from Athletes where AthleteId = @AthleteId"; cn.Open(); using (SqlCommand cmd = new SqlCommand(sqlText, cn)) { cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery(); } cn.Close(); cn.Dispose(); }
16.Build the application and then refresh the Service References in the Silverlight project again as you did in a previous step (right-click the AthleteService reference and select "Update Service Reference.")
17.Inside Page.xaml.cs, inside the constructor, wire up the Completed event handlers for the new Save and Delete methods:
svc.DeleteAthleteCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(svc_DeleteAthleteCompleted); svc.SaveAthleteCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(svc_SaveAthleteCompleted);
18.Now wire up the click event handlers for our Save, Add New, and Delete buttons. Add this to the Page.xaml.cs constructor code:
btnSave.Click += new RoutedEventHandler(btnSave_Click); btnAddNew.Click += new RoutedEventHandler(btnAddNew_Click); btnDelete.Click += new RoutedEventHandler(btnDelete_Click);
19.Lastly, we can call the web methods inside the button handers.
void btnDelete_Click(object sender, RoutedEventArgs e) { AthleteService.Athlete athlete = (LayoutRoot.DataContext as AthleteService.Athlete); svc.DeleteAthleteAsync(athlete); } void btnAddNew_Click(object sender, RoutedEventArgs e) { AthleteService.Athlete athlete = new AthleteService.Athlete(); LayoutRoot.DataContext = athlete; } void btnSave_Click(object sender, RoutedEventArgs e) { AthleteService.Athlete athlete = (LayoutRoot.DataContext as AthleteService.Athlete); svc.SaveAthleteAsync(athlete); }
20.Run the application, and try adding, updating and deleting a record.
Error. This text should not be shown. Please email courseware@webucator.com to report it:
Lab: Accessing Data In Silverlight
In this lab, you will extend the athlete management application login dialog by providing the user the option to save their credentials and automatically login on future visits. The user credentials will be stored in isolated storage.
Exercise: Store User Credentials in Isolated Storage
Duration: 30 to 45 minutes.
In this exercise, you will store user credentials in isolated storage.
1.Let's improve the login dialog by adding a checkbox to the canvas so that users can select an option to automatically log them in on follow-up visits. We'll implement this by storing the user's login credentials in isolated storage. Bear in mind that isolated storage is not guaranteed to be persistent. Isolated storage presents a virtual file system to the developer by using cookies. If a user deletes the associated cookies, they will remove their login information and will be required to login again on following visits (just as with any Web site).
2.We will need to add references to the System.IO and System.IO.IsolatedStorage namespace. Add this to the top of the Page.xaml.cs code file:
using System.IO.IsolatedStorage; using System.IO;
3.The AuthenticateUserCompleted event is the place we'll want to store the user's information in isolated storage if they select the checkbox for us to do so. That way we don't forget to store the credentials away at a later point. When writing to isolated storage, you can gain access to the isolated storage mechanism through the System.IO.IsolatedStorage.IsolatedStorageFile class. Once an instance of the file class is created, a stream must be created for reading from and writing to the file. Finally, a file stream is used to actually write into the stream. The example version of the updated AuthenticateUserCompleted event is shown below:
void svc_AuthenticateUserCompleted(object sender,
AthleteManager.AthleteService.AuthenticateUserCompletedEventArgs e) { if
(e.Result) { ucLoginStatus1.IsLoggedIn = true; // remember the user's
credentials for next time. if (chkRememberMe.IsChecked == true) { using
(IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication()) { using
(IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("UserCredentials.txt", FileMode.Create, isoStore)) {
using (StreamWriter writer = new StreamWriter(isoStream)) {
writer.Write(txtUserName.Text + "|" + txtPassword.Password); } } } } } else {
ucLoginStatus1.IsLoggedIn = false; } }
4.Next, we'll need to read the user's credentials from isolated storage on follow up visits, if the information is stored in isolated storage. We'll want to completely subvert the login dialog in this scenario, if we can, so we'll add code to the code behind class constructor to check for the user's credentials in isolated storage.
5.The process of reading from isolated storage is almost exactly the opposite to the process of writing to isolated storage. The updated example code behind constructor is shown below:
public Page() { InitializeComponent();
svc.AuthenticateUserCompleted += new
EventHandler<AthleteManager.AthleteService.AuthenticateUserCompletedEventArgs>(svc_AuthenticateUserCompleted);
// determine if the user's credentials exist in isolated storage. using
(IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication()) { using
(IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("UserCredentials.txt", FileMode.Open, isoStore)) {
using (StreamReader reader = new StreamReader(isoStream)) { // read the
credentials. string[] sb = reader.ReadLine().Split('|'); // if the credentials
exist, parse them out and authenticate them. string username = sb[0]; string
password = sb[1]; // authenticate. svc.AuthenticateUserAsync(username,
password); } } } btnLogin.Click += new RoutedEventHandler(btnLogin_Click); }
Exercise: Add Controls to Display Athlete Information
Duration: 45 to 60 minutes.
In this exercise, you will add controls to the athlete management application design area to display athlete information.
1.Let's enhance our web service so that it can read Athlete information from the database. First, add a few namespace imports to the top of AthleteService.cs in the web project:
using System.Data.SqlClient; using
System.Configuration; using System.Data;
2.We need a Connection object to the database. Add this declaration just inside the AthleteService class:
SqlConnection cn = new
SqlConnection(ConfigurationManager.ConnectionStrings["athleteDB"].ConnectionString);
3.Next we add a new web service method to AthleteService.cs to retrieve the athlete information:
[WebMethod] public List<Athlete>
ReadAthletes() { List<Athlete> athletes = new List<Athlete>(); cn.Open(); using
(SqlCommand cmd = new SqlCommand("select * from Athletes", cn)) {
cmd.CommandType = CommandType.Text; SqlDataReader rdr = cmd.ExecuteReader(); if
(rdr.HasRows) { Athlete athlete; while (rdr.Read()) { athlete = new Athlete();
athlete.AthleteId = int.Parse(rdr["AthleteId"].ToString()); athlete.SportId =
int.Parse(rdr["SportId"].ToString()); athlete.FirstName =
rdr["FirstName"].ToString(); athlete.LastName = rdr["LastName"].ToString();
athlete.Address = rdr["Address"].ToString(); athlete.City =
rdr["City"].ToString(); athlete.State = rdr["State"].ToString(); athlete.Zip =
rdr["Zip"].ToString(); athletes.Add(athlete); } } } cn.Close(); cn.Dispose();
return athletes; }
4.Since we have added a method to the Web Service, we need to refresh the Service Reference from the Silverlight application. In the Silverlight project, expand the Service References node and then right-click the AthleteService control and select "Update Service Reference." Then, wire up the event handler for the call to ReadAthletes on the web service. Place this code in the Page constructor, just after the event wire-up for AuthenticateUserCompleted:
svc.ReadAthletesCompleted += new
EventHandler<AthleteManager.AthleteService.ReadAthletesCompletedEventArgs>(svc_ReadAthletesCompleted);
5.The next step of the process is to display the athletes that are in the database in a datagrid. The datagrid is a control that is included in the Silverlight SDK. It might be easiest to set the visibility of the login dialog to Collapsed while designing the DataGrid and data display. Add code to the svc_AuthenticateUserCompleted event handler to hide the login dialog if the user has successfully authenticated, and call the ReadAthletesAsync method of the web service:
canvasLogin.Visibility = Visibility.Collapsed; svc.ReadAthletesAsync();
6.Drag a DataGrid from the toolbox to the Silverlight XAML. Assign the DataGrid a name and set the AutoGenerateColumns property to True.
<my:DataGrid x:Name="grdAthletes" AutoGenerateColumns="True"></my:DataGrid>
7.In the ReadAthletesCompleted callback event handler, write code to set the results of the method as the DataGrid ItemSource. We are returning an array of athlete objects from the ReadAthletes method.
void
svc_ReadAthletesCompleted(object sender,
AthleteManager.AthleteService.ReadAthletesCompletedEventArgs e) {
grdAthletes.ItemsSource = e.Result; }
8.If you find that the DataGrid is not displaying data correctly, ensure that you specify Height and Width property values for the DataGrid. Test the Silverlight control to ensure that the DataGrid is displaying data correctly.
9.Modify the Silverlight control by adding additional controls for displaying athlete information, and buttons for Save, Add New and Delete. Use Expression Blend to design this data entry form. First create a new Canvas named canvasMain and be sure to place all of the following controls inside the Canvas (we will later show/hide this Canvas as necessary). The figures below illustrate the controls added to canvasMain and the resulting appearance.
1.txtFirstName: A TextBox for first name.
2.txtLastName: A TextBox for last name.
3.txtAddress: A TextBox for address.
4.txtCity: A TextBox for city.
5.txtState: A TextBox for state.
6.txtZip: A TextBox for zip.
7.btnSave: A button to save the current record.
8.btnAddNew: A button for entering "new record" mode.
9.btnDelete: A button for deleting the current record.
10.Complete the Silverlight control by adding additional controls to the control for displaying athlete information.
11.Add Data Binding markup syntax to the textboxes in XAML so that they show the value of the fields when databound:
<TextBox Height="20" x:Name="txtFirstName" Width="137" Canvas.Left="89"
Canvas.Top="255" Text="{Binding FirstName, Mode=TwoWay}" TextWrapping="Wrap" />
<TextBox Height="20" x:Name="txtLastName" Width="137" Text="{Binding LastName,
Mode=TwoWay}" TextWrapping="Wrap" Canvas.Top="255" Canvas.Left="240"/> <TextBox
Height="20" x:Name="txtAddress" Width="285" Text="{Binding Address,
Mode=TwoWay}" TextWrapping="Wrap" Canvas.Left="89" Canvas.Top="288"/> <TextBox
Height="20" x:Name="txtCity" Width="99" Text="{Binding City, Mode=TwoWay}"
TextWrapping="Wrap" Canvas.Left="89" Canvas.Top="320"/> <TextBox Height="20"
x:Name="txtState" Width="29.539" Text="{Binding State, Mode=TwoWay}"
TextWrapping="Wrap" Canvas.Left="240" Canvas.Top="320"/> <TextBox Height="20"
x:Name="txtZip" Width="60" Text="{Binding Zip, Mode=TwoWay}" TextWrapping="Wrap"
Canvas.Top="320" Canvas.Left="314"/>
12.Ensure that the new controls only display when the user has successfully logged into the application. You can do this by adding code to the svc_AuthenticateUserCompleted event:
canvasMain.Visibility = Visibility.Visible;
13.Add an event handler to the DataGrid SelectionChange event. In the event handler, set the DataContext to data bind the values displayed in the controls to the athlete information for the currently selected athlete in the DataGrid list. The example code is shown below.
void grdAthletes_SelectionChanged(object sender, SelectionChangedEventArgs e) {
AthleteService.Athlete athlete =
(AthleteService.Athlete)grdAthletes.SelectedItem; LayoutRoot.DataContext =
athlete; }
14.Run the application. You should be able to browse the available records.
15.Next we'll add update capabilities to the Save, Delete and Add New buttons. Inside the AthleteService.cs Web Service class, add the following two Web Methods:
[WebMethod] public void SaveAthlete(Athlete athlete) {
string sqlText = string.Empty; if (athlete.AthleteId > 0) sqlText = "update
Athletes set FirstName=@FirstName, LastName=@LastName, Address=@Address,
City=@City, State=@State, Zip=@Zip where AthleteId = @AthleteId"; else sqlText =
"insert into Athletes (FirstName, LastName, Address, City, State, Zip) values
(@FirstName, @LastName, @Address, @City, @State, @Zip)"; cn.Open(); using
(SqlCommand cmd = new SqlCommand(sqlText, cn)) { cmd.Parameters.Add(new
SqlParameter("@FirstName", athlete.FirstName)); cmd.Parameters.Add(new
SqlParameter("@LastName", athlete.LastName)); cmd.Parameters.Add(new
SqlParameter("@Address", athlete.Address)); cmd.Parameters.Add(new
SqlParameter("@City", athlete.City)); cmd.Parameters.Add(new
SqlParameter("@State", athlete.State)); cmd.Parameters.Add(new
SqlParameter("@Zip", athlete.Zip)); cmd.Parameters.Add(new
SqlParameter("@AthleteId", athlete.AthleteId)); cmd.CommandType =
CommandType.Text; cmd.ExecuteNonQuery(); } cn.Close(); cn.Dispose(); }
[WebMethod] public void DeleteAthlete(Athlete athlete) { string sqlText =
"delete from Athletes where AthleteId = @AthleteId"; cn.Open(); using
(SqlCommand cmd = new SqlCommand(sqlText, cn)) { cmd.CommandType =
CommandType.Text; cmd.ExecuteNonQuery(); } cn.Close(); cn.Dispose(); }
16.Build the application and then refresh the Service References in the Silverlight project again as you did in a previous step (right-click the AthleteService reference and select "Update Service Reference.")
17.Inside Page.xaml.cs, inside the constructor, wire up the Completed event handlers for the new Save and Delete methods:
svc.DeleteAthleteCompleted += new
EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(svc_DeleteAthleteCompleted);
svc.SaveAthleteCompleted += new
EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(svc_SaveAthleteCompleted);
18.Now wire up the click event handlers for our Save, Add New, and Delete buttons. Add this to the Page.xaml.cs constructor code:
btnSave.Click += new
RoutedEventHandler(btnSave_Click); btnAddNew.Click += new
RoutedEventHandler(btnAddNew_Click); btnDelete.Click += new
RoutedEventHandler(btnDelete_Click);
19.Lastly, we can call the web methods inside the button handers.
void btnDelete_Click(object sender, RoutedEventArgs e) { AthleteService.Athlete
athlete = (LayoutRoot.DataContext as AthleteService.Athlete);
svc.DeleteAthleteAsync(athlete); } void btnAddNew_Click(object sender,
RoutedEventArgs e) { AthleteService.Athlete athlete = new
AthleteService.Athlete(); LayoutRoot.DataContext = athlete; } void
btnSave_Click(object sender, RoutedEventArgs e) { AthleteService.Athlete athlete
= (LayoutRoot.DataContext as AthleteService.Athlete);
svc.SaveAthleteAsync(athlete); }
20.Run the application, and try adding, updating and deleting a record.
In this lesson of the Silverlight tutorial, you
Managed data by using LINQ
Stored and retrieved XML using Silverlight
Stored data to and retrieved data from isolated storage
Footnotes
1.Comprehensive coverage of LINQ is well beyond the scope of this course. To learn more about LINQ, visit the LINQ Developer Center (the LINQ Project) located at http://msdn2.microsoft.com/en-us/netframework/aa904594.aspx.
To continue to learn Silverlight go to the top of this page and click on the next lesson in this Silverlight Tutorial's Table of Contents.
Tutorial on Silverlight 4 databinding in code-behind, custom user controls, etc.
19102010
Introduction
This small tutorial was written to show the students the following aspects of Silverlight:
Writing a class that can be used for databinding
Perform data-binding through code instead of XAML
Creating a custom user control
Writing simple data converters
Suppose we are creating a Silverlight game in which each player is represented as a pawn. However, the player class itself is somewhere deep inside the game-engine and we would like the pawn user control to be only loosely coupled to this player class. By doing this, we are able to make a rapid Silverlight prototype and if we later decide that the frontend is pretty lame, we can simply redesign it without too much fuss.
Player class
We create a small class that represents a player, with its name, color and location:
public class Player
{
private string name;
public string Name {
get { return name; }
set { name = value; }
}
private Point location;
public Point Location {
get { return location; }
set { location = value; }
}
private Color color;
public Color Color {
get { return color; }
set { color = value; }
}
}
For two-way databinding to work in Silverlight (and WPF) the Player class needs to implement the INotifyPropertyChanged interface:
public class Player: INotifyPropertyChanged
{
private string name;
public string Name {
get { return name; }
set {
name = value;
NotifyPropertyChanged("Name");
}
}
private Point location;
public Point Location {
get { return location; }
set {
location = value;
NotifyPropertyChanged("Location");
}
}
private Color color;
public Color Color {
get { return color; }
set {
color = value;
NotifyPropertyChanged("Color");
}
}
//Notify
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
We can now create a Player object anywhere it’s needed, e.g. :
Player player1 = new Player() {
Location = new Point(0, 0),
Name = "Tim",
Color=Colors.Blue };
Creating a user control
We create a custom user control that will represent the player in the game. Right-click your project and choose “Add new item…”. Next pick Silverlight User Control and give the control a meaningful name, such as pawn.
Set the DesignHeight and DesignWidth to 30 and then insert the following the XAML-code:
<Grid x:Name="LayoutRoot" Background="{x:Null}" >
<Ellipse x:Name="playerEllipse" Stroke="Black"
StrokeThickness="2" Height="30" Width="30" Fill="#FFFF1717"/>
</Grid>
By defining Background=”{x:Null} we make sure that the background of our control is transparent and thus will blend nicely on the game-board.
It is important to explicitly name each element if we wish to be able to bind certain properties to it later on.
Adding the user control to a canvas
Suppose we define a canvas somewhere on our MainPage.xaml:
<Canvas x:Name="playboardCanvas" Background="#FFD7FF07"
Width="400" Height="200">
Yeah, it’s a very ugly color, but let’s keep the design to other people.
If we wish to add the newly created user control to this canvas we need to perform the following steps:
1. Create a new instance of the usercontrol
2. Define any bindings needed
3. Add the control to the children of the canvas
This results in:
//Step 1
Pawn pawn = new Pawn();
//Step 2: bindings and datacontext comes here (discussed further on)
//Step 3
playboardCanvas.Children.Add(pawn);
Binding the pawn control to the player class
In order for the pawn to be bound to the player, we first point the pawns datacontext to the player:
pawn.DataContext = player1;
We then create a binding object in which we will bind the location of the player to the location of the pawn on the canvas.
//Bind location.X
Binding c = new Binding();
c.Source = player1;
c.Path = new PropertyPath("Location.X");
c.Mode = BindingMode.OneWay;
pawn.SetBinding(Canvas.LeftProperty, c);
We do the same for the Y-coordinate, only this one needs to be bound to the TopProperty of the pawn:
pawn.SetBinding(Canvas.TopProperty, c);
Writing a convertor
Suppose we defined the Location of our player to be an (x,y)coordinate between (0,0) and (8,8) (for example to define a pawn on a checkerboard). Our previously databound pawn would then be able to move between the (0,0) and (8,8) zone on the canvas…that’s a pretty small canvas.
We’ll write convertor that takes the actual dimensions of the canvas on the screen in account. The convertor will then transform the Location of the player to an equivalent location on the canvas.
The convertor is pretty straightforward. value will contain the X or Y coordinate of the player, and the extra parameter will contain a reference to the canvas on which the pawn is drawn:
public class CanvasLocationWidthConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Canvas canv = (Canvas)parameter;
return (double)value * (canv.ActualWidth / 5);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
We now simply add the convertor to the binding object we created earlier, so our binding code now is:
//Bind location.Y
Binding c = new Binding();
c.Source = player1;
c.Path = new PropertyPath("Location.X");
c.Mode = BindingMode.OneWay;
c.Converter = new CanvasLocationWidthConvertor();
c.ConverterParameter = playboardCanvas;
pionCanvas.SetBinding(Canvas.LeftProperty, c);
Binding the color
To bind the color of the player object to the pawn, we write the following binding in which the fillproperty of the ellipse is bound to the Color property:
Binding e = new Binding();
e.Source = player1;
e.Path = new PropertyPath("Color");
e.Mode = BindingMode.OneWay;
e.Converter = new PlayerColorConvertor();
pionCanvas.pionEllipse.SetBinding(Ellipse.FillProperty, e);
Since the FillProperty is defined by a SolidColorBrush instead of a Color we have to write a small convertor for that. Again, pretty straightforward:
public class PlayerColorConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Binding to a grid
The fun thing of databinding in Silverlight (and WPF) is that we kind bind any property of an object to any property of an XAML element. Suppose we defined a 5-by-5 checkerboard grid in xaml (note: make your life easy and write this kind of stuff in the code behind using some loops) :
<Grid x:Name="playGrid" >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black"></Rectangle>
<Rectangle Grid.Row="0" Grid.Column="2" Fill="Black"></Rectangle>
<Rectangle Grid.Row="0" Grid.Column="4" Fill="Black"></Rectangle>
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black"></Rectangle>
<Rectangle Grid.Row="1" Grid.Column="3" Fill="Black"></Rectangle>
<Rectangle Grid.Row="2" Grid.Column="0" Fill="Black"></Rectangle>
<Rectangle Grid.Row="2" Grid.Column="2" Fill="Black"></Rectangle>
<Rectangle Grid.Row="2" Grid.Column="4" Fill="Black"></Rectangle>
<Rectangle Grid.Row="3" Grid.Column="1" Fill="Black"></Rectangle>
<Rectangle Grid.Row="3" Grid.Column="3" Fill="Black"></Rectangle>
<Rectangle Grid.Row="4" Grid.Column="0" Fill="Black"></Rectangle>
<Rectangle Grid.Row="4" Grid.Column="2" Fill="Black"></Rectangle>
<Rectangle Grid.Row="4" Grid.Column="4" Fill="Black"></Rectangle>
</Grid>
Simply bind the X and Y coordinates of the player to the respective Grid.Row and Grid.Column properties of the playGrid object, e.g.:
//Bind location.X
Binding c2 = new Binding();
c2.Source = player1;
c2.Path = new PropertyPath("Location.X");
c2.Mode = BindingMode.OneWay;
playGrid.SetBinding(Grid.RowProperty,c2);
0 comments:
Post a Comment