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

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

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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: CHAPTER 16 ■ DATA BINDING 570 lstProducts.ItemsSource = ((Category)lstCategories.SelectedItem).Products; Data Conversion In an ordinary binding, the information travels from the source to the target without any change. This seems logical, but it’s not always the behavior you want. Your data source may use a low-level representation that you don’t want to display directly in your user interface. For example, you may have numeric codes you want to replace with human-readable strings, numbers that need to be cut down to size, dates that need to be displayed in a long format, and so on. If so, you need a way to convert these values into the correct display form. And if you’re using a two-way binding, you also need to do the converse–take user-supplied data and convert it to a representation suitable for storage in the appropriate data object. Fortunately, Silverlight allows you to do both by creating (and using) a value-converter class. The value converter is responsible for converting the source data just before it’s displayed in the target and (in the case of a two-way binding) converting the new target value just before it’s applied back to the source. Value converters are an extremely useful piece of the Silverlight data-binding puzzle. You can use them several ways: • To format data to a string representation. For example, you can convert a number to a currency string. This is the most obvious use of value converters, but it’s certainly not the only one. • To create a specific type of Silverlight object. For example, you can read a block of binary data and create a BitmapImage object that can be bound to an Image element. • To conditionally alter a property in an element based on the bound data. For example, you may create a value converter that changes the background color of an element to highlight values in a specific range. In the following sections, you’ll consider an example of each of these approaches. Formatting Strings with a Value Converter Value converters are the perfect tool for formatting numbers that need to be displayed as text. For example, consider the Product.UnitCost property in the previous example. It’s stored as a decimal; and, as a result, when it’s displayed in a text box, you see values like 3.9900. Not only does this display format show more decimal places than you’d probably like, but it also leaves out the currency symbol. A more intuitive representation is the currency-formatted value $49.99, as shown in Figure 16-9. [...]... benefits appear when you use binding to latch it onto the control you’re captioning using the Target property, like this: When used this way, the label does something interesting Rather than rely on you... sorting, editing, grouping, and (with the help of the DataPager) paging • The TreeView control: Silverlight s hierarchical tree isn’t limited to data binding and doesn’t support editing However, it’s a true timesaver when dealing with hierarchical data–for example, a list of categories with nested lists of products In this chapter, you’ll learn how to extend the data-binding basics you picked up in. .. ... valid ACME Industries ModelNumber.")] public string ModelNumber { } 595 CHAPTER 17 ■ DATA CONTROLS StringLength This attribute sets the maximum length of a string You can also (optionally) set a minimum length by setting the MinimumLength property, as shown here: [StringLength(25, MinimumLength=5)] public string ModelName { } When you’re using an attribute that has important parameters, like StringLength,... client-side Silverlight applications ■ What’s New Virtually all the features and controls in this chapter are new to Silverlight 3 The exception is the DataGrid control, which still boasts several improvements, including cancellable editing events, support for data annotations, grouping, and paging 585 CHAPTER 17 ■ DATA CONTROLS Better Data Forms In the previous chapter, you learned how to use data binding... that stores the file name of an associated product image In this case, you have even more reason to delay creating the image object First, the image may not be available, depending on where the application is running Second, there’s no point in incurring the extra memory overhead from storing the image unless it’s going to be displayed The ProductImage field includes the file name but not the full URI... can’t perform the conversion, you can return the value Binding.UnsetValue to tell Silverlight to ignore your binding The bound property (in this case, Background) will keep its default value Once again, the value converter is carefully designed with reusability in mind Rather than hard-coding the color highlights in the converter, they’re specified in the XAML by the code that uses the converter: ... although data binding is a flexible and powerful system, getting the result you want can still take a lot of work For example, a typical data form needs to bind a number of different properties to different controls, arrange them in a meaningful way, and use the appropriate converters, templates, and validation logic Creating these ingredients is as time-consuming as any other type of UI design Silverlight . 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. = new List<Product>(); foreach (Product product in products) { if (product.UnitCost >= 100) { matches.Add(product); } } Using LINQ, you can use the following expression,. list of products. List<Product> products = App.StoreDb.GetProducts(); // Create a second collection with matching products. CHAPTER 16 ■ DATA BINDING 565 IEnumerable<Product>

Ngày đăng: 06/08/2014, 08:22

Từ khóa liên quan

Mục lục

  • Data Binding

    • Binding to a Collection of Objects

      • Inserting and Removing Collection Items

      • Binding to a LINQ Expression

      • Master-Details Display

      • Data Conversion

        • Formatting Strings with a Value Converter

        • Creating Objects with a Value Converter

        • Applying Conditional Formatting

        • Data Templates

          • Separating and Reusing Templates

          • More Advanced Templates

          • Changing Item Layout

          • The Last Word

          • Data Controls

            • Better Data Forms

              • The Goal: Data Class Markup

              • The Label

              • The DescriptionViewer

              • The ValidationSummary

              • Data Annotations

                • Raising Annotation Errors

                • The Annotation Attributes

                • Web Service Type Sharing

                • The DataGrid

                  • Creating a Simple Grid

                  • Resizing and Rearranging Columns

                  • Defining Columns

Tài liệu cùng người dùng

Tài liệu liên quan