Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Apress pro Silverlight 3 in C# phần 9 pps
PREMIUM
Số trang
95
Kích thước
2.7 MB
Định dạng
PDF
Lượt xem
1770

Apress pro Silverlight 3 in C# phần 9 pps

Nội dung xem thử

Mô tả chi tiết

CHAPTER 16 ■ DATA BINDING

561

while (reader.Read())

{

// Create a Product object that wraps the

// current record.

Product product = new Product((string)reader["ModelNumber"],

(string)reader["ModelName"], Convert.ToDouble(reader["UnitCost"]),

(string)reader["Description"], (string)reader["CategoryName"]);

// Add to collection

products.Add(product);

}

}

finally

{

con.Close();

}

return products;

}

When the user clicks the Get Products button, the event-handling code calls the

GetProducts() method asynchronously:

private void cmdGetProducts_Click(object sender, RoutedEventArgs e)

{

StoreDbClient client = new StoreDbClient();

client.GetProductsCompleted += client_GetProductsCompleted;

client.GetProductsAsync();

}

When the product list is received from the web service, the code stores the collection

as a member variable in the page class for easier access elsewhere in your code. The code then

sets that collection as the ItemsSource for the list:

private ObservableCollection[] products;

private void client_GetProductsCompleted(object sender,

GetProductsCompletedEventArgs e)

{

try

{

products = e.Result;

lstProducts.ItemsSource = products;

}

catch (Exception err)

{

lblError.Text = "Failed to contact service.";

}

}

CHAPTER 16 ■ DATA BINDING

562

■ Note Keen eyes will notice one unusual detail in this example. Although the web service returned an array

of Product objects, the client applications receives them in a different sort of package: the ObservableCollection.

You’ll learn why Silverlight performs this sleight of hand in the next section.

This code successfully fills the list with Product objects. However, the list doesn’t know

how to display a Product object, so it calls the ToString() method. Because this method hasn’t

been overridden in the Product class, this has the unimpressive result of showing the fully

qualified class name for every item (see Figure 16-6).

Figure 16-6. An unhelpful bound list

You have three options to solve this problem:

• Set the list’s DisplayMemberPath property. For example, set it to ModelName to get the

result shown in Figure 16-5.

• Override the Product.ToString() method to return more useful information. For example,

you can return a string with the model number and model name of each item. This

approach gives you a way to show more than one property in the list (for example, it’s

great for combining the FirstName and LastName properties in a Customer class).

CHAPTER 16 ■ DATA BINDING

563

• Supply a data template. This way, you can show any arrangement of property values

(and along with fixed text). You’ll learn how to use this trick later in this chapter.

When you’ve decided how to display information in the list, you’re ready to move on to

the second challenge: displaying the details for the currently selected item in the grid that

appears below the list. To make this work, you need to respond to the SelectionChanged event

and change the DataContext of the Grid that contains the product details. Here’s the code that

does it:

private void lstProducts_SelectionChanged(object sender,

SelectionChangedEventArgs e)

{

gridProductDetails.DataContext = lstProducts.SelectedItem;

}

■ Tip To prevent a field from being edited, set the TextBox.IsReadOnly property to true or, better yet, use a

read-only control like a TextBlock.

If you try this example, you’ll be surprised to see that it’s already fully functional. You

can edit product items, navigate away (using the list), and then return to see that your edits

were successfully committed to the in-memory data objects. You can even change a value that

affects the display text in the list. If you modify the model name and tab to another control, the

corresponding entry in the list is refreshed automatically.

But there’s one quirk. Changes are committed only when a control loses focus. If you

change a value in a text box and then move to another text box, the data object is updated just

as you’d expect. However, if you change a value and then click a new item in the list, the edited

value is discarded, and the information from the selected data object is loaded. If this behavior

isn’t what you want, you can add code that explicitly forces a change to be committed. Unlike

WPF, Silverlight has no direct way to accomplish this. Your only option is to programmatically

send the focus to another control (if necessary, an invisible one) by calling its Focus() method.

This commits the change to the data object. You can then bring the focus back to the original

text box by calling its Focus() method. You can use this code when reacting to TextChanged, or

you can add a Save or Update button. If you use the button approach, no code is required,

because clicking the button changes the focus and triggers the update automatically.

Inserting and Removing Collection Items

As you saw in the previous section, Silverlight performs a change when it generates the client￾side code for communicating with a web service. Your web service may return an array or List

collection, but the client-side code places the objects into an ObservableCollection. The same

translation step happens if you return an object with a collection property.

This shift takes place because the client doesn’t really know what type of collection the

web server is returning. Silverlight assumes that it should use an ObservableCollection to be

safe, because an ObservableCollection is more fully featured than an array or an ordinary List

collection.

CHAPTER 16 ■ DATA BINDING

564

So what does the ObservableCollection add that arrays and List objects lack? First, like

the List, the ObservableCollection has support for adding and removing items. For example,

you try deleting an item with a Delete button that executes this code:

private void cmdDeleteProduct_Click(object sender, RoutedEventArgs e)

{

products.Remove((Product)lstProducts.SelectedItem);

}

This obviously doesn’t work with an array. It does work with a List collection, but

there’s a problem: although the deleted item is removed from the collection, it remains

stubbornly visible in the bound list.

To enable collection change tracking, you need to use a collection that implements the

INotifyCollectionChanged interface. In Silverlight, the only collection that meets this bar is the

ObservableCollection class. When you execute the above code with an ObservableCollection

like the collection of products returned from the web service, you’ll see the bound list is

refreshed immediately. Of course, it’s still up to you to create the data-access code that can

commit changes like these permanently–for example, the web service methods that insert and

remove products from the back-end database.

Binding to a LINQ Expression

One of Silverlight’s many surprises is its support for Language Integrated Query, which is an all￾purpose query syntax that was introduced in .NET 3.5.

LINQ works with any data source that has a LINQ provider. Using the support that’s

included with Silverlight, you can use similarly structured LINQ queries to retrieve data from an

in-memory collection or an XML file. And as with other query languages, LINQ lets you apply

filtering, sorting, grouping, and transformations to the data you retrieve.

Although LINQ is somewhat outside the scope of this chapter, you can learn a lot from

a simple example. For example, imagine you have a collection of Product objects named

products, and you want to create a second collection that contains only those products that

exceed $100 in cost. Using procedural code, you can write something like this:

// Get the full list of products.

List<Product> products = App.StoreDb.GetProducts();

// Create a second collection with matching products.

List<Product> matches = new List<Product>();

foreach (Product product in products)

{

if (product.UnitCost >= 100)

{

matches.Add(product);

}

}

Using LINQ, you can use the following expression, which is far more concise:

// Get the full list of products.

List<Product> products = App.StoreDb.GetProducts();

// Create a second collection with matching products.

CHAPTER 16 ■ DATA BINDING

565

IEnumerable<Product> matches = from product in products

where product.UnitCost >= 100

select product;

This example uses LINQ to Objects, which means it uses a LINQ expression to query

the data in an in-memory collection. LINQ expressions use a set of new language keywords,

including from, in, where, and select. These LINQ keywords are a genuine part of the C#

language.

■ Note A full discussion of LINQ is beyond the scope of this book. For a detailed treatment, you can refer to

the book Pro LINQ: Language Integrated Query in C# 2008, the LINQ developer center at

http://msdn.microsoft.com/en-us/netframework/aa904594.aspx, or the huge catalog of LINQ

examples at http://msdn2.microsoft.com/en-us/vcsharp/aa336746.aspx.

LINQ revolves around the IEnumerable<T> interface. No matter what data source you

use, every LINQ expression returns some object that implements IEnumerable<T>. Because

IEnumerable<T> extends IEnumerable, you can bind it in a Silverlight page just as you bind an

ordinary collection (see Figure 16-7):

lstProducts.ItemsSource = matches;

CHAPTER 16 ■ DATA BINDING

566

Figure 16-7. Filtering a collection with LINQ

Unlike the List and ObservableCollection classes, the IEnumerable<T> interface

doesn’t provide a way to add or remove items. If you need this capability, you must first convert

your IEnumerable<T> object into an array or List collection using the ToArray() or ToList()

method.

Here’s an example that uses ToList() to convert the result of a LINQ query (shown

previously) into a strongly typed List collection of Product objects:

List<Product> productMatches = matches.ToList();

CHAPTER 16 ■ DATA BINDING

567

■ Note ToList() is an extension method, which means it’s defined in a different class from the one in which it’s

used. Technically, ToList() is defined in the System.Linq.Enumerable helper class, and it’s available to all

IEnumerable<T> objects. However, it isn’t available if the Enumerable class isn’t in scope, which means the

code shown here won’t work if you haven’t imported the System.Linq namespace.

The ToList() method causes the LINQ expression to be evaluated immediately. The

end result is an ordinary List collection, which you can deal with in all the usual ways. If you

want to make the collection editable, so that changes show up in bound controls immediately,

you’ll need to copy the contents of the List to a new ObservableCollection.

Master-Details Display

As you’ve seen, you can bind other elements to the SelectedItem property of your list to show

more details about the currently selected item. Interestingly, you can use a similar technique to

build a master-details display of your data. For example, you can create a page that shows a list

of categories and a list of products. When the user chooses a category in the first list, you can

show just the products that belong to that category in the second list. Figure 16-8 shows this

example.

Figure 16-8. A master-details list

CHAPTER 16 ■ DATA BINDING

568

To pull this off, you need a parent data object that provides a collection of related child

data objects through a property. For example, you can build a Category class that provides a

property named Category.Products with the products that belong to that category. Like the

Product class, the Category class can implement the INotifyPropertyChanged to provide change

notifications. Here’s the complete code:

public class Category : INotifyPropertyChanged

{

private string categoryName;

public string CategoryName

{

get { return categoryName; }

set { categoryName = value;

OnPropertyChanged(new PropertyChangedEventArgs("CategoryName"));

}

}

private List<Product> products;

public List<Product> Products

{

get { return products; }

set { products = value;

OnPropertyChanged(new PropertyChangedEventArgs("Products"));

}

}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(PropertyChangedEventArgs e)

{

if (PropertyChanged != null)

PropertyChanged(this, e);

}

public Category(string categoryName, List<Product> products)

{

CategoryName = categoryName;

Products = products;

}

public Category(){}

}

To use the Category class, you also need to modify the data-access code that you saw

earlier. Now, you query the information about products and categories from the database. The

example in Figure 16-8 uses a web service method named GetCategoriesWithProducts(), which

returns a collection of Category objects, each of which has a nested collection of Product

objects:

[OperationContract()]

public List<Category> GetCategoriesWithProducts()

{

// Perform the query for products using the GetProducts stored procedure.

SqlConnection con = new SqlConnection(connectionString);

CHAPTER 16 ■ DATA BINDING

569

SqlCommand cmd = new SqlCommand("GetProducts", con);

cmd.CommandType = CommandType.StoredProcedure;

// Store the results (temporarily) in a DataSet.

SqlDataAdapter adapter = new SqlDataAdapter(cmd);

DataSet ds = new DataSet();

adapter.Fill(ds, "Products");

// Perform the query for categories using the GetCategories stored procedure.

cmd.CommandText = "GetCategories";

adapter.Fill(ds, "Categories");

// Set up a relation between these tables.

// This makes it easier to discover the products in each category.

DataRelation relCategoryProduct = new DataRelation("CategoryProduct",

ds.Tables["Categories"].Columns["CategoryID"],

ds.Tables["Products"].Columns["CategoryID"]);

ds.Relations.Add(relCategoryProduct);

// Build the collection of Category objects.

List<Category> categories = new List<Category>();

foreach (DataRow categoryRow in ds.Tables["Categories"].Rows)

{

// Add the nested collection of Product objects for this category.

List<Product> products = new List<Product>();

foreach (DataRow productRow in categoryRow.GetChildRows(relCategoryProduct))

{

products.Add(new Product(productRow["ModelNumber"].ToString(),

productRow["ModelName"].ToString(),

Convert.ToDouble(productRow["UnitCost"]),

productRow["Description"].ToString()));

}

categories.Add(new Category(categoryRow["CategoryName"].ToString(),

products));

}

return categories;

}

To display this data, you need the two lists shown here:

<ListBox x:Name="lstCategories" DisplayMemberPath="CategoryName"

SelectionChanged="lstCategories_SelectionChanged"></ListBox>

<ListBox x:Name="lstProducts" Grid.Row="1" DisplayMemberPath="ModelName">

</ListBox>

After you receive the collection from the GetCategoriesWithProducts() method, you

can set the ItemsSource of the topmost list to show the categories:

lstCategories.ItemsSource = e.Result;

To show the related products, you must react when an item is clicked in the first list,

and then set the ItemsSource property of the second list to the Category.Products property of

the selected Category object:

Tải ngay đi em, còn do dự, trời tối mất!