Archive for the 'XAML' Category

Animations in WPF

Animations in WPF

Animations in WPF are not like the cartoons that you see on TV or in the movies. In WPF an animation is a mechanism that changes the value of a dependency property over time. Animations can be used to add dramatic and appealing effects to a user interface provided that one resists the ever present temptation to overuse them. Animation is a deep topic, implemented in WPF with well over 150 classes. This post will only scratch the surface.

The example here expands on the example in my last post. In this XAML only example RenderTransforms are animated to produce quirky mouse over effects. This is what is displayed before the mouse cursor is moved over any of the images.

anim_tools

The Style for the bell image applies a RotateTransform with its origin approximately at the bell’s handle. By default it rotates the image zero degrees, so nothing is changed. However event triggers launch a Storyboard that animates the RotateTransform making the bell look like it is being rung.

<Storyboard x:Key="ringStoryboard">
  <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
    To="10" Duration="0:0:0.2"/>
  <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
    To="0" BeginTime="0:0:0.2" Duration="0:0:0.2"/>
</Storyboard>
<Style x:Key="bellImage" TargetType ="{x:Type Image}">
  <Setter Property="RenderTransformOrigin" Value="0.6,0.2"/>
  <Setter Property="RenderTransform">
    <Setter.Value>
        <RotateTransform/>
    </Setter.Value>
  </Setter> 
  <Style.Triggers>
    <EventTrigger RoutedEvent="Image.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard Storyboard="{StaticResource ringStoryboard}" />
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Image.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard Storyboard="{StaticResource ringStoryboard}" />
        </EventTrigger.Actions>
    </EventTrigger>
  </Style.Triggers>
</Style>

A few things need to be explained at this point. Storyboard is simply a class that wraps one or more animations. More importantly a Storyboard can be wrapped by a BeginStoryboard. BeginStoryboard inherits from TriggerAction, so it can be set as the action of any event or property trigger. The animation classes are not TriggerActions, so they cannot be directly set as a trigger’s action.

The animations in this Storyboard are altering the Angle property of the RotateTransform. Angle is type Double, so the class DoubleAnimation must be used to animate it. There are other animation classes for use with other data types, e.g. Int32Animation. Consult MSDN.

The first DoubleAnimation changes Angle to a value of 10 (To=”10″) over a period of 0.2 seconds (Duration=”0:0:0.2″). It changes it to 10 from whatever its present value is. Angle’s default value is 0, so that is its starting point. The second animation begins at 0.2 seconds (BeginTime=”0:0:0.2″), i.e. after the first has completed. It animates Angle back to 0 over a period of 0.2 seconds. You must download the example code and try this yourself to see the result. Just open anim.xaml in your favorite XAML editor. The zip file contains the needed images but the paths will likely be different on your machine. Just change the image paths and run the XAML.

For the tools image a ScaleTransform is animated to make the image appear to grow when the mouse cursor is moved over it. It shrinks back to normal size when the mouse cursor is moved away. Here a PropertyTrigger is used instead of EventTriggers just to demonstrate its use. EventTriggers could have been used here to achieve the same effect. Likewise a PropertyTrigger could have been used on the bell.

<Style x:Key="toolsImage" TargetType ="{x:Type Image}">
  <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
  <Setter Property="RenderTransform">
    <Setter.Value>
        <ScaleTransform/>
    </Setter.Value>
  </Setter> 
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
        <Trigger.EnterActions>
            <BeginStoryboard Storyboard="{StaticResource expandStoryboard}" />
        </Trigger.EnterActions>
        <Trigger.ExitActions>
            <BeginStoryboard Storyboard="{StaticResource shrinkStoryboard}" />
        </Trigger.ExitActions>
    </Trigger>
  </Style.Triggers>
</Style>

The pencil image also uses a ScaleTransform except that the RenderTransformOrigin is set to approximately where the pencil tip is. This makes the pencil seem to grow lengthwise instead of uniformly in two dimensions as the tools did. The thumb image uses a SkewTransform to make it appear that a “thumbs-up” is being given. The animation for the hourglass is more complex, so I’ll explain it in a future post. In the mean time you can see what it does by running the XAML example.

The images used in this example were provided by the kind folks at VistaICO.com. Source code for this example may be downloaded here.

LayoutTransform vs. RenderTransform in WPF

Every FrameworkElement in WPF, in other words every visual element that we deal with in WPF, has two properties to support display transformations, LayoutTransform and RenderTransform. RenderTransform is actually inherited from FrameworkElement’s base class, UIElement. Both LayoutTransform and RenderTransform are of type Transform.

Transform is an abstract class, and WPF provides several concrete implementations that can be applied to the two aforementioned properties in XAML and code. Some of them are:

  • RotateTransform
  • ScaleTransform
  • SkewTransform
  • TranslateTransform

So, what is the difference between LayoutTransform and RenderTransform? The two property names reveal much in this case. Any Transform assigned to LayoutTransform is applied when layout is performed. RenderTransform is applied after layout when rendering is performed. A quick XAML example will demonstrate this. Copy/paste the XAML below into XAMLPad, Kaxaml, or your favorite XAML editor. You’ll have to supply your own images or download them from the link at the bottom of this post as I haven’t uploaded them yet. Just change the path where you see <Image Source="c:/code/mywpf/anim/.../>.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>
    <!-- Style for border around each image -->
    <Style x:Key="imageBorder" TargetType="{x:Type Border}">
      <Setter Property="Width" Value="100"/>
      <Setter Property="Height" Value="130"/>
      <Setter Property="Margin" Value="4,0,0,0"/>
      <Setter Property="BorderThickness" Value="3"/>
      <Setter Property="BorderBrush" Value="Khaki"/>
      <Setter Property="Background" Value="Beige"/>
      <Setter Property="CornerRadius" Value="6"/>
      <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
    </Style>
  </Page.Resources>

  <StackPanel>
    <Label>LayoutTransform:</Label>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Top" Background="#aaa" Height="140">
      <Border Style="{StaticResource imageBorder}">
        <Border.LayoutTransform>
          <RotateTransform Angle="10"/>
        </Border.LayoutTransform>
        <Image Source="c:/code/mywpf/anim/alert.png">
        </Image>
      </Border>
    
      <Border Style="{StaticResource imageBorder}">
        <Border.LayoutTransform>
          <RotateTransform Angle="-12"/>
        </Border.LayoutTransform>
        <Image Source="c:/code/mywpf/anim/config-tools.png">
        </Image>
      </Border>
    </StackPanel>

    <Label>RenderTransform:</Label>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Top" Background="#aaa" Height="140">
      <Border Style="{StaticResource imageBorder}">
        <Border.RenderTransform>
          <RotateTransform Angle="10"/>
        </Border.RenderTransform>
        <Image Source="c:/code/mywpf/anim/alert.png">
        </Image>
      </Border>
    
      <Border Style="{StaticResource imageBorder}">
        <Border.RenderTransform>
          <RotateTransform Angle="-12"/>
        </Border.RenderTransform>
        <Image Source="c:/code/mywpf/anim/config-tools.png">
        </Image>
      </Border>
    </StackPanel>
  </StackPanel>
</Page>

The result should look like this.
transforms
A pair of Borders is created, each containing a single image. A RotateTransform is assigned to the LayoutTransform property of each border. This causes each Border and its contents to be rotated slightly, the first by positive 10 degrees and the second by negative 12 degrees. Margins, as defined in the imageBorder Style, between the Borders is maintained since the transform is applied at layout time. The Borders are fully contained within the containing StackPanel, being clipped to within the panel’s dimensions.

The Borders are repeated on the second row. This time RenderTransfom is used instead of LayoutTransform. Notice that the two Borders now overlap and are not clipped to the containing StackPanel. In this case the Borders were positioned and clipped in an untransformed state, i.e. unrotated. Then the RotateTransform was applied to each Border with the results shown here.

The images used in this example were provided by the kind folks at VistaICO.com

Consuming RESTful Web Services in WPF

It turns out that consuming the RESTful web service created in my last post is very simple when WPF’s XML data binding features are used. I have previously posted about binding to XML data in a WPF application.

My RESTful web service uses XML namespaces in its return data, so I first declare an XmlNamespaceMappingCollection in my WPF application XAML. Next I declare an XmlDataProvider for the composer list and set its Source property to the URI of my Composers RESTful operation. A second XmlDataProvider is declared for the composition data, but that is still empty at this time.

<Window.Resources>
    <XmlNamespaceMappingCollection x:Key="NamespaceMapping">
        <XmlNamespaceMapping Uri="https://patconroy.wordpress.com/data" Prefix="pc" />
    </XmlNamespaceMappingCollection>
    <XmlDataProvider x:Key="ComposerData" 
                     XmlNamespaceManager="{StaticResource NamespaceMapping}"
                     Source="http://localhost:8000/service/restbaroque/composers"/>
    <XmlDataProvider x:Key="CompositionData"
                     XmlNamespaceManager="{StaticResource NamespaceMapping}"/>
</Window.Resources>

A ListBox created on the main Window that binds to the ComposerData XML source. Note that the StringFormat property used on the MultiBinding here was introduced in the 3.5 SP1 version of the framework. It is not available in earlier versions.

<ListBox x:Name="_composerList" Margin="40,20" 
                 ItemsSource="{Binding Source={StaticResource ComposerData}, XPath=//pc:Composers/pc:Composer}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Text>
                    <MultiBinding StringFormat="{}{0} {1} {2}">
                        <Binding XPath="pc:FirstName"/>
                        <Binding XPath="pc:MiddleName"/>
                        <Binding XPath="pc:LastName"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

That’s all the work the needs to be done to display the composer list. A Click event handler for a button on the Window sets the appropriate Source for the compositions XmlDataSource.

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (_composerList.SelectedItem == null)
    {
        MessageBox.Show("Please select a composer");
    }
    else
    {
        XmlNamespaceManager ns = Resources["NamespaceMapping"] as XmlNamespaceManager;
        XmlNode node = _composerList.SelectedItem as XmlNode;
        string id = node.SelectSingleNode("pc:Id", ns).InnerText;
        string uriString = String.Format("http://localhost:8000/service/restbaroque/composers/{0}/compositions", id);
        Uri uri = new Uri(uriString);
        XmlDataProvider dp = Resources["CompositionData"] as XmlDataProvider;
        dp.Source = uri;
    }
}
 

A second ListBox on the Window binds to the compositions XmlDataProvider.

<ListBox x:Name="_compositionList" Margin="40,20" 
         ItemsSource="{Binding Source={StaticResource CompositionData}, XPath=//pc:Compositions/pc:Composition}"/>

That’s it!
restfulclient

Asynchronous Web Service Invocation

In my last post I described various ways to perform work on a background thread in a WPF smart client application. Performing long running operations on the main application thread will freeze the user interface and yield a poor user experience. On the other hand any GUI elements must be manipulated on the main application thread and only on that thread. In this post I describe three different ways to make web service calls on a background thread using WCF client components.

First I created a simple, self-hosted WCF service implementing a simple calculator interface. The Windows SDK has several example implementations of this interface. Each of the implementation methods in my service sleeps for 10 seconds to simulate a long running process.

[ServiceContract(Namespace="https://patconroy.wordpress.com/service")]
public interface ICalculator
{
    [OperationContract]
    double Add(double n1, double n2);
    [OperationContract]
    double Subtract(double n1, double n2);
    [OperationContract]
    double Multiply(double n1, double n2);
    [OperationContract]
    double Divide(double n1, double n2);
}

Those of you running Vista will need to execute the following command as administrator to update the system URL ACL, substituting your own Windows domain and userid in the appropriate place.

netsh http add urlacl url=http://+:8000/ user=domain\userid

The client application example presents four different ways of calling the web service. The main windows has four radio buttons to select from amongst them.

  1. Synchronously on the GUI thread
  2. Asynchronously using BackgroundWorker
  3. Using the Asynchronous Programming Model
  4. Using a new .NET 3.5 event based model

The first of these is certainly the easiest to code. All work occurs on the main application thread, but herein lies a problem. The user interface is frozen while the application waits for the web service to return.

Using BackgroundWorker

The second technique is certainly an improvement. BackgroundWorker is used to launch a background thread that makes the web service call. The background thread waits for the web service to return then sets DoWorkEventArgs.Result. This Result is handed to the RunWorkerCompleted delegate on the GUI thread.

private static void CallServiceBackgroundWorker(Window1 win, double n1, double n2)
{
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += BackgroundWorker_DoWork;
    bw.RunWorkerCompleted += win.BackgroundWorker_RunWorkerCompleted;
    bw.RunWorkerAsync(win);
}
// BackgroundWorker callbacks
static void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Window1 win = (e.Argument) as Window1;
    e.Result = _calcClient.Add(win._data.Number1, win._data.Number2);
}
void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // This callback is made on the GUI thread
    UpdateResult((double)e.Result);
}

Do you see a problem here? Earlier I stated that “The background thread waits for the web service.” The application consumes a background thread while waiting for the web service to finish. This background thread could be utilized for something else while the web service call is outstanding. This isn’t a problem in a small example but could be a scalability killer in a larger real-world application.

Using the Asynchronous Programming Model

This last problem is solved by the Asynchronous Programming Model. The APM pervades the .NET framework. One can, for example, read a FileStream using this method. It works roughly like this.

void CallServiceAsyncPattern(Window1 win, double n1, double n2)
{
    IAsyncResult ar = obj.BeginXyz(param, new AsyncCallback(MyCallback), state);
}
void MyCallback(IAsyncResult ar)
{
    var result = obj.EndXyz(ar);
}

The names of the Begin and End methods will of course differ from class to class. The main application thread initiates the asynchronous operation by calling BeginXyz(). The framework itself then performs the operation on a background thread or better yet by using overlapped I/O. Overlapped I/O is a Windows OS feature that completes the I/O operation in the Windows kernel. An application callback is called when the operation completes. The callback occurs on a background thread.

The .NET web service proxy generator creates a Begin and End method for each of the service interface methods when the /async command line option is specified.

svcutil http://localhost:8000/service/calculator/mex /async

Or just check “Generate asynchronous operations” when creating a service reference in Visual Studio. This example client uses BeginAdd and EndAdd. AsyncCallback is called on a background thread when the web service operation completes. AsyncCallback cannot access the GUI directly, so it dispatches the result to the GUI thread.

private delegate void UpdateResultDelegate(double n);
private static void AsyncCallback(IAsyncResult ar)
{
    double result = _calcClient.EndAdd(ar);
    Window1 win = (ar.AsyncState as Window1);
    // Result must be dispatched to the GUI thread
    win.Dispatcher.Invoke(new UpdateResultDelegate(win.UpdateResult), result);
}
private static void CallServiceAsyncPattern(Window1 win, double n1, double n2)
{
    IAsyncResult ar = _calcClient.BeginAdd(n1, n2, AsyncCallback, win);
}

Using an event based model

A new event based asynchronous model was introduced in the framework version 3.5. The service proxy generator creates all the necessary delegate declarations and event argument classes. It also adds a completed event and an XyzAsync() method to the proxy class for each of the web service methods. Specify /tcv:version35 in addition to /async on the svcutil command line to generate this additional code. This is also done automatically in Visual Studio when “Generate asynchronous operations” is checked.

The event model is an improvement over APM because the completed event is called on the same thread that initially made the asynchronous service request. In the example here AddAsync() is called on the GUI thread, so the AddCompleted event handler is called on the GUI thread after the web service returns. The need to dispatch the result to the GUI thread has been eliminated. GUI state can be manipulated within the event handler, so coding is much easier than with APM.

Event handlers must be set on the service proxy instance prior to making any service requests. This example only uses the Add service method, so only an AddCompleted event handler is set. CalculatorClient also includes SubtractCompleted, MultiplyCompleted and DivideCompleted event handlers.

private static CalculatorClient _calcClient;
static Window1()
{
    _calcClient = new CalculatorClient();
    _calcClient.AddCompleted +=new EventHandler<AddCompletedEventArgs>(CalcClient_AddCompleted);
}
private static void CalcClient_AddCompleted(object sender, AddCompletedEventArgs e)
{
    // This callback is made on the GUI thread
    (e.UserState as Window1).UpdateResult(e.Result);
}

Initiating an asynchronous call to Add is simply a matter of calling CalculatorClient.AddAsync(). Note that the code generator also created SubtractAsync, MultiplyAsync and DivideAsync methods.

private static void CallServiceNewAsync(Window1 win, double n1, double n2)
{
   _calcClient.AddAsync(n1, n2, win);
}

All source code for this example is located here.

More on XML Data Binding

In my last post I demonstrated how to bind elements from an XML document to WPF controls. I created small master/detail dual pane application to demonstrate this. Here I continue exploring XML data binding using the same XML document but with these important differences.

  1. The XML document will use namespaces.
  2. The data will be sorted in a different order.
  3. The view of the data will be flattened into a ListView.

Using XML Namespaces

Adding a namespace to the XML document itself is a simple matter. Once this is done, however, changes must be made to the code that accesses the now namespace scoped XML elements and attributes. First, here’s the updates XML document.

<pc:States xmlns:pc="https://patconroy.wordpress.com/States/">
    <pc:State pc:Name="Delaware" pc:Abbrev="DE">
        <pc:Capital>Dover</pc:Capital>
        <pc:Nickname>The First State</pc:Nickname>
        <pc:Bird>Blue Hen Chicken</pc:Bird>
        <pc:Flower>Peach Blossom</pc:Flower>
        <pc:Tree>American Holly</pc:Tree>
        <pc:Motto>Liberty and Independence</pc:Motto>
    </pc:State>
    <!-- Other states omitted for brevity -->
</pc:States>

The application no longer displays the data after making this change. It turns out that two changes must be made to the XAML source. First an XmlNamespaceMappingCollection must be associated with the XmlDataProvider. The XmlNamespaceMappingCollection creates a mapping between namespace URIs and their prefixes that are later used in XPath expressions. The XmlDataProvider.XmlNamespaceManager property must be set to an instance of XmlNamespaceMappingCollection. Here I create a mapping between the prefix “pc” and my namespace URI http://patconroy.workpress.com/States/.

<XmlNamespaceMappingCollection x:Key="StateDataNamespaceMapping">
    <XmlNamespaceMapping Uri="https://patconroy.wordpress.com/States/" Prefix="pc" />
</XmlNamespaceMappingCollection>
<XmlDataProvider x:Key="StateData" XmlNamespaceManager="{StaticResource StateDataNamespaceMapping}">
    <!-- etc. -->
</XmlDataProvider>

Next the XPath expressions used in the application must be updated to use the “pc” prefix. Without the prefix the XPath processor looks for elements and attributes in the global namespace, and this in no longer the case. The state XPath expression to access the state collection now becomes //pc:States/pc:State instead of //States/State. The expression to access the Name attribute of the State element now becomes @pc:Name.

Sorting the Data

In the XML document the states are presented in the order in which they ratified the Constitution for the United States of America. Any ItemsControl, including the ListBox used in the last example and the ListView used here, will display them in this order when bound to the XmlElement collection returned by the XPath expression //pc:States/pc:State. In this application I want them alphabetized by state abbreviation. WPF includes the CollectionViewSource class to do exactly this. CollectionViewSource can also do grouping and filtering. Check MSDN for the details. Here I create a CollectionViewSource in the main window’s resource dictionary to sort on the Abbrev XML attribute.

<CollectionViewSource x:Key="StateViewSource" Source="{Binding Source={StaticResource StateData},XPath=//pc:States/pc:State}">
    <CollectionViewSource.SortDescriptions>
        <cm:SortDescription PropertyName="@pc:Abbrev"/>
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

The CollectionViewSource binds to the XmlDataProvider and applies an XPath expression to access the collection of State elements. A SortDescription is added to sort on the Abbrev attribute, which is again accessed using an XPath expression. The SortDescription class doesn’t exist in any of the default XAML namespaces, so the namespace declaration xmlns:cm="clr-namespace:System.ComponentModel;assembly=WindowsBase" must be added to the XAML. The ItemsControl displaying this data must bind to the CollectionViewSource, not the XmlDataProvider: ItemsSource="{Binding Source={StaticResource StateViewSource}}".

Displaying in a ListView

In previous posts I re-templated the ListView control. As part of this I implemented alternating row colors to achieve an accounting ledger look. The accounting ledger look can be achieved far more simply than this; it isn’t necessary to re-template the entire control. When working with .Net 3.5 SP1 apply the AlternationCount attribute to the ListView itself. Then add an ItemContainerStyle containing the property triggers on ItemsControl.AlternationIndex as shown in the previous post. I had to create a DataTemplate for the first column in order to display the state flag there. A value converter is used to obtain the image file path. Thanks again to folks at 3DFlags.com for the flag images. Here’s the complete ListView XAML for this project.

<ListView x:Name="_stateList" Margin="4"
          AlternationCount="2"
          ItemsSource="{Binding Source={StaticResource StateViewSource}}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Style.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Background" Value="Khaki"/>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="Beige"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.View>
        <GridView AllowsColumnReorder="true">
            <GridViewColumn Width="36">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <Image Source="{Binding XPath=@pc:Abbrev, Converter={StaticResource ImageNameConverter}}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Width="48" DisplayMemberBinding="{Binding Path=@pc:Abbrev}" Header="Abbrev"/>
            <GridViewColumn Width="100" DisplayMemberBinding="{Binding Path=@pc:Name}" Header="Name"/>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding XPath=pc:Nickname}" Header="Nickname"/>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding XPath=pc:Capital}" Header="Capltal City"/>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding XPath=pc:Bird}" Header="State Bird"/>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding XPath=pc:Flower}" Header="State Flower"/>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding XPath=pc:Tree}" Header="State Tree"/>
            <GridViewColumn Width="200" DisplayMemberBinding="{Binding XPath=pc:Motto}" Header="Motto"/>
        </GridView>
    </ListView.View>
</ListView>

Here’s how the finished application looks.
xmllistview

Source code can be downloaded here.

XML Data Binding in WPF

Data binding is a powerful and time saving feature of the WPF framework. It allows a depencency property in the application’s view layer, such as a TextBox’s Text property, to be bound to a property in the application’s underlying data model. Data model in this instance means .NET classes or, as I’ll show later, an XML document. Data binding saves us from the tedious task of writing code to move data to and from WPF controls on the screen.

In my last two posts about styling the ListView I bound a ListView control to an underlying collection of type ObservableCollection<string>. Most real-world applications will bind ItemsControls to a collection of objects more complex than string. The screen controls then access properties of the underlying objects using data binding. More visually complex representations can be created by using data templates.

The XmlDataProvider in conjunction with the Binding class enable binding to elements and attributes in an XML DOM using XPath expressions. XmlDataProvider can load an XML document from a local system disk, from a remote system, or, as the example here does, from an XML data island in the application itself. XmlDataProviders can be easily created in a resource dictionary using XAML.

<Application.Resources>
    <XmlDataProvider x:Key="Xml1" Source="c:\documents\xml\automobiles.xml"/>
    <XmlDataProvider x:Key="Xml2" Source="http://www.somecompany.com/finance/Q3results.xml"/>

The sample application here displays various facts about the states of the United States of America. The data is limited to the first 13 states as all 50 aren’t needed for the example. They are listed in the order that they ratified the Constitution for the United States of America. The XML schema for the document is listed below.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="StateType">
    <xs:all>
      <xs:element name="Capital" type="xs:string" />
      <xs:element name="Nickname" type="xs:string" />
      <xs:element name="Bird" type="xs:string" />
      <xs:element name="Flower" type="xs:string" />
      <xs:element name="Tree" type="xs:string" />
      <xs:element name="Motto" type="xs:string" />
    </xs:all>
    <xs:attribute name="Name" type="xs:string" use="required" />
    <xs:attribute name="Abbrev" type="xs:string" use="required" />
  </xs:complexType>
  <xs:complexType name="StatesType">
    <xs:sequence maxOccurs="unbounded">
      <xs:element name="State" type="StateType" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="States" type="StatesType" />
</xs:schema>

The XmlDataProvider containing the XML document itself is created in the application resource dictionary. Note that the data island is contained within an <x:XData> element.

<XmlDataProvider x:Key="StateData">
    <x:XData>
        <States xmlns="">
            <State Name="Delaware" Abbrev="DE">
                <Capital>Dover</Capital>
                <Nickname>The First State</Nickname>
                <Bird>Blue Hen Chicken</Bird>
                <Flower>Peach Blossom</Flower>
                <Tree>American Holly</Tree>
                <Motto>Liberty and Independence</Motto>
            </State>
            <!-- Remaining states omitted for brevity -->
        </States>
    </x:XData>
</XmlDataProvider>

The application window consists of two panes. The left-hand pane contains a ListBox that lists the states from the XML document. This isn’t a simple list of state names. A DataTemplate is used to display an image of the state’s flag along with its name. The GIF images of the state flags used in this example were provided by the kind folks at 3DFlags.com.

xmldata

The DataTemplate is created in the application resource dictionary. See the XAML source below. Note that a value converter is used to construct the name of the flag image file. The GIF images are included in the Visual Studio project in a folder named Images.

<local:ImageNameConverter x:Key="ImageNameConverter"/>
<DataTemplate x:Key="StateListTemplate">
    <StackPanel Orientation="Horizontal">
        <Image Width="64" Source="{Binding XPath=@Abbrev,Converter={StaticResource ImageNameConverter}}"/>
        <TextBlock Margin="6,12,0,0" Text="{Binding XPath=@Name}" FontSize="14"/>
    </StackPanel>
</DataTemplate>

Here’s the source for the value converter.

class ImageNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is string)
        {
            return @"Images\" + (value as string) + ".gif";
        }
        else
        {
            throw new ArgumentException();
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

In the main Window I establish the data source for the window by binding to the XmlDataSource. An XPath expression is used to access the collection of <State> elements in the document.

<Window x:Class="XMLData.Window1"
    DataContext="{Binding Source={StaticResource StateData},XPath=//States/State}">
</Window>

The ListBox ItemTemplate property references the DataTemplate shown earlier.

<ListBox x:Name="_stateList" Grid.Column="0" Margin="2,2,6,2" 
         Background="Beige"
         ItemsSource="{Binding}" ItemTemplate="{StaticResource StateListTemplate}"
         IsSynchronizedWithCurrentItem="True"/>

The rest of the application references the ListBox’s SelectedItem. For example the status bar displays the name of the state currently selected in the ListBox. I did this by binding the Text property of a TextBlock to the SelectedItem property of the ListBox. A value converter is used once again to get the desired text.

<StatusBarItem>
    <TextBlock Text="{Binding ElementName=_stateList,Path=SelectedItem,Converter={StaticResource SelectedItemConverter}}"/>
</StatusBarItem>

The right-hand pane of the application displays far more information than just the state name. A FlowDocumentScrollViewer is used to display a FlowDocument that contains the relevant information. Within the FlowDocument instances of TextBlock have their Text properties bound to various elements within the currently selected <State> element. Again, XPath expressions are used to obtain the desired data. By using data binding I avoid writing any C# code whatsoever to update the details pane when a new state is selected in the list box. The WPF binding framework handles this automatically. In fact there is very little code behind in this application at all. All data movement is handled through binding. I only created two value converters to coerce some of the data into a more displayable form.

<FlowDocumentScrollViewer Grid.Column="1" Margin="2">
    <FlowDocument Background="Beige" DataContext="{Binding ElementName=_stateList,Path=SelectedItem}">
        <Paragraph FontSize="16" FontStyle="Italic" FontWeight="Bold" TextAlignment="Center">
            <TextBlock Text="{Binding XPath=@Abbrev}"/>
            <Run Text=" - "/>
            <TextBlock Text="{Binding XPath=@Name}"/>
        </Paragraph>
        <Table FontSize="12" CellSpacing="8">
            <Table.Columns>
                <TableColumn Width="Auto"/>
                <TableColumn/>
            </Table.Columns>
            <TableRowGroup>
                <TableRow>
                    <TableCell><Paragraph>Capital</Paragraph></TableCell>
                    <TableCell>
                        <Paragraph>
                            <TextBlock Text="{Binding XPath=Capital}"/>
                        </Paragraph>
                    </TableCell>
                </TableRow>
                <!-- Other rows omitted for brevity -->
            </TableRowGroup>
        </Table>
    </FlowDocument>
</FlowDocumentScrollViewer>

All source code may be downloaded here.

More on ListView styles

In my last post I described how to display a ListView control with alternating row colors. This gives the ListView the look of an accounting ledger. I ended by mentioning that there is a new feature in the .NET 3.5 SP1 libraries that makes this much easier. Microsoft added a new property to ItemsControl named AlternationCount. This technique works for any ItemsControl, not just the ListView demonstrated here.

Set AlternationCount to the number of rows that are to partake in the pattern. In this example a 2 row pattern is specified but any number may be used. You only must specify display properties for each row in the pattern. More on that later.

<Style x:Key="{x:Type ListView}" TargetType="{x:Type ListView}">
  <Setter Property="AlternationCount" Value="2"/>
…
</Style>

The ItemsControl, a ListView in this example, attaches the property ItemsControl.AlternationIndex to each of its item containers. The value of ItemsControl.AlternationIndex is the row ordinal mod n, where n is the value specified for AlternationCount. In the example here it will be set to 0 for even numbered rows and 1 for the odd numbered ones. Now simply add property triggers on ItemsControl.AlternationIndex to the item container style. The item container in this example is a ListViewItem.

<Style x:Key="{x:Type ListViewItem}" TargetType="ListViewItem">
  <Style.Triggers>
    <Trigger Property="ItemsControl.AlternationIndex" Value="0">
      <Setter Property="Background" Value="{StaticResource ListViewLine2Brush}"/>
    </Trigger>
    <Trigger Property="ItemsControl.AlternationIndex" Value="1">
      <Setter Property="Background" Value="{StaticResource ListViewLine1Brush}"/>
    </Trigger>
  </Style.Triggers>
</Style>

This technique does not suffer from the same problem detailed in my last post. The ItemsControl automatically updates AlternationIndex on all items affected by an adds or deletes on the underlying collection. No special workaround code needs to be written like in the last post. All source code for both of my ListView posts is located here.


February 2017
M T W T F S S
« Apr    
 12345
6789101112
13141516171819
20212223242526
2728  
I am a part of all that I have met;
Yet all exprience is an arch whitherthro'
Gleams that untravell'd world, whose margin fades
For ever and for ever when I move.
How dull it is to pause, to make an end,
To rust unburnish'd, not to shine in use!
Alfred, Lord Tennyson