Windows Presentation Foundation

Hello, WPF

WPF from Scratch
Navigation Applications
Content Model
Layout
Controls
Data Binding
Dependency Properties
Resources
Styles and Control Templates
Graphics
Application Deployment
Where Are We?

Layout

Introduction
Layout Basics
DockPanel
StackPanel
Grid
Canvas
Viewbox
Text Layout
Common Layout Properties
When Content Doesn't Fit
Custom Layout
Where Are We?

Controls

Introduction
What Are Controls?
Handling Input
Built-In Controls
Where Are We?

Data Binding

Introduction
Without Data Binding
Data Binding
Binding to List Data
Data Sources
Master-Detail Binding
Where Are We?

Styles and Control Templates

Introduction
Without Styles
Inline Styles
Named Styles
Element-Typed Styles
Data Templates and Styles
Triggers
Control Templates
Where Are We?

Resources

Introduction
Creating and Using Resources
Resources and Styles
Binary Resources
Global Applications
Where Are We?

Graphics

Introduction
Graphics Fundamentals
Shapes
Brushes and Pens
Transformations
Visual-Layer Programming
Video and 3-D
Where Are We?

Animation

Animation Fundamentals
Timelines
Storyboards
Key Frame Animations
Creating Animations Procedurally
Where Are We?

Custom Controls

Introduction
Custom Control Basics
Choosing a Base Class
Custom Functionality
Templates
Default Visuals
Where Are We?

ClickOnce Deployment

A Brief History of Windows Deployment
ClickOnce: Local Install
The Pieces of ClickOnce
Publish Properties
Deploying Updates
ClickOnce: Express Applications
Choosing Local Install versus Express
Signing ClickOnce Applications
Programming for ClickOnce
Security Considerations
Where Are We?

Data Sources

4.4. Data Sources

So far, we've been dealing simply with objects. However, that's not the only place that data can come from; XML and relational databases spring to mind as popular alternatives. Further, since neither XML nor relational databases store their data as .NET objects, some translation is going to be needed to support data binding which, as you recall, requires .NET properties on data-source objects. And even if we can declare objects directly in XAML, we'd still like a layer of indirection for pulling objects from other sources and even pushing that work off to a worker thread if said retrieval is a ponderous operation.

In short, we'd like some indirection away from the direct declaration of objects for translation and loading. For this indirection, we have but to turn to IDataSource implementations, one of which is the object data source.

4.4.1. Object Data Source

An implementation of the IDataSource interface provides a layer of indirection for all kinds of operations that produce objects against which to data-bind. For example, if we wanted to load a set of Person objects over the Web, we could encapsulate that logic into a bit of code, such as Example 4-34.

Example 4-34. A type to be used by ObjectDataSource
namespace PersonBinding {
  public class Person : INotifyPropertyChanged {...}
  public class People : ObservableCollection<Person> {}

  public class RemotePeopleLoader : People {
    public RemotePeopleLoader(  ) {
      // Load people from afar
      ...
    }
  }
}

In Example 4-34, the RemotePeopleLoader class derives from the People collection class, retrieving the data in the constructor, because the object data source expects the object it creates to be the collection, as in Example 4-35.

Example 4-35. Using the ObjectDataSource

<Window.Resources>
  ...
  <ObjectDataSource
    x:Key="Family"
    TypeName="PersonBinding.RemotePeopleLoader"
    Asynchronous="True" />
</Window.Resources>
<Grid DataContext="{StaticResource Family}">
  ...
  <ListBox ItemsSource="{Binding}" ...>
</Grid>

The ObjectDataSource element is most often placed in a resource block to be used by name elsewhere in the XAML. The TypeName property refers to the fully qualified type name of the class that will be the collection.

Most of the classes in WPF that take type parameters, such as the DataType property of the DataTemplate element, can be set with the type markup extension, which includes class, namespace, and assembly information using the mapping syntax:

<!-- set up DataTemplate for Bar.Quux in assembly foo -->
<?Mapping
  XmlNamespace="local"
  ClrNamespace="Bar"  Assembly="foo" /><Window ... xmlns="local">
  <Window.Resources>
    <DataTemplate
      DataType="{x:Type local:Quux}">...</DataTemplate>
  </Window.Resources>

  ...
</Window>

However, the ObjectDataSource takes its type information in its own form:

<!-- set up ObjectDataSource for Bar.Quux in assembly foo -->
<ObjectDataSource x:Key="foo" TypeName="Bar.Quux, foo" />


One can only hope that the two techniques will be rationalized by RTM.


With an object data source acting as an intermediary between the data and the bindings, we need to update our code when we're retrieving the People collection (now a base class of the RemotePeopleLoader, but still the container of our Person objects), as in Example 4-36.

Example 4-36. Accessing the data held by an object data source
public partial class Window1 : Window {
  ...
  ICollectionView GetFamilyView(  ) {
    IDataSource 
 ds = (IDataSource)this.FindResource("Family");
    People people = (People)ds.Data;
    return BindingOperations.GetDefaultView(people);
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    Person person = (Person)view.CurrentItem;

    ++person.Age;
    MessageBox.Show(...);
  }

  void addButton_Click(object sender, RoutedEventArgs e) {
    IDataSource ds = (IDataSource)this.FindResource("Family");
    People people = (People)ds.Data;
    people.Add(new Person("Chris", 35));
  }
}

Since the Family resource is now an ObjectDataSource, itself an implementation of the IDataSource, in Example 4-36, when we need the People collection, we're casting to IDataSource on the Family resource and pulling the collection out of the Data property.

Even though the object data source exposes its data from the Data property, that doesn't mean that you should bind to it. If you notice from Example 4-35, we're still binding the listbox as before:

<!-- do not bind to Path=Data -->
<ListBox ItemsSource="{Binding}" ...>

The reason this works is because WPF has built-in knowledge of IDataSource, so there's no need for you to do the indirection yourself.


4.4.1.1. Asynchronous data retrieval

In Example 4-35, we applied the Asynchronous property, which is easily the most interesting piece of functionality that the object data source gives us that we lack when we declare object graphs directly in XAML. When the Asynchronous property is set to true (the default is false), the task of creating the object specified by the TypeName property is handled on a worker thread, only performing the binding on the UI thread when the data has been retrieved. This is not the same as binding to the data as its retrievede.g., from a stream over the networkbut it's better than blocking the UI thread while a long retrieval happens.

4.4.1.2. Passing parameters

In addition to the Asynchronous property, the object data source also provides the Parameters property, which is a comma-delimited list of strings to be passed as string arguments to the type created by the object data source. For example, if we wanted to pass in a set of URLs from which to try and retrieve the data, we could use the Parameters property as in Example 4-37.

Example 4-37. Passing parameters via ObjectDataSource
<ObjectDataSource
  x:Key="Family"
  TypeName="PersonBinding.RemotePeopleLoader"
  Asynchronous="True"
  Parameters="http://sellsbrothers.com/sons.dat,
              http://sellssisters.com/daughters.dat" />

In Example 4-37, we've added a list of two URLs, which will be translated into a call to the RemotePeopleLoader constructor that takes two strings, as in Example 4-38.

Example 4-38. Accepting arguments passed by ObjectDataSource
namespace PersonBinding {
  public class RemotePeopleLoader : People {
    public RemotePeopleLoader(string url1, string url2) {
      // Load People from afar using two URLs
      ...
    }
}

Unfortunately, if we put other data types into the list of parameters supported by the object data source's Parameters property, like integers, they will not be translated, even if constructors of the appropriate types are available; the object data source only supports the creation of objects with constructors taking zero or more strings. If any data conversion is necessary, you will have to do it.

4.4.2. XmlDataSource

As I mentioned, while objects are the only thing that data binding supports, data isn't only stored as objects in the world. In fact, most data isn't stored in objects. One increasingly popular way to store data is XML. For example, Example 4-39 shows our family data represented in XML.

Example 4-39. My family rendered in XML
<!-- family.xml -->
<Family xmlns="">

  <Person Name="Tom" Age="9" />
  <Person Name="John" Age="11" />
  <Person Name="Melissa" Age="36" />
</Family>

With this file available in the same folder as the executing application, we can bind to it using the XmlDataSource, as shown in Example 4-40.

Example 4-40. An XmlDataSource in action
<!-- Window1.xaml -->
<Window ...>
  <Window.Resources>
    ...
    <XmlDataSource
      x:Key="Family"
      Source="family.xml"
      XPath="/Family/Person" />
  </Window.Resources>

  <Grid DataContext="{StaticResource Family}">
    ...
    <ListBox ... ItemsSource="{Binding}">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">

            <TextBlock TextContent="{Binding XPath=@Name}" />
            <TextBlock TextContent=" (age: " />
            <TextBlock TextContent="{Binding XPath=@Age}" ... />
            <TextBlock TextContent=")" />

          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    <TextBlock ...>Name:</TextBlock>

    <TextBox Text="{Binding XPath=@Name}" .../>
    <TextBlock ...>Age:</TextBlock>
    <TextBox Text="{Binding XPath=@Age}" ... />

    ...
  </Grid>
</Window>

Notice the use of the XmlDataSource with a relative URL that points to the family.xml file and the XPath expression[*] that pulls out the Person elements under the Family element root. The only other thing that changes in the XAML file from the use of the ObjectDataSource is that when binding Name and Age to TextBlock and TextBox controls, we use XPath statements instead of Path statements.

[*] An explanation of the XPath syntax is beyond the scope of this book, but for a good reference, I'd start with Essential XML Quick Reference by Aaron Skonnard and Martin Gudgin (Addison Wesley).

4.4.2.1. XML island data

If you happen to know your data at compile time, the XML data source also supports "data islands " in the same way that XAML creating objects directly does, as in Example 4-41.

Example 4-41. An XML data island in XAML
<XmlDataSource x:Key="Family" XPath="/Family/Person">
  <Family xmlns="">

    <Person Name="Tom" Age="9" />
    <Person Name="John" Age="11" />
    <Person Name="Melissa" Age="36" />
  </Family>
</XmlDataSource>

In Example 4-41, we've just copied the contents of family.xml under the XmlDataSource element, dropping the Source attribute but leaving the XPath statement.

However, now that we're using XML instead of object data, some of the operations in our sample application need changing, such as accessing and/or changing the current item (as we do in the Birthday button implementation), adding a new item, and sorting items or filtering items. In short, anything where we assumed a collection of Person objects needs to change. On the other hand, moving between items using the ICollectionView.MovingCurrentToXxx( ) family of methods continues to work just fine, as does our AgeToForegroundValueConverter.

Our implementation of IValueConverter.Convert continues to work because we parsed the string value of the object instead of casting directly to an Int32. A cast would have been preferred in the Person object case, because Age was of type Int32 and parsing it was unnecessary. However, in the XML case and in the absence of any application of an XSD, Age is of type String, so the parsing is necessary.


4.4.2.2. XML data sources and item access

To access and manipulate items in an XML data source, instead of instances of your custom type, you'll be using instances of the XmlElement class from the System.Xml namespace, as in Example 4-42.

Example 4-42. Accessing XML from an XML data source
// Window1.xaml.cs
...
namespace PersonBinding {
  public partial class Window1 : Window {
    ...

    ICollectionView GetFamilyView(  ) {
      IDataSource ds = (IDataSource)this.FindResource("Family");
      IEnumerable people = (IEnumerable)ds.Data;
      return BindingOperations.GetDefaultView(people);
    }

    void birthdayButton_Click(object sender, RoutedEventArgs e) {
      ICollectionView view = GetFamilyView(  );

      XmlElement person = (XmlElement)view.CurrentItem;
      person.SetAttribute("Age",
        (int.Parse(person.Attributes["Age"].Value) + 1).ToString(  ));
      MessageBox.Show(
        string.Format(
          "Happy Birthday, {0}, age {1}!",
          person.Attributes["Name"].Value,
          person.Attributes["Age"].Value),
        "Birthday");
    }
    ...
  }
}

The first thing to notice in Example 4-42 is that in our implementation of GetFamilyView, we're no longer looking for the People collection directly but rather some implementation of IEnumerable provided by the XML data source. IEnumerable is the simplest thing you can have in .NET and still have a collection, so that's what the GetdefaultView method requires.

Also notice in Example 4-42 that the CurrentItem property of the collection view is an instance of the XmlElement. To increment the age, we access the element's Age attribute, pull out its value, parse it as an integer, increment it, convert the whole thing back to a string, and set it as the new Age attribute value of the current element. Showing each attribute is merely another couple of attribute accesses.

4.4.2.3. XML data sources and adding items

When adding (or removing) items, it's best to get access to the XmlDataSource itself so that you can access the Document property for creating and adding new elements, as in Example 4-43.

Example 4-43. Adding an item to an XML data source

void addButton_Click(object sender, RoutedEventArgs e) {
  XmlDataSource xds = (XmlDataSource)this.FindResource("Family");
  XmlElement person = xds.Document.CreateElement("Person");
  person.SetAttribute("Name", "Chris");
  person.SetAttribute("Age", "35");
  xds.Document.ChildNodes[0].AppendChild(person);
}

Here, we're using the XmlDataSource to get to the XmlDocument and then using the XmlDocument to create a new element called Person (to fit in with the rest of our Person elements), setting the Name and Age attributes, and adding the element under the Family root element (available at ChildNodes[0] on the top-level Document object).

4.4.2.4. XML data sources and sorting

Sorting XML data-source items is a matter of remembering that we're dealing with XmlElements, as in Example 4-44.

Example 4-44. Sorting XML
class PersonSorter : IComparer {
  public int Compare(object x, object y) {
    XmlElement lhs = (XmlElement)x;
    XmlElement rhs = (XmlElement)y;

    // Sort Name ascending and Age descending
    int nameCompare =
      lhs.Attributes["Name"].Value.CompareTo(
        rhs.Attributes["Name"].Value);

    if( nameCompare != 0 ) {
      return nameCompare;
    }

    return int.Parse(rhs.Attributes["Age"].Value) -
           int.Parse(lhs.Attributes["Age"].Value);
  }
}

void sortButton_Click(object sender, RoutedEventArgs e) {
  ListCollectionView view = (ListCollectionView)GetFamilyView(  );

  // Managing the view.Sort collection would work, too
  if( view.CustomSort == null ) {
    view.CustomSort = new PersonSorter(  );
  }
  else {
    view.CustomSort = null;
  }
}

In Example 4-44, we're sorting just like before, but we're pulling out the Name and Age attributes and converting as appropriate to do so.

4.4.2.5. XML data sources and filtering

XML filtering is very much like object filtering, except that we're dealing with XmlElements, as in Example 4-45.

Example 4-45. Filtering XML
void filterButton_Click(object sender, RoutedEventArgs e) {
  ICollectionView view = GetFamilyView(  );

  if( view.Filter == null ) {
    view.Filter = delegate(object item) {
      return
        int.Parse(((XmlElement)item).Attributes["Age"].Value) >= 18;

    };
  }
  else {
    view.Filter = null;
  }
}

Here our filter delegate casts each item to an XmlElement to do the filtering.

4.4.3. Relational Data Source

As of the current build, WPF has no direct support for binding to relational databases, and the indirect support is not in such great shape, either. For an example of the current state of binding to relational data in WPF, I recommend the WinFX SDK sample entitled "Binding with Data in an ADO DataSet Sample."

4.4.4. Custom Data Sources

If you'd like to take advantage of the indirection that data sources provide for retrieving objects, but none of the built-in data sources tickles you, a custom implementation of IDataSource should do the trick. For example, instead of creating the RemotePersonLoader collection to load the remove family data (it was kind of hokey to add collection items in the collection's constructor, anyway), we could have created a custom implementation of IDataSource to do the magic, as in Example 4-46.

Example 4-46. A simple custom data source
namespace PersonBinding {
  public class Person : INotifyPropertyChanged {...}
  public class People : ObservableCollection<Person> {}

  public class RemotePeopleSource : IDataSource {
    People people = null;

    public RemotePeopleSource(  ) {
      // Load People from afar
      ...

      // Let data binding know we've got data
      if( DataChanged != null ) {
        DataChanged(this, EventArgs.Empty);
      }
    }

    // IDataSource Members

    // Gets the underlying data object
    public object Data {
      get { return people; }
    }

    // Occurs when a new data object becomes available
    // Especially handy for async object retrieval
    public event EventHandler DataChanged;

    // Refreshes the data source object using the most current
    // values for the object's configuration properties
    public void Refresh(  ) {
      // Not needed in our case...
    }
  }
}

In Example 4-46, we've implemented IDataSource by creating an instance of the People collection, and, after some mysterious data retrieval process in the constructor, we fire off an event to let data binding know that we've got data[*] and that it should now recheck the Data property. This protocol is especially useful if you're doing asynchronous data retrieval like the object data source does.

[*] Wouldn't "Got Data?" look nice on a T-shirt?

If your data source provides custom propertiese.g., Asynchronousit's possible that one or more of the properties could be changed at runtime. If you've got more than one property that affects data retrieval, you may not want to kick off the search for the new data until the Refresh method is called; otherwise, you may start things off after one property is changed but before the client has a chance to change the rest of them.


©2008 FAQ - WPF Labs - Discuss - Terms of Use - Privacy Policy - About WPF
- Interview Questions - Sharepoint Articles - Interview Questions Resource Library - All about LINQ - MS Knowledgebase Articles - Electronics and Hardware discussions