6. Data

WINDOWS PRESENTATION FOUNDATION differs from many UI frameworks in how it treats data. The old User32-based UI model was fairly data-agnostic. Controls contained data, and displaying something required transforming the data into something the control understood. Other frameworks, like Java’s Swing library, had a strict data model that required separation between data and control. In WPF we wanted a separation between data and UI to be possible but not required.

Although WPF enables the mixing of data and UI, generally data-driven models provide more flexibility and ability to integrate the work of both developers and designers.

Data Principles

Most applications are built to display or create some type of data. Whether that data is a document, database, or drawing, applications are fundamentally about displaying, creating, and editing data. There are as many ways of representing data as there are applications. When .NET was first launched, it introduced a standard data model that significantly changed the way that frameworks handled data.

The .NET Data Model

A data model describes the contract between a source of data and the consumer of that data. Historically, every framework introduced a new data model: Visual Basic had DAO (Data Access Objects), RDO (Remote Data Objects), and finally ADO (ActiveX Data Objects), before moving on to .NET. With .NET, there was a significant change away from API-specific data models to using a common data model across the framework.

.NET has an object model, with classes, interfaces, structs, enums, delegates, and so on. Beyond that, though, .NET introduced a very basic data model. Lists in .NET are represented by System.Collection interfaces: IEnumerable and IList. Properties are defined either with the built-in CLR properties or by the implementation of ICustomTypeDescriptor. This basic data model has remained constant even as new data access technologies have come about, and even through major new language changes like the language-integrated query that is being discussed for the next major release of .NET.

.NET also included several concrete ways of dealing with data: ADO.NET (System.Data namespace), XML (System.Xml namespace), data contract (System.Runtime.Serialization namespace), and markup (System.Windows.Markup namespace), to name a few. The important thing is that all of these are built on the basic .NET data model.

Because all of WPF’s data operations are based on the fundamental .NET data model, WPF controls can retrieve data from any CLR object:

image

Namita Gupta, a program manager on the WPF team, used a great phrase to describe this functionality: “Your data, your way.” As long as data can be accessed through the CLR, it can be visualized in WPF.

Pervasive Binding

Of all the major innovations in WPF, probably the most significant to the programming experience is the complete integration of binding throughout the system. Control templates utilize template bindings, resources are loaded via resource bindings, and even the base model for controls like Button and ListBox are based on the “content model,” which leverages data binding heavily. The notion of binding properties to data sources, tracking dependencies, and automatically updating the display is inherent in every part of WPF.

Binding in WPF goes by many names. Normally the reason we chose to use a different name was that the flavor of binding had a limitation or behavior that was very specific to the scenario. For example, template bindings use the {TemplateBinding} markup instead of the standard {Binding} notation for data binding. Template bindings work only in the context of a template, and they can bind only to properties of the templated control. This limitation makes template bindings very efficient—a specialization that was necessary because so many controls are bound to templates in WPF.

Binding provides the ability to synchronize two pieces of data. In WPF, that means binding a data item (i.e., a property of a data source) to a property on a UI element. Binding works great as a way to keep two objects in sync, but it begins to have problems when the data types of these objects don’t match up perfectly. To make binding truly pervasive in the framework, it was necessary to provide ways to transform data.

Data Transformation

In a framework with pervasive binding, accessing any data is possible only if that data can be transformed anywhere that binding is supported.[1]

WPF supports two main types of transformation: value conversion and data templates. Value converters are just as advertised: They convert values from one format to another (e.g., converting the string value “Red” into Brushes.Red). This is a powerful capability because the converter can take either representation and convert it (a two-way transformation of data). Data templates, on the other hand, allow for controls to be created on the fly to represent data (this is similar in concept to the XSLT, or Extensible Stylesheet Language Transformations, model). In the data template model, the controls have access back to the data model through bindings, which allow for read and (potentially write) access back to the source of the data.

Transformations allow any data to be transformed into a compelling display. To start the tour of data in WPF, we can begin with one of the more common data sources: resources.

Resources

Generally the first place that people encounter the separation of display and data is with the use of resources. Every element has a Resources property, which is a simple dictionary.[2] Resources provide a simple hierarchy lookup of a value via a key. Theming, styling, and data binding all leverage this simple technique.

When writing code in C#, we can easily define a variable for later use. Often we do this for code readability; sometimes we do it for sharing:

image

In markup this is a harder problem. The XAML model for parsing requires that all created objects be set as properties of something else. If we wanted to create a brush and share it with two buttons, we would have to create some type of control to control binding, which seemed like a big leap just to create a variable. Instead, the WPF team created a common property—Resources—which can hold a list of objects with a name. These variables can then be used by any child element. Here we define a brush in the window’s Resources property, and use it in the button:

image

With element composition, we wanted this lookup to be hierarchical. If an element’s direct parent doesn’t contain a variable, then the next parent up is used, and so on.

In this example we will override a resource at multiple levels of the element tree. First we define the value toShare at the root Window element, just like before. Then we can override the resource at the nested StackPanel object by creating a resource with the name toShare. The various buttons will all reference toShare and will get the correct value according to their location in the tree (Figure 6.1):

image

Figure 6.1. Overriding resources in the element hierarchy

image

The lookup path for resources is slightly more complicated than just the element hierarchy. The process also looks at the application object, system theme, and default theme for types. The order is

  1. Element hierarchy
  2. Application.Resources
  3. Type theme
  4. System theme

Themes will be covered in more detail in Chapter 8, but the important thing to note here is that resource references can get data from many locations. For an application author, the ability to define resources at the application level is probably the most interesting aspect to this functionality.

In Chapter 2 we learned about the Application object. The Application object also has a Resources property that lets us define resources global to the application:

image

This technique allows us to share resources among pages, windows, and controls. At any level in the application we can override the default value of the resource, as we saw earlier. In general, it is best to define resources in the lowest level of scoping: If a resource will be used only within a single panel, it should be defined at that panel. If the resource will be used in several windows, it should be defined at the application level.

When we define a resource, it is important to remember that it may be used in more than one location. Because elements in WPF can appear in only one location in the tree, we cannot reliably use an element as a resource:

image

This markup will work fine, provided that sharedTextBox is referenced only once. If we try to use the resource a second time, the application will fail at runtime:

image

In Chapter 3 we learned about the fix for cases in which a resource needs to be used more than once: FrameworkElementFactory. For elements in control templates, we create a factory instead of creating the elements directly. Most visual objects (brushes, pens, meshes, etc.) don’t require a factory, because they support multiuse by virtue of deriving from Freezable.[3]

You may be asking yourself, “Why are we talking about resources in a chapter about data binding?” When using static resource references, we are effectively performing a variable assignment, just as in the first C# example we saw. Once a variable is used, there is no connection to the original variable. Consider the following C#:

image

When using resources, we can either perform this static assignment style of binding, or create a dynamic binding:

image

Because we used a dynamic resource binding this time, we can update the color of the button by modifying the window’s Resources property:

image

This functionality is very powerful. Combined with the hierarchical scoping of resources, it can be used to cause updates across all the windows or pages in an application. To perform a dynamic resource binding in code, we use the FrameworkElement.SetResourceReference API:

image

Using dynamic resource references does introduce some performance overhead because the system must track changes in the resources. In building the resource model, the design goal was to create something that the system could use broadly for resource discovery, without introducing a large performance overhead. For this reason, resources are optimized for coarse-grained changes.

When any resource is changed, the entire tree is updated. Therefore, static and dynamic resource references can be used in many places without a cost per reference being incurred. What this means is that we should not make frequent changes of resources to update the UI. It also means that we should not worry about using lots of resource references.

Resources are a specialized form of data binding, optimized for a high number of bindings with low update frequency. Generally, data binding is optimized for a moderate number of bindings with high update frequency (including two-way binding). This more general type of data binding got the simple name; it is called just binding in WPF.

Binding Basics

Binding is simply keeping two data points in sync with each other. Data point is an abstract concept, the idea of a single “node” of data. A data point can be described in a variety of ways; generally it is a data source and query. For example, a property data point would be an object and a property name. The property name determines the property on the source object from which data should be retrieved.

In WPF the Binding class represents a data point. To construct a binding, we provide it a source (the data source) and path (the query). In this case we can create a data point that references the Text property of a TextBox object:

image

We need a second data point to keep in sync with this first one. Because WPF binding is limited to binding data only to and from the element tree, we must use the SetBinding method to define one of the data points. We call SetBinding on the data source, and we bind the data to the query[4] (ContentControl.ContentProperty in this example):
contentControl1.SetBinding(ContentControl.Content,  bind);

This example will bind the Text property from textBox1 to the Content property on contentControl1. We could write exactly the same code using XAML (Figure 6.2):

image

Figure 6.2. Binding from TextBox to ContentControl

image

Bindings declared in markup can use the ElementName property to specify the source of the binding.

Given all that we learned about content controls in Chapter 3, it should come as no surprise that we can use data binding to bind the Text property (which is a string) from TextBox to the Content property (which is an object). What might be surprising is that we can bind the Text property to something completely different, like the FontFamily property (which is not a string):

image

Figure 6.3 shows that the value is being transformed from a string into an instance of FontFamily. There are two base mechanisms for value conversion: TypeConverter, which is a base mechanism that has been in .NET since version 1.0; and the new IValueConverter. In the case of FontFamily, TypeConverter is associated with the FontFamily type, which makes the transformation occur automatically.

Figure 6.3. Binding TextBox to FontFamily

image

We can use value converters associated with a binding to perform any value transformation that we want (Figure 6.4). In this case we will take the source (a string from the Text property) and convert it into a custom object, which we’ll use in the destination (the Content property).

Figure 6.4. The conceptual model of binding using a value converter

image

We will start by creating a small custom type:

image

This could really be any type—a built-in type, a custom type, anything. The idea is that we want to convert the Text property into a particular type. To write the converter, we derive from IValueConverter and implement the two methods:

image

In more complex examples it may not be possible to implement both directions of transformation. The final step for using the value converter is wiring it up to the binding:

image

Value transformation is one part of the story, but in this example (shown in Figure 6.5), it would be nice to generate a complete display for our custom type. In Chapter 3 we saw that content controls support many ways of visualizing data. The most powerful of these is to use a data template to define a transformation from a piece of data into a display tree.

Figure 6.5. Binding using a value converter

image

Data templates take a piece of data (described by the DataType property) and build a display tree. Within that display tree, we can bind to parts of the piece of data. In this case we will build a simple template for our Human type and bind data to the Name property:

image

We can associate this template with ContentControl in a variety of ways (via resources, for example). Here we’ll set it directly on the ContentTemplate property (Figure 6.6):

image

Figure 6.6. Binding with a data template

image

A close look at the data template we built reveals that no data source is specified for our binding:
<TextBlock  Text=’{Binding  Path=Name}’  />

This is possible because WPF allows an ambient data context to be associated with elements. With data templates, the data context is automatically set to be the data that the template is transforming. We can explicitly set the DataContext property on any element, and that data source will be used for any bindings on that element (or its children).

Data points and transformation are the two fundamental constructs of binding. Now that we’ve seen all the basic ingredients of data, we can drill into the details of binding against CLR objects, XML, and then into the depths of the data template system.

Binding to CLR Objects

As we have seen, we bind data to CLR objects via properties and lists (the latter defined as any type implementing IEnumerable).

Binding establishes a relationship between a source and a target. In the case of object binding, the selected item for the source is determined by a property path, which is a series of named properties or indexes separated by dots. I choose my words very carefully here: Initially the property path associated with binding can often be confused with C# syntax because in the basic form it is very similar. The syntax for the binding property path is the same syntax that we saw in the discussion of storyboards in Chapter 5, and we’ll see more of it in Chapter 8 when we talk about styles.

The property name identifier for object binding has two forms: one for simple CLR properties and another for WPF’s DependencyProperty-based properties. To understand the difference between these two models, let’s start with a simple example:

image

Here we bind the Text property of one TextBox object to the Text property of another. We saw a very similar example earlier. Because Text is a dependency property, this first example is the same as the following:

image

In this second case we are using the “class-qualified” form of property identifier. Behaviorally, these examples will produce the same effect, but the second will avoid using CLR reflection to resolve the name “Text” in the binding expression. This optimization is useful for two reasons: first, to avoid the performance impact of using reflection; and second, to enable binding to attached properties.[5]

To gain a better understanding of how the property path works, we can use a slightly more complex object. In this case we will define a Person object that has complex Address and Name values (Figure 6.7 on page 324).

Figure 6.7. The object model of a Person object. Person has a single name and zero or more addresses.

image

These three classes—Person, Name, and Address—provide a small amount of object model but demonstrate a number of interesting challenges with binding. First let’s create a small display for a person (Figure 6.8 on page 325):

image

Figure 6.8. Complex binding paths

image

This example illustrates simple property binding (Path=Name) and more complex paths (Path=Addresses[0].AddressName). Using square brackets—[ and ]—allows us to access items in a collection. Notice also that we can compose an arbitrarily complex path by combining property and list accessors.

We bind to a list in the same way that we bind to a property. The property path needs to result in an object that supports IEnumerable, but otherwise the process is identical to property binding. We could replace displaying a single address by adding a list box to the display and binding to ItemsSource (of course, we could then define a data template for address type):

image

Up to now we have focused on the display of data. If a property to which we bind has a setter, then two-way binding is possible.

Editing

To edit values, we must have a way to know when the value has been updated. In addition to supporting a property setter, several interfaces allow an object or list to broadcast change notifications. It is important for data sources to provide change notification (i.e., a signal that their values have changed) to allow the binding system to respond when the data is modified. To enable our Person object model to support change notification, we have three options: (1) implement INotifyPropertyChanged, (2) add events that report changes to properties, or (3) create DependencyProperty-based properties.

The pattern of using events to report property changes was first introduced in .NET 1.0, and it is supported by Windows Forms and ASP.NET data binding. INotifyPropertyChanged was introduced in .NET 2.0. This interface is optimized for data-binding scenarios; it has better performance characteristics and is easy for the object author and data-binding system to use. However, INotifyPropertyChanged is somewhat harder for normal change notification scenarios.

Using a DependencyProperty-based property is relatively straightforward, and it allows the object to benefit from having sparse storage and plug into other services in WPF (dynamic resource binding and styling, for example). Creating objects with DependencyProperty-based properties is covered in detail in the appendix.

In the end, all three techniques provide the same runtime behavior. Generally, when creating data models we should implement INotifyPropertyChanged. Using DependencyProperty-based properties adds the requirement of deriving from DependencyObject, which in turn ties the data object to an STA thread. Events for reporting property changes bloat the object model and generally should be implemented only for the properties that developers would commonly listen to:

image

Once we apply the same pattern to each of the three data objects, we can update the UI to leverage the newly updatable objects. We can use two read-only text fields (TextBlock) to display the current name, and two TextBox objects to edit the values. Because Name supports INotifyPropertyChanged, when the values are updated the binding system will be notified, causing it to update the TextBlock objects (Figure 6.9 on page 328):

image

Figure 6.9. Editing an object using two-way data binding

image

Typing in either text box updates the appropriate display. Notice that we have to tab out of the text box to see the update. By default, TextBox updates the data when it loses keyboard focus. To change this setting, we can specify that the binding should update whenever the property changes value, using the UpdateSourceTrigger property on the binding:

image

Lists are more complicated than just property changes. For efficient binding we need to know when items are added or removed to the list. For this purpose WPF has INotifyCollectionChanged. INotifyCollectionChanged offers a single event, CollectionChanged, which has the following event arguments:

image

In this sample object model, we might want to support the dynamic addition and removal of addresses from a person’s record. The simplest way to accomplish this is to use ObservableCollection<T>, which derives from the common Collection<T> class and adds support for INotifyCollectionChanged:

image

We can then modify our little application to display all the addresses in a list box, and provide some UI for creating new addresses and adding them to the list:

image

image

Running this example yields something like Figure 6.10.

Figure 6.10. Editing a list using two-way binding

image

New information can be typed into the text boxes, and pressing Add will update the list of addresses. Because the Addresses property implements INotifyCollectionChanged, the data-binding engine is notified of the additional item and updates the UI correctly.

Binding to CLR objects is mostly automatic. By adding support for INotifyPropertyChanged and INotifyCollectionChanged, we can create richer data sources. WPF binding also has built-in support for binding to XML.[6]

Binding to XML

WPF’s XML support is rooted in the Document Object Model (DOM) provided in the System.Xml namespace. We can bind to part of an XML document using any XmlDocument, XmlElement, or XmlNode object as the source. Properties can be bound only to an attribute or to the content of an element; lists can be bound to any set of elements.

XPath 101

If you aren’t familiar with XPath, it’s worth spending a few minutes to become familiar with the syntax because WPF’s binding relies heavily on it. Effectively, XPath is a query language for filtering a subsection of an XML document. We can filter down to a set of elements, a single element, or even a single attribute on an element.

The first and most common operator in XPath is the/operator. This operator allows us to build a path to the element that we want. Let’s consider the following XML document:

image

Using the/operator, we can build a path to any element. To select the CD element, we would use Media/CD as the path. By default, any text name in the path refers to the local name of elements. For complex XML documents, we need to include the XML namespace, but for this basic tutorial we’ll stick with basic documents. XPath’s Media/CD actually says, “Select all the elements with the element name ‘CD’ that are direct children of any elements named ‘Media’.” To see this more clearly, let’s look at what is generated when Media/Book is selected:

image

All the Book elements were selected. The idea that XPath can produce either a list of nodes or a single node is very important as we progress into XML binding.

In XML, elements and attributes are both considered XML nodes. XPath actually works by selecting nodes, not just elements. To refer to an attribute name instead of an element name, we use the @ operator. So the XPath Media/DVD/@Title will return “Fish: The Movie”. Again, just as with element name selection, we can get a list of attributes. If we use Media/Book/@Title, we will get the following:

image

The data type of each node returned in this case is XmlAttributeNode (which will be important a little farther down the line). Using the XPath operator *, we can get any named nodes (attributes or elements).

The final basic XPath concept is the [] operator. With this operator we can select a node either by position or by attribute. All the concepts we’ve learned so far apply, but now we can qualify any name. Note that XPath indexes are one-based, not zero-based like CLR collections. The simplest usage is by position—for example, Media/Book[1]:

<Book  Author=’John’  Title=’Fish  are  my  friends’  />

The other common usage is to select by attribute—for example, Media/Book[@Author  =  "Jane"]:

<Book  Author=’Jane’  Title=’Fish  are  my  food’  />

Of course, we can combine all of these to create complex queries across heterogeneous data. */*[@Title  =  "Fish:  The  Movie"] will pick any element with the appropriate title—in this case, the DVD. Table 6.1 (on page 334) lists the common XPath queries and provides examples from our sample document.

Table 6.1. Results of Using Various XPath Queries against an XML Document

image

image

XML Binding

Now that we understand the basics of XPath, we can return to XML binding. We can begin by binding to the same XML document as earlier. We’ll start without using any real “binding” to speak of (Figure 6.11 on page 335):

image

Figure 6.11. Selecting nodes from an XML document

image

The important point here is that binding is an integrated part of WPF. The model for list and content controls natively supports displaying arbitrary data. Data binding is really a way to move the imperative code just displayed into a more tool-friendly declarative form. If we switch this code over to using binding, it looks very similar (in fact, Figure 6.11 shows exactly what this looks like):

image

An important difference between the two examples is that, once we begin using binding, we can track changes. If we update or modify the XmlDataProvider object, ListBox will automatically be updated. In addition, by not basing the selection on imperative code, we have moved it into a regular object with known semantics, improving the support that tools can provide and reducing the number of concepts that a developer must understand.

The markup version of the same code is relatively straightforward:

image

XmlDataProvider serves two purposes: It is a markup-friendly way to build an XmlDocument object and apply an XPath (we can filter directly on the data source), and it is a common way of moving XML data into a data source (later we’ll see other data source types). Often we can simply use an XmlDocument or XmlElement object as the source for binding, with no need to create the XmlDataProvider object.

Note the following important detail: The Source property of the binding is set via StaticResource. The Binding object itself isn’t an element, so we cannot set its properties using a dynamic resource. If the data source must be determined dynamically (either with a dynamic resource reference, or via binding to determine the data source), we can alternatively use the DataContext property (which has the added benefit of simplifying the binding expression):

image

Now that we’ve reviewed object and XML binding, the next major thing to investigate is data templates, which provide us with the ability to visualize our data. In all the examples so far, we’ve been using data templates, though sometimes undetectably.

Data Templates

As we’ve already seen, data templates allow us to define how a piece of data will appear. In this section we’ll use XML binding, but everything we do here can be applied to binding to any type of data.

To understand data templates, let’s jump back to the code example that we had for binding. Instead of binding directly to the Title attribute, we’ll bind to the elements:

image

This markup produces an apparently empty list box that, in reality, contains three empty elements, as clicking inside the list reveals. Because no template is associated with XmlElement, the system simply calls ToString on it, which returns an empty string because there are no child elements under “Book”.

The DataTemplate type resembles ControlTemplate quite a bit. They both leverage FrameworkElementFactory to define the display tree. ControlTemplate defines a display tree for a control, within which we use template binding to wire up display properties to control properties. DataTemplate, however, defines a display tree for a data item, within which we use data binding to wire up display properties to data properties. DataTemplate also automatically sets the data context of the display tree to be the template data item.

To build our first template in code, let’s create a simple text display with the title of the book. We create a DataTemplate object associated with XmlElement as the data type:

image

Next we have to create the visual tree for the template:

image

We need to bind the Text property so that TextBlock will be the correct XPath statement:

image

Notice that there is no need to declare a data source. The DataContext property for the instantiated visual tree is automatically set to the item to which the template is being applied. We can now associate the factory with the template:

image

The last step is to set the ItemTemplate property on ListBox to reference our newly defined template. Putting all this together, we have a simple application that displays the book titles:

image

DataTemplate and ControlTemplate have very similar functionality and usage, and they use many of the same patterns. These resource references are often used to bind the ItemTemplate property of a list to the template instead. All of the code we just wrote would have been much simpler if we had written it using XAML instead:

image

Templates are an extremely powerful mechanism for visualizing data. In addition to building complex templates, we can also create multiple templates and switch between then dynamically.

Template Selection

Typically we associate a template with a specific piece of data, but often we want to dynamically determine what template to use—on the basis of either a property value (although in Chapter 7 we’ll learn about triggers, which help with this) or a global state. When we really need to replace the template wholesale, we can use DataTemplateSelector.

DataTemplateSelector provides a single method, SelectTemplate, that allows us to perform any logic we want to determine which template to return. We can find the template in the contained element (i.e., ListBox) and return some hard-coded templates, or even dynamically create a template for each item.

We can start by creating a class that derives from DataTemplateSelector and performs some logic to select among several templates. In this case we’ll just look at the LocalName property of XmlElement and retrieve a resource with that name from the container:

image

To initialize all the templates, we will build three templates: brown rectangles for books, silver circles for CDs, and blue circles for DVDs. Because the template selector will look at the local name of the XmlElement object, we need to set x:Key for each template:

image

The remaining change is to associated the template selector with the list box instead of with the static template (and we’ll select all media, not just books):

image

This markup yields something like Figure 6.12.

Figure 6.12. Binding using a template selector

image

Advanced Binding

Hierarchical Binding

So far, the data that we’ve examined has all been relatively flat: lists of customers or media. Data is often hierarchical, like a generic XML tree or file system. Because WPF supports element composition, our first thought might be to simply leverage element composition to build a tree of elements.

To try this out, we can start by querying the file system. We need to define a simple template, and we will populate the data into a list box. Because ItemsSource expects a collection, we will take our initial directory and wrap it in an array:

image

Running this code will produce the rather uninteresting results shown in Figure 6.13. What we really want to do is drill into the directory structure.

Figure 6.13. A list box displaying our one directory

image

The System.IO.DirectoryInfo class doesn’t have a handy property for retrieving the child items from the directory, so we will create a value converter to retrieve the necessary data:[7]

image

Now we can switch from binding to just the Name property of DirectoryInfo, to getting the child items of the directory. Inside of our data template we will nest a list box, within which we will define another data template (yes, it’s getting that weird!):

image

Now we have the hierarchy shown in Figure 6.14 (on page 346).

Figure 6.14. Simple hierarchical binding using nested data templates

image

We still aren’t quite there. To get the entire structure of the document, we need to add more and more copies of the template for each level of hierarchy that we want to display. Copying this a couple more times will yield the display shown in Figure 6.15 (on page 346).

Figure 6.15. More hierarchy using more nested data templates

image

Although this approach does work, it has some problems. First, no control has knowledge of the hierarchy. Think about a control like TreeView or Menu that has inherent hierarchy; to work correctly, the control needs to see the hierarchy. Second, the template must be copied for each level of hierarchy that we want to support.

To start, let’s fix the copy problem. Instead of defining the template with ListBox, we can pull the definition out as a resource and then use a resource reference to introduce recursion (this is just like having a function that calls itself):

image

Wait! Where is the recursion? Remember that data templates can be discovered by the DataType property. This means that when ListBox sees an item of type DirectoryInfo, it will automatically find the template that we just defined. Within that template is a nested list box that will do the same thing. Voilà! The application will look identical to Figure 6.15, but we avoided the manual cloning of the template.

For a control, like TreeView or Menu, that natively supports hierarchy, we don’t want to nest dozens of control objects together. To expose the hierarchical nature of some data to the control, we can use HierarchicalDataTemplate. This is really just an instance of DataTemplate with an added set of properties, as we would expect on an ItemsControl object:

image

In the case of a simple recursive template, as we have with the file system, we can use the fact that data templates are automatically associated to a type and create an incredibly simple template:

image

At first blush this is a bit confusing: The content of HierarchicalDataTemplate is the template for the item. This is what we want displayed for each directory. The ItemsSource property tells the template how to retrieve the child items that should be displayed. Because the children are also DirectoryInfo objects, the data-binding system will automatically apply the same template. Figure 6.16 shows the result.

Figure 6.16. Using HierarchicalDataTemplate to provide data to a TreeView control

image

We can use HierarchicalDataTemplate even when the data is not uniform. For each type in the hierarchy, we can define a different template (instead of reusing the same one).

Collection Views

Up to now we have talked about three objects in play to perform binding: the data source, the binding, and the target element. With list binding there is actually a fourth player: the collection view. This view is responsible for keeping track of the current item (currency management), as well as filtering, sorting, and grouping the data. We can think of the collection view as a lightweight wrapper on the underlying data that allows us to have multiple “views” on top of the same data. For small lists, like the ones used in this book, this isn’t so important, but with larger data sets it becomes increasingly important not to load the data into memory more than once.

Currency Management

The most important service that a collection view supplies is currency management, tracking the current item in a list. Whenever we bind to a list of data, a default collection view is created behind the scenes to track the current item. The simplest way to see how currency management works is to use the IsSynchronizedWithCurrentItem property on a list control. This property synchronizes the list selection with the current item in the view.

Typically we bind a collection to a collection-valued property (like ItemsSource). We can bind the entire collection, an indexed element from the collection, or a property of the current item. We’ve already seen the entire collection and indexed element binding; to bind to a property of the current item, we simply name the property. The current item is implied by the lack of square brackets:

image

The window that is displayed will look something like Figure 6.17, and as we select an item in the list box, the text block will update.

Figure 6.17. Using ListBox to show currency management

image

In this case we are using the default view for the names collection. We can access this view to programmatically manipulate it using the GetDefaultView static method of the CollectionViewSource type:

image

Currency management is the most important thing that a collection view does. The remaining features focus more on providing a virtual view over the data. This virtual view allows for controls (like a grid) to provide some default features (like clicking a column header to sort).

Filtering, Sorting, and Grouping

Filtering is the simplest of the view features: We provide a delegate callback that determines whether an item should be visible to the controls. As with any of the view features, we can either modify or use the default view, or we can create a new view. To make it apparent how items are being handled, here we will have two lists: one with the default view (unmodified) and the other having a custom view with tweaks.

First let’s create the custom view, filtering out anyone named Don:

image

To display the view, we can use it as the ItemsSource property for a list. Figure 6.18 shows that, as a result, only one entry is removed from the list:

image

Figure 6.18. Filtering items out of a view

image

Sorting is also very easy. By adding one or more SortDescription objects, we can sort the list by any number of properties. Here we’ll sort by last name and then first name (Figure 6.19 on page 354):

image

Figure 6.19. Sorting items in a view

image

The final feature of collection views, grouping, requires support from both the collection view and the control that is binding to the data. On the collection view side, grouping is handled almost exactly like sorting: We add one or more GroupDescription objects to define how the data will be translated into groups:

view.GroupDescriptions.Add(new  PropertyGroupDescription("Name.Last"));

One the control side, we need to provide a way to visualize the groups. All the WPF list controls support a GroupStyle property for this purpose (Figure 6.20):

image

Figure 6.20. Grouping items in a view

image

This example uses the PropertyGroupDescription class, which determines grouping on the basis of a property value. It is relatively easy to create custom grouping logic by deriving from GroupDescription and overriding the GroupNameFromItem method.

Converting this into markup is relatively straightforward, but there are some interesting gotchas. To describe a collection view in markup, we must use the CollectionViewSource type:

image

To wire up data to the collection view source, we need to set the Source property. In the spirit of the original example, we will describe the source as an array of Person objects. We will also set the data context on the window:

image

We must use StaticResource to set Source because it is not a dependency property. We must use DynamicResource to set DataContext on the window in this case because the definition for dataSource is provided inside of the window (DynamicResource enables this type of forward referencing).

Filtering on CollectionViewSource uses an event instead of a simple delegate callback, so we need to update the C# definition of our filter slightly:

image

Adding the filter then becomes as simple as wiring up an event handler to CollectionViewSource:

image

Sorting is complicated only by the fact that SortDescription is defined in the namespace System.ComponentModel, which isn’t included in the normal WPF xmlns:

image

Grouping is trivial, and exactly how we would expect:

image

We wire up the list box to the collection view source using the standard ItemsSource property. Notice that, although a collection view can be used directly as the ItemsSource property, we must use a Binding object to wire up the collection view source:

image

Collection views enable efficient sharing of data, as well as multiple views for the user. Although it isn’t necessary to use collection views in every binding scenario, effective use of views can greatly increase the performance of an application.

Data-Driven Display

For most applications, people build a simple UI, create a data model, and wire up the two using data binding. One of the more powerful things we can do with WPF is to reverse the typical relationship: The data can be the center of an application, and the UI can be secondary. The power of this reversed relationship is that we can build rich data visualizations independent of the usage. Three classes—ContentControl, ItemsControl, and DataTemplate—are the key to this model.

Let’s begin with a simple data model: an image. In Chapter 5, we saw that there are two primary data types for dealing with images: the Image control and the ImageSource class hierarchy (including BitmapImage, which is used frequently). The Image control is a typical UI first model for dealing with images. The simplest usage is to set the Source property to a URI:

<Image  Source=’c:dataookimagesflowers.jpg’  />

Source is really a property of type ImageSource, and we can directly specify BitmapImage if we want to control detail-loading properties (like decode resolution):

image

Now we can reverse the model: Instead of using the Image control and populating it with data, let’s start with the data (the image) and figure out how to display it. Because this is only one image, we will use ContentControl:

image

Running this code produces something like Figure 6.21—relatively uninteresting.

Figure 6.21. Displaying a bitmap image with ContentControl

image

What happened here is that the default logic for ContentControl saw a piece of data, in this case the bitmap data, and called ToString to produce a display. By adding a date template, we can make this look much more interesting. To start, we will define a simple template that uses an Image control to display the image (Figure 6.22):

image

Figure 6.22. Displaying a bitmap image with a more interesting template

image

As Figure 6.22 shows, the display is now more interesting. Effectively, though, this is identical to our original use of the Image element. We can make this more interesting by adding a richer display (Figure 6.23 on page 360):

image

image

Figure 6.23. Displaying a bitmap image with more chrome

image

We could take this even further, adding panning controls or other types of adornments (Figure 6.24).

Figure 6.24. More complex chrome, including actions and additional binding

image

The key thing to note here is that the data model for the image has not changed. Imagine for a moment that, instead of using BitmapImage as the data, we were using a Customer object. We could create the data model for that customer, with the correct properties, behavior, and so on. Then we could build various user interfaces to interact with that data.

The great thing about building a UI from the data up is that rich views of the data can be reused everywhere in the application. We can use this technique of “data first” programming on many levels. For example, suppose we wanted to display a list of images. At first, we might be tempted to use a list box and hard-code the list of images. This is relatively simple for the first place that we want to display a list of images, but if we plan to display a list in multiple places, we might want to reuse the definition of how we display the list. We might want a list box that scrolls horizontally or vertically, or maybe a list view instead, with column details. If we start by defining a new type for the image list, we can create a template for it just as we did for a single image:

image

As we’ve already seen in this chapter, ObservableCollection is a list implementation that supports change notification and the necessary patterns for working easily in markup. We can now set the content of our window to be simply a list of images:

image

To specify how this list will appear (Figure 6.25), we do exactly what we did for the single image:

image

Figure 6.25. Reusing a complex template in a list

image

By moving to a data-driven model for defining the display of an application, we create a clear separation between the data model and the UI model of the program. As a result, it is much easier to change the look of the application. Having an explicit contract between the data model and UI model also makes it easier to understand when we make a change that might break the UI display.

Where Are We?

In this chapter we have looked at how WPF deals with data in an application. WPF’s data-binding system is deeply integrated in the platform, and with the proper model we can create applications with a completely data-driven approach.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset