February 22, 2008

How can I drag and drop items between data bound ItemsControls?

In this blog post, I will explain how I implemented a sample that allows you to drag and drop items between data bound ItemsControls. Imagine you have two or more data bound (or not data bound) ItemsControls in the same Window. You may want to drag an item from an ItemsControl and drop it on another one. Or maybe you want to drag and drop it in a different position within the same ItemsControl. The code in this blog post allows you to do those operations.

Use this code

You don’t need to understand the implementation details of this sample to take advantage of its functionality. If you want to use this code in your app, you should copy three files from the project at the end of the post - DraggedAdorner.cs, InsertionAdorner.cs and DragDropHelper.cs. You may want to change the namespace in these files. Then, within your app, you simply need to add three attached properties:

  • IsDropTarget should be set on the ItemsControl that you want to drag items to. This property is of type boolean and should be set to true. Don’t forget to add a namespace mapping on the top window that maps to the namespace you chose for DragDropHelper.

    <Window …
    xmlns:local=”clr-namespace:DragDropListBox” … />

    <ItemsControl …
    local:DragDropHelper.IsDropTarget=”true” …/>

  • IsDragSource should be set on the ItemsControl that you want to drag items from. This property should also be set to true.
  • DragDropTemplate should also be set on the drag source ItemsControl (where you want to drag the items from). This property should be set to a DataTemplate that controls how the data item should be displayed while it is dragged around. If you want it to be displayed exactly the same way as in the source ItemsControl, the DragDropTemplate and ItemTemplate properties can share the same template.

    <ItemsControl …
    local:DragDropHelper.IsDragSource=”true” local:DragDropHelper.DragDropTemplate=”{StaticResource pictureTemplate}”/>

And that’s all there is to it. Next I will show the list of features and limitations of this solution.

Features

  • The code in this sample enables the user to drag and drop between ItemsControls and any controls that derive from it (see exception for TreeView in the limitations section).
  • This solution supports dragging and dropping within the same ItemsControl.
  • This solution supports dragging and dropping between non data bound and data bound ItemsControls.
  • I’ve added support for ItemsControls that use FlowPanel and StackPanel (with both horizontal and vertical orientation). Support for other panels can be added easily.
  • This sample prevents the user from dropping a data item into a target ItemsControl when its data items are of an incompatible type. For example, if the drag source ItemsControl contains a collection of Potatoes but the drop target ItemsControl is data bound to a collection of Squirrels, the app will prevent the user from doing the drop.
  • This sample provides visual feedback of where the dragged item will go if dropped. For example, when dragging a data item over a ListBox, this sample shows a horizontal line between the two items where the item will go if dropped.

Limitations

  • This solution doesn’t drag and drop data items across Windows.
  • It also doesn’t work across Applications.
  • It allows drag and drop to work between ItemsControls, but it does not allow you to drag or drop to a ContentControl.
  • This sample works as expected for the first level of a TreeView, but not for nested levels of the hierarchy.

Overview

To implement a drag and drop operation, we need to attach handlers for several events on the drag source and drop target elements. In this scenario, I decided that I am going to support only ItemsControls as the source and target of the drag and drop operation. All the events that I need to provide handlers for are public, so I could have easily done that in the Window1.xaml.cs file. However, I wanted an easily reusable solution that would allow all the drag-and-drop-specific code to be abstracted in one place. I thought of three different solutions to this problem (although I’m sure there are more):

  • I could derive from ItemsControl and override “OnPreviewMouseLeftButtonDown” and all the other “On” methods that correspond to the events I care about. I don’t like this solution so much because users would have to replace their ItemsControls with my custom “DragAndDropItemsControl”.
  • I could create a DragHelper class that takes an instance of the drag source ItemsControl as a parameter, and a DropHelper class that takes the drop target. The only disadvantage of this solution is that it needs to be set up from code.
  • Instead, I decided to create a DragDropHelper class that defines properties to be attached to the drag source and drop target IsDragSource and IsDropTarget. The main advantages of this solution are that it can be implemented entirely in XAML, and that the XAML changes are minimal (you only need to set these properties). I’ll explain below how this works.

These two attached properties are of type boolean, and should be attached to the drag source and drop target. When they’re set to false, dragging/dropping is not allowed, otherwise it is. If your application requires drag and drop operations to be allowed sometimes but not other times, you can simply change the properties on the fly.

When registering the attached properties, I made sure I passed change handlers called IsDragSourceChanged and IsDropTargetChanged to the UIPropertyMetadata. These methods are responsible for adding and removing handlers for the desired events, depending on the value of the attached property. I will show here the code for IsDragSourceChanged (the code for the target is similar):

    private static void IsDragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var dragSource = obj as ItemsControl;
        if (dragSource != null)
        {
            if (Object.Equals(e.NewValue, true))
            {
                dragSource.PreviewMouseLeftButtonDown += Instance.DragSource_PreviewMouseLeftButtonDown;
                dragSource.PreviewMouseLeftButtonUp += Instance.DragSource_PreviewMouseLeftButtonUp;
                dragSource.PreviewMouseMove += Instance.DragSource_PreviewMouseMove;
            }
            else
            {
                dragSource.PreviewMouseLeftButtonDown -= Instance.DragSource_PreviewMouseLeftButtonDown;
                dragSource.PreviewMouseLeftButtonUp -= Instance.DragSource_PreviewMouseLeftButtonUp;
                dragSource.PreviewMouseMove -= Instance.DragSource_PreviewMouseMove;
            }
        }
    }

I describe below the events that I provided handlers for. I had to use the preview versions of these events to make sure I handle them before other controls do (for example, ListBox handles MouseLeftButtonDown).

ItemsControl Drag Source

  • PreviewMouseLeftButtonDown Its main responsibility is to remember the data item that the user clicked on.

    this.sourceItemContainer = Utilities.GetItemContainer(this.sourceItemsControl, visual);
    if (this.sourceItemContainer != null)
    {
        this.draggedData = this.sourceItemContainer.DataContext;
    }

  • PreviewMouseMove For a drag to happen, the user needs to click on the drag source and move the mouse by a significant amount. When a user performs a button click, there is a pretty high likelihood that the mouse will move inadvertently by a small amount, and I want to ignore those situations. Fortunately, there is a SystemParameter that can be used in these situations, so I make sure that the mouse has moved by at least SystemParameters.MinimumHorizontalDragDistance or SystemParameters.MinimumVerticalDragDistance before I consider it a drag. Once I decide a drag has started, I initiate the drag-and-drop operation. Notice below that I am initiating the drag operation with the data that I determined in the PreviewMouseLeftButtonDown handler (draggedData).

    DataObject data = new DataObject(this.format.Name, this.draggedData);
    …
    DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);

  • PreviewMouseLeftButtonUp This happens when the user releases the mouse without initiating a drag-and-drop operation. In this case, I simply set the draggedData field to null.

    this.draggedData = null;

ItemsControl Drop Target

  • PreviewDragEnter This event fires every time the user first drags a data item into an element with AllowsDrop=true (I set this property on the drop target ItemsControl within the IsDropTargetChanged method, so you don’t have to). Since this property is inherited, this handler will be called every time the mouse enters any element that belongs to the visual tree of the drop target ItemsControl. Among other things, within this handler I have code that updates the two adorners that I will explain later in this post.
  • PreviewDragOver This event fires when the user drags the data item over elements with AllowsDrop=true. Similarly to the previous handler, the adorners are updated in this handler.
  • PreviewDragLeave As expected, this event fires when the mouse leaves an element that has AllowsDrop=true. Here, I remove one of the adorners. More details on the adorners later.
  • PreviewDrop This event notifies us that the drop operation has actually occurred. Here, I remove the data item from the drag source ItemsControl’s collection and add it to the drop target.

    object movedItem = e.Data.GetData(this.format.Name);
    …
    indexRemoved = Utilities.RemoveItemFromItemsControl(this.sourceItemsControl, movedItem);
    …
    Utilities.InsertItemInItemsControl(this.targetItemsControl, movedItem, this.insertionIndex);

Dragging and dropping data

There are several problems that are particular to dragging and dropping data items between ItemsControls. I will discuss a few of those next.

When dragging a data item over the target ItemsControl, I need to determine where the item will go if dropped. I broke down this scenario into three possible different situations:

  • The mouse is over an empty ItemsControl. In this case, if the ItemsControl is data bound to an empty collection the item gets added as the first and only item of that collection; similarly, if the ItemsControl is empty and not data bound, the item is added to the Items collection of the ItemsControl.
  • The ItemsControl has items, although the mouse is over the empty part of the ItemsControl, after the last item. In this case, the item should be added to the end of the ItemsSource or Items collection.
  • Last, the mouse could be over an item container. In this case, I need to determine whether I should insert the dragged data before or after the item under the mouse. If the ItemsControl uses a panel with vertical orientation, I check to see whether the mouse is over the top half or bottom half of the item container. Similarly, if the panel is laid out horizontally, I check to see whether the mouse is over the left half or right half of the item container.

This leads me to the next topic: I had to store the orientation of the panel so that I could calculate the insertion location when dropping over an item container. This calculation is done in the following method:

    public static bool HasVerticalOrientation(FrameworkElement itemContainer)
    {
        bool hasVerticalOrientation = true;
        if (itemContainer != null)
        {
            Panel panel = VisualTreeHelper.GetParent(itemContainer) as Panel;
            StackPanel stackPanel;
            WrapPanel wrapPanel;
    
            if ((stackPanel = panel as StackPanel) != null)
            {
                hasVerticalOrientation = (stackPanel.Orientation == Orientation.Vertical);
            }
            else if ((wrapPanel = panel as WrapPanel) != null)
            {
                hasVerticalOrientation = (wrapPanel.Orientation == Orientation.Vertical);
            }
            // You can add support for more panel types here.
        }
        return hasVerticalOrientation;
    }

Notice that I am providing support only for StackPanel and WrapPanel here, but you can easily add more “if” statements to support other panels.

Once I have the panel’s orientation information, I can determine whether the dragged data should be inserted before or after the item container under the mouse. In the code below, “first half” means the left half for a horizontally oriented panel, and top half for a vertically oriented one.

    public static bool IsInFirstHalf(FrameworkElement container, Point clickedPoint, bool hasVerticalOrientation)
    {
        if (hasVerticalOrientation)
        {
            return clickedPoint.Y < container.ActualHeight / 2;
        }
        return clickedPoint.X < container.ActualWidth / 2;
    }

Finally, I wanted to give the user feedback when a certain data item type can not be added to the drop target collection. To achieve this, I wrote a method called IsDropDataTypeAllowed, from which the following code snippet was taken. The drop operation is allowed if the target collection is of type IList where the dragged type can be assigned to type T, or if the target collection is of type IList, or if the drop target is not data bound. If none of the above is true, the cursor will change to a slashed circle to indicate that a drop is not allowed in that location.

    Type draggedType = draggedItem.GetType();
    Type collectionType = collectionSource.GetType();
    
    Type genericIListType = collectionType.GetInterface(”IList`1″);
    if (genericIListType != null)
    {
        Type[] genericArguments = genericIListType.GetGenericArguments();
        isDropDataTypeAllowed = genericArguments[0].IsAssignableFrom(draggedType);
    }
    else if (typeof(IList).IsAssignableFrom(collectionType))
    {
        isDropDataTypeAllowed = true;
    }
    else
    {
        isDropDataTypeAllowed = false;
    }

Dragged Adorner

As I mentioned earlier, the DragDropHelper class contains a DragDropTemplate attached property that should be set to the DataTemplate responsible for determining the look of the dragged data item. The DraggedAdorner’s main responsibility is to provide a representation for the dragged data using that DataTemplate. To achieve this behavior, I decided that the adorner’s only child should be a ContentPresenter with its Content set to the dragged data, and its ContentTemplate set to the DataTemplate specified in the DragDropTemplate attached property. I also decided to give the adorner a bit of transparency, so I set the Opacity of the ContentPresenter to 0.7. Here is the constructor for this adorner:

    public DraggedAdorner(object dragDropData, DataTemplate dragDropTemplate, UIElement adornedElement, AdornerLayer adornerLayer)
        : base(adornedElement)
    {
        this.adornerLayer = adornerLayer;
        
        this.contentPresenter = new ContentPresenter();
        this.contentPresenter.Content = dragDropData;
        this.contentPresenter.ContentTemplate = dragDropTemplate;
        this.contentPresenter.Opacity = 0.7;
        
        this.adornerLayer.Add(this);
    }

And here is what this adorner looks like, with the template I specified in this blog’s project:

There’s a bit more logic in this adorner class, such as the code that allows the adorner to follow the position of the mouse and the code that sets up the ContentPresenter as the adorner’s child visual.

Next I will explain briefly when this adorner is created, removed, and updated.

The DraggedAdorner is created:

  • When the user first drags data into an element belonging to the drop target DropTarget_PreviewDragEnter. This event is fired every time we enter a new element in the target’s tree, but we only need to create the Dragged Adorner the first time.
  • Every time we re-enter the Window TopWindow_DragEnter.

The DraggedAdorner is removed:

  • When dropping the item in a valid area DropTarget_PreviewDrop.
  • When leaving the window TopWindow_DragLeave.
  • If I drop the item in an invalid area, the drop handler doesn’t get called but the Window’s leave handler does, so this scenario is covered.

The DraggedAdorner is updated:

  • Every time the cursor moves over the drop target or anywhere else within the window. DropTarget_PreviewDragOver, TopWindow_DragOver.

In summary, I create the adorner once, the first time it is needed. I remove the adorner every time the mouse leaves the window and recreate it when the mouse re-enters the window. And I remove the adorner again at the end of the drag-and-drop operation.

Insertion Adorner

The insertion adorner is responsible for the visual feedback that indicates the insertion point of a dropped data item. For example, if you drag a data item over a ListBox, you will see a line between two of the items indicating the position where the dragged item will be inserted if dropped. The visual appearance of an insertion adorner is a line with two triangles facing inwards at the ends:

In order to make this adorner as performant as possible, I wrote a static constructor where I create a triangle and freeze all Freezable objects involved in its creation:

    static InsertionAdorner()
    {
        pen = new Pen { Brush = Brushes.Gray, Thickness = 2 };
        pen.Freeze();
    
        LineSegment firstLine = new LineSegment(new Point(0, -5), false);
        firstLine.Freeze();
        LineSegment secondLine = new LineSegment(new Point(0, 5), false);
        secondLine.Freeze();
    
        PathFigure figure = new PathFigure { StartPoint = new Point(5, 0) };
        figure.Segments.Add(firstLine);
        figure.Segments.Add(secondLine);
        figure.Freeze();
    
        triangle = new PathGeometry();
        triangle.Figures.Add(figure);
        triangle.Freeze();
    }

I need two triangles in this adorner. Because their positions and orientations vary according to the ItemsControl’s layout, I created just one triangle object and draw it twice after applying the appropriate translate and rotate transforms. The method below is called twice with different parameters:

    private void DrawTriangle(DrawingContext drawingContext, Point origin, double angle)
    {
        drawingContext.PushTransform(new TranslateTransform(origin.X, origin.Y));
        drawingContext.PushTransform(new RotateTransform(angle));
    
        drawingContext.DrawGeometry(pen.Brush, null, triangle);
    
        drawingContext.Pop();
        drawingContext.Pop();
    }

Next I will explain briefly when this adorner is created, removed, and updated.

The InsertionAdorner is created:

  • Every time the cursor enters a new element in the target’s tree. It needs to be created this frequently because different elements can have different sizes DropTarget_PreviewDragEnter.

The InsertionAdorner is removed:

  • Every time the cursor leaves the current element or the item is dropped DropTarget_PreviewDragLeave, DropTarget_PreviewDrop.

The InsertionAdorner is updated:

  • Every time the cursor moves over the drop target DropTarget_PreviewDragOver.

Windows events

The fact that I’m providing handlers for the window’s drag drop events may seem a little strange at first.

Imagine I provide handlers for the ItemsControl drop target only. Since I have code in these handlers that displays the preview adorner (which is where DragDropTemplate gets applied), the preview would only come up when the mouse cursor enters the drop target. Anywhere else in the window, the mouse cursor would turn into a crossed circle, indicating that it’s not an allowed drop location.

I like the crossed circle to come up in those areas, but I would like the adorner to be displayed anywhere I drag the item within the window.

To achieve the effect I wanted, I set the AllowDrop property of the window to true, and hooked up handlers for its drag events. I used the preview versions of the drop events for the target, but not for the window. This way, when the cursor is within the drop target I get the drop target events (and set Handled to true), but when the cursor is anywhere else in the window, I get the window’s events instead. I used these events to ensure the creation and removal of the preview dragged adorner, and to make sure that the mouse cursor is set to the crossed circle.

Other similar solutions available

Other developers have blogged about similar drag-and-drop solutions in the past. I will link to some of those solutions here, although there are probably more that I haven’t come across.

Pavan Podila has a great series of four blog posts about a similar scenario, which I highly recommend reading. The main difference between our two solutions is that his code can be used to drag UI elements from one panel to another, while my solution is used to drag data from one ItemsControl to another. I was very glad to see that Pavan Podila is also a fan of the attached properties solution for extending functionality.

Jaime Rodriguez also talked about this topic in a great three part series blog post. As in this post, he provides a solution for dragging and dropping data items.

If you need drag-and-drop functionality in your app, you will probably end up using a combination of the features from many blog posts. I hope you find my solution useful!

Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under WPF General | Comments (0)

January 27, 2008

How can I debug WPF bindings?

Data Binding can be tricky to debug. In this post, I will share the techniques I use to debug WPF bindings, including the new debugging improvements we implemented in the latest 3.5 release. I will discuss the following four techniques:

  • Scanning Visual Studio’s Output window for errors.
  • Using Trace Sources for maximum control.
  • Using the new property introduced in WPF 3.5 PresentationTraceSources.TraceLevel.
  • Adding a breakpoint to a Converter.

The DataContext of this app is set to a new instance of Star, which is a class that contains two properties: Picture and Caption. Picture is of type BitmapImage and contains an image of the sun, which I included in the project. Caption is a string property that takes a while to be initialized (more details about this later).


Output Window

In the XAML of this application, I have an Image whose Source is data bound to an incorrect property name. This is a simple scenario that causes a Binding to fail.

    <Image Source="{Binding Path=PictureWrong}" Width="300" Height="300" Grid.Row="0"/>

Every time a Binding fails, the binding engine prints an informative message in the Output window of Visual Studio. In this case, I get the following message:

    System.Windows.Data Error: 35 : BindingExpression path error: ‘PictureWrong’ property not found on ‘object’ ”Star’ (HashCode=49535530)’. BindingExpression:Path=PictureWrong; DataItem=’Star’ (HashCode=49535530); target element is ‘Image’ (Name=”); target property is ‘Source’ (type ‘ImageSource’)

This message should give you enough information to quickly figure out the mistake in the property name.

Advantage of this technique:

  • It is very easy to look at the Output window, and in most cases it’s sufficient. It should be the first approach you consider when you have a problem with your bindings.

Disadvantage of this technique:

  • Most real world applications print so much information to the Output window that it may be hard to find the error you’re looking for.


Trace Sources

The Trace Sources solution was already around in WPF 3.0. It adds a lot more flexibility to the previous solution by allowing you to control the level of detail you care about, where you want that messages to be printed and the WPF feature you want to debug.

The Trace Sources solution relies on a config file for the configuration of its behavior - the App.config file. Here is a portion of the contents of that file:

    <configuration>
         <system.diagnostics>
            <sources>
    
            <source name="System.Windows.Data" switchName="SourceSwitch" >
                <listeners>
                    <add name="textListener" />
                </listeners>
            </source>
            …
        
            <switches>
                …
                <add name="SourceSwitch" value="All" />
            </switches>
        
            <sharedListeners>
                …
                <add name="textListener"
                type="System.Diagnostics.TextWriterTraceListener"
                initializeData="DebugTrace.txt" />
                …
            </sharedListeners>
        
            <trace autoflush="true" indentsize="4"></trace>
        
        </system.diagnostics>
    </configuration>

In this file, I am specifying that:

  • I want only messages generated in the Data subarea to be printed. If you’re trying to debug, for example, animations, you would instead add the area System.Windows.Media.Animation.
  • I want as much information as possible about data binding. This was done by setting the switch to All. Other possible values are Off, Error, Warning. For a complete list, look up SourceLevels in .NET Reflector.
  • I want the messages to be printed to an external file called DebugTrace.txt, instead of the Output Window. This file will be created in the bin\debug folder for the application. If you run this application twice, the messages generated the second time will be appended to the existing messages in this file. If you don’t want this behavior, remember to delete the file before running the app.

Other pre-defined listeners allow printing to the Console (ConsoleTraceListener), or to an external file in XML format (XmlWriterTraceListener).

If you run the application with the settings above, you should find a DebugTrace.txt file in the bin\debug directory. If you open it, you will see the data binding error we saw previously in the Output Window, plus four “Information” messages. These lower-priority messages are printed because I specified in the switch that I want all the information available about the bindings.

If you want to learn more about this topic, I recommend Mike Hillberg’s blog. He wrote the best article I’ve read about Trace Sources, which I use frequently as a reference.

Advantages of this technique:

  • It separates the debug messages you care about from the rest of the information printed in the Output window.
  • This solution may help you debug other areas in WPF, not just binding.
  • You can get lower-priority messages (such as information or warnings) that are not typically printed to the Output window.

Disadvantages of this technique:

  • The text file (or whatever form of output you choose) will contain debug messages about all the bindings in your application. Although this is not as much clutter as the Output window, it may still require some digging for you to find exactly the information you need.
  • It won’t help you in scenarios where your Binding actually succeeds, but you still don’t see what you expect in the UI. The first and second techniques I show here only help in scenarios where the Binding fails.

Before you move on, make sure you correct the Path in the Image’s Source so that these errors won’t interfere with the ones I show next.


Trace Level - new in 3.5

In order to understand this feature, let’s start by uncommenting the first TextBlock in the XAML of this application:

    <TextBlock Text="{Binding Path=Caption}" … />

This TextBlock attempts to bind to Caption, a property whose value is slow to be initialized. In this case, I am simulating a slow data source by adding a Dispatcher timer to the constructor, but in reality this delay could have many different causes. Notice also that I am *not* raising a property changed notification when Caption changes value.

    public string Caption
    {
        private set
        {
            this.caption= value;
            //OnPropertyChanged("Caption");
        }
        get
        {
            return caption;
        }
    }
    
    public Star()
    {
        this.Picture = new BitmapImage(new Uri("Images\\sun.jpg", UriKind.Relative));
        this.Caption = String.Empty;
        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 3);
        timer.Tick += new EventHandler(Timer_Tick);
        timer.Start();
    }
    
    private void Timer_Tick(object sender, EventArgs e)
    {
        this.Caption = "Sun";
    }

In this scenario, the Binding will succeed (it will bind to the initial value of empty string), but you don’t see what you expect to see in the UI. The unwanted behavior is caused by the fact that the events happen in an order different from what you expect: the Binding will be evaluated before Caption gets its real value. In this case, there are no errors in the Output window or DebugTrace.txt because the Binding succeeds.

You can use the new debugging feature in 3.5 to debug this scenario by adding the attached property PresentationTraceSources.TraceLevel to the Binding, which can be set to None, Low, Medium and High. Since PresentationTraceSource is not in the default namespace mappings for WPF, you will have to write the following XAML:

    <Window …
    xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
    />
    
    <TextBlock Text="{Binding Path=Caption, diagnostics:PresentationTraceSources.TraceLevel=High}" … />

If you look at the Output window, you will notice that the binding engine generated debug information for every task that may help users find problems with this particular binding:

    System.Windows.Data Warning: 47 : Created BindingExpression (hash=25209742) for Binding (hash=3888474)
    …
    System.Windows.Data Warning: 91 : BindingExpression (hash=25209742): GetValue at level 0 from Star (hash=31609076) using RuntimePropertyInfo(Caption): ‘ ‘
    System.Windows.Data Warning: 71 : BindingExpression (hash=25209742): TransferValue - got raw value ‘ ‘
    System.Windows.Data Warning: 78 : BindingExpression (hash=25209742): TransferValue - using final value ‘ ‘

In this case, we can scan the debug messages quickly to see that there were no errors, and look at the last few messages to understand that the value found at the Source was the empty string. This information will help you come to the conclusion that there is some timing issue with the scenario.

Advantages of this technique:

  • This feature is particularly useful when you know exactly which binding you want to find out more about, which is the most common scenario.
  • It allows you to know more about bindings that succeed, which many times helps you find the mistake in your logic.

Disadvantages of this technique:

  • If you forget to remove the property after you found the problem, it adds to the clutter of the Output Window (which adds to the time it takes to start your application in debug mode).


Converter

The last solution is extremely simple: you can simply add a no-op Converter to your binding and put a breakpoint in its Convert method. This is what this solution looks like:

    <TextBlock Text="{Binding Path=Caption, Converter={StaticResource converter}}" …/>

    public class DebuggingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value; // Add the breakpoint here!!
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException("This method should never be called");
        }
    }

If you run this code and set a breakpoint inside the Convert method, you can simply hover over the “value” parameter to see what is being passed from the source to the target. In this case, you will see that “value” contains an empty string. This may help you realize that the source of your binding does not have the value you thought it had.

Advantages of this technique:

  • It is really easy to implement. It relies on a concept that most data binding users are familiar with.
  • It does not depend on the Output window.
  • It helps you find out more about scenarios where the binding doesn’t fail.

Disadvantages to this technique:

  • It doesn’t provide as much information as the TraceLevel technique.
  • If the Binding fails early, the Converter may not be called.


Conclusion

I mentioned here techniques to debug WPF bindings using Visual Studio, however there are other tools that can help you with this process. One other tool I use frequently is Snoop, not only to debug bindings but many other aspects of my application. Mole is also useful, and has great documentation.

What technique or tool you use to debug your bindings comes down to a combination of personal preference and your specific scenario.


Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under WPF Data Binding | Comments (0)

January 13, 2008

How can I implement the Master-Detail pattern when binding to XLinq and XML?

I have explained in previous posts how to implement:

  • A two-level Master-Detail scenario using objects as data source here.
  • A three level Master-Detail scenario using objects as data source here
  • A three level Master-Detail scenario using ADO.NET here. The two-level version of this scenario with ADO.NET should be obvious based on the samples above.

In this post, I will explain how to implement a two and three level Master-Detail when binding to XLinq and XML.

Two level Master-Detail with XML

Here is the XML data source I used for this scenario:

    <XmlDataProvider x:Key=”PlanetsXDP” XPath=”SolarSystemPlanets/Planet” Source=”SolarSystemPlanets.xml” />
            
    <!– In file SolarSystemPlanets.xml –>
    <?xml version=”1.0″ encoding=”utf-8″ ?>
    <SolarSystemPlanets>
        <Planet Name=”Mercury”>
            <Orbit>57,910,000 km (0.38 AU)</Orbit>
            <Diameter>4,880 km</Diameter>
            <Mass>3.30e23 kg</Mass>
            <Details>The small and rocky planet Mercury is the closest planet to the Sun.</Details>
        </Planet>
        …
    </SolarSystemPlanets>

The two level Master-Detail scenario works very similarly regardless of the data source. The following code and XAML make up the core of this scenario, when using XML as a data source:

    <Grid Margin=”30″ Name=”XML2Level” DataContext=”{Binding Source={StaticResource PlanetsXDP}}”>
        …
        <ListBox ItemsSource=”{Binding}” Name=”MasterListBox2″ ItemTemplate=”{StaticResource XMLMasterPlanetTemplate}” IsSynchronizedWithCurrentItem=”True” Width=”100″ … />
        <ContentControl Name=”Detail2″ Content=”{Binding}” ContentTemplate=”{StaticResource XMLDetailPlanetTemplate}” … />
    </Grid>

Notice that both the ListBox and ContentControl elements have the exact same empty Binding as content. This is possible because, as I explained in a previous post, the data binding engine detects that the ContentControl can not display a list of items, and grabs the current item of that list instead. This is true for every data source type.

Two level Master-Detail with XLinq

I started by creating an XElement by loading the same XML file shown above, and used it as the data source for the XLinq scenario. In my last blog post I loaded the XML in code, so this time I show how to do this in XAML, using ObjectDataProvider.

    <ObjectDataProvider ObjectType=”{x:Type xlinq:XElement}” MethodName=”Load” x:Key=”PlanetsODP”>
        <ObjectDataProvider.MethodParameters>
            <system:String>../../SolarSystemPlanets.xml</system:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

You can see below that the only somewhat interesting difference between the XLinq and XML scenarios is the Path of the DataContext Binding. In this scenario, we need to drill into the “Elements” property of the root XElement, in order to obtain a list of Planet elements. Since we now have a collection as the DataContext, we can use a simple empty Binding on the ListBox and ContentControl to get the Master-Detail pattern to work.

    <Grid Margin=”30″ Name=”XLinq2Level” DataContext=”{Binding Source={StaticResource PlanetsODP}, Path=Elements}”>
        …
        <ListBox ItemsSource=”{Binding}” Name=”MasterListBox1″ ItemTemplate=”{StaticResource XLinqMasterPlanetTemplate}” IsSynchronizedWithCurrentItem=”True” … />
        <ContentControl Name=”Detail1″ Content=”{Binding}” ContentTemplate=”{StaticResource XLinqDetailPlanetTemplate}” … />
    </Grid>

Three level Master-Detail with XML

Here is the XML data source I used for this scenario :

    <XmlDataProvider x:Key=”MountainsXDP” XPath=”Mountains/Mountain” Source=”Mountains.xml”/>
    
    <!– In file Mountains.xml –>
    <Mountains >
        <Mountain Name=”Whistler”>
            <Lifts>
                <Lift Name=”Big Red Express”>
                    <Runs>
                        <Run>Headwall</Run>
                        <Run>Fisheye</Run>
                        <Run>Jimmy’s Joker</Run>
                    </Runs>
                </Lift>
                …
            </Lifts>
        </Mountain>
        …
    </Mountains>

The three level Master-Detail with XML is a little trickier than the two level version of the same pattern. Here is the code and XAML necessary to implement this scenario:

    
    <Grid Margin=”30″ Name=”XML3Level” DataContext=”{Binding Source={StaticResource MountainsXDP}}”>
        …
        <ListBox ItemsSource=”{Binding}” Name=”MasterListBox4″ ItemTemplate=”{StaticResource XMLMountainTemplate}” IsSynchronizedWithCurrentItem=”True” …/>
        <ListBox ItemsSource=”{Binding XPath=Lifts/Lift}” ItemTemplate=”{StaticResource XMLLiftTemplate}” IsSynchronizedWithCurrentItem=”True” Name=”MiddleListBox4″ …/>
        <ListBox Name=”Detail4″ DataContext=”{Binding ElementName=MiddleListBox4, Path=SelectedItem}” ItemsSource=”{Binding XPath=Runs/Run}” ItemTemplate=”{StaticResource XMLRunTemplate}” …/>
    </Grid>
    

The first ListBox simply binds to the collection of Mountains (notice the XPath in the XmlDataProvider).

The second ListBox drills into the collection of Lifts of each Mountain. This is implemented internally by executing an XPath query on the instance of the Moutains collection shown in the first ListBox, with the syntax found in the XPath property. The two collections can be kept in sync because the second collection instance is a subcollection of the first.

If you’ve seen my post on three level Master-Detail when binding to objects, you probably expect the following syntax to work for the third ListBox (a similar syntax works when binding to objects):

    <ListBox … ItemsSource=”{Binding XPath=Lifts/Lift/Runs/Run}” … />
    

If you try this on the third ListBox, you might be surprised by the results. The third ListBox shows all the runs of all the lifts of the selected mountain, instead of showing all the runs of the selected lift of the selected mountain. That’s because when you use this syntax, the binding engine executes a new XPath query on the DataContext, which happens to be the collection of mountains displayed in the first list box. The XPath query can’t be executed on a collection, so the binding engine executes the query on the current item of the collection - whichever mountain is selected in the first ListBox. The result of the query is completely independent of the current selection in the second ListBox.

The only way to grab instances of items from the collection in the second ListBox is by binding to the ListBox itself. This is why the DataContext of the third ListBox is bound to the SelectedItem of the second ListBox in the working solution.

You’re probably wondering why I’m binding to the DataContext and the ItemsSource of the third ListBox, instead of using the ItemsSource property only. This is what the Binding would look like, if I used the ItemsSource only:

    <ListBox … ItemsSource=”{Binding ElementName=MiddleListBox4, Path=SelectedItem, XPath=Runs/Run}” … />

As I explained in my previous post, it’s actually possible (and sometimes very useful) to have a Path and XPath in the same Binding. Keep in mind, however, that whenever XPath and Path are both specified, the XPath is always executed before the Path, independently of the order in which you specify them in the Binding. We decided on this order of execution because this feature is used mostly to drill into properties of XmlElement (such as HasAttributes, InnerXml or Name). In the Master-Detail sample, we need to first get the SelectedItem (which is an XmlElement), and then execute the XPath query on it. Unfortunately there is no way for us to specify in a Binding that we want to execute the Path before the XPath. And this is the reason I had to break up the Binding into two: the part with the Path needs to be executed first so it goes in the DataContext, and the XPath needs to be executed second, so it goes in the ItemsSource.

Three level Master-Detail with XLinq

Here is the code and XAML for the XLinq version of the three level Master-Detail:

    <ObjectDataProvider ObjectType=”{x:Type xlinq:XElement}” MethodName=”Load” x:Key=”MountainsODP”>
        <ObjectDataProvider.MethodParameters>
            <system:String>../../Mountains.xml</system:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    
    <Grid Margin=”30″ Name=”XLinq3Level” DataContext=”{Binding Source={StaticResource MountainsODP}, Path=Elements}”>
        …
        <ListBox ItemsSource=”{Binding}” Name=”MasterListBox3″ ItemTemplate=”{StaticResource XLinqMountainTemplate}” IsSynchronizedWithCurrentItem=”True” …/>
        <ListBox ItemsSource=”{Binding Path=Element[Lifts].Elements}” ItemTemplate=”{StaticResource XLinqLiftTemplate}” IsSynchronizedWithCurrentItem=”True” Name=”MiddleListBox3″ …/>
        <ListBox Name=”Detail3″ ItemsSource=”{Binding ElementName=MiddleListBox3, Path=SelectedItem.Element[Runs].Elements}” ItemTemplate=”{StaticResource XLinqRunTemplate}” IsSynchronizedWithCurrentItem=”True” …/>
    </Grid>
    

Similarly to the XML scenario, the first ListBox binds to the collection of Mountain XElements directly. The second ListBox gets the Lifts collection of a particular mountain, and drills into its Elements collection. This returns all the Lift XElements for the selected mountain.

But the most interesting part of this scenario is the third ListBox. First of all, notice that I am still using ElementName to bind to the second ListBox. The reason for this is exactly the same as in the XML scenario. Notice also I am no longer splitting the Binding between the DataContext and ItemsSource. That is because we use Path to do XLinq queries, so there are no issues with precedence of Path versus XPath. As you can see, we can specify our query in a much more linear way: we first get the SeletedItem (of type XElement) from the second ListBox, then get its Runs child, and then drill into all the Run elements.

Keeping two ListBoxes in sync

There are some differences of behavior when keeping two ListBoxes in sync (as part of three level Master-Detail or in isolation) using different data sources, so I will add a few extra words on this.

In the XML three-level Master-Detail, if you select “Whistler”, then “Stevens Pass”, then “Jupiter Chair”, then “Whistler”, then “Stevens Pass” again, you will notice that “Jupiter Chair” is no longer selected (the first element of that ListBox is selected instead). This happens because every time you click on “Stevens Pass”, we execute the appropriate XPath query, and that returns a different instance of the same collection.

If you try this scenario when binding to CLR objects, ADO.NET or XLinq, you will see that previous selections in the second ListBox will be remembered. This is typically what users want, so we paid special attention to it when designing integration between WPF and XLinq.

Conclusion

When you have the option of either binding directly to the current item of the previous collection or using ElementName and SelectedItem to achieve Master-Detail, I typically advise people to bind to the current item. One reason for this is that the Master-Detail scenario is a data concept, and if possible it should be implemented by using only data concepts such as currency (SelectedItem is a concept similar to currency, but relevant to the UI only). Besides, your scenario becomes independent of the names of the ListBoxes.

However, in scenarios like the three-level Master-Detail using XML and XLinq, where there is no solution purely based on current item, in my opinion it’s perfectly OK to implement the binding on the second ListBox using ElementName and SelectedItem. This has the advantage of maintaining consistency between the second and third ListBoxes, making the XAML easier to read. In this post, I wanted to show how far you can go in Master-Detail by binding to the current item only, which is why I opted to show different styles of Bindings in the second and third ListBoxes. In my opinion, both ways are OK, so it really comes down to personal preference.

Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under Master-detail | Comments (0)

January 1, 2008

How can I bind WPF elements to XLinq?

One of the main features that we shipped in .NET 3.5 is the ability to bind to XLinq directly from XAML. In this post, I will first cover the basics of this new feature, and then discuss the seven queries I added to the sample project. Please keep in mind that this blog post does not attempt to provide an introduction to XLinq. For those of you that need a quick introduction to the topic, I found this site to be a good resource.

Basics of binding to XLinq

When the XLinq and WPF teams started thinking about the integration of the two products, we quickly realized that we would need to do some work to improve our XAML scenarios in .NET 3.5. The main reason for this decision was the fact that simple XLinq queries are mostly done by calling sequences of methods on an XElement (such as .Elements() or .Descendants()) and we don’t have a good syntax to do that in XAML. (Yes, we can bind to the result of a method by using ObjectDataProvider as I explained in this post, but you can imagine how cumbersome the syntax would be if you wanted to call a method on the object returned by another method call. It would be much easier to do it in code.)

Since WPF already has a good syntax for binding to properties and subproperties, the logical solution to this problem was to add PropertyDescriptors to the XLinq DLL that expose certain key methods as if they were properties. If you are not familiar with PropertyDescriptors, I encourage you to read this article on ICustomTypeDescriptor and this one on TypeDescriptorProvider. These are the two best articles I have found on the topic. There is a precedent for this solution: we did basically the same thing to provide XAML support when binding to ADO.NET. As I described in this post , we expose the Relation names using PropertyDescriptors so that you can bind to them as if they were properties.

I want you to understand how we implemented this feature so that it doesn’t come as a surprise when I say that to bind to XLinq, you will need to use a binding’s Path (not XPath!) and the same “dot syntax” that we use to bind to subproperties.

We implemented six new PropertyDescriptors. You can find the new code added by the XLinq team in the System.Xml.XLinq.ComponentModel namespace of the System.Xml.XLinq DLL. You will see the TypeDescriptionProvider (XTypeDescriptionProvider<T>) and a new PropertyDescriptor class for each property we expose this way (e.g., XElementAttributePropertyDescriptor). The table below shows the correspondence between the properties exposed by the PropertyDescriptors (to be used in XAML) and their equivalent XLinq code.

XAML Code Notes
Element[MyElementName] .Element(”MyElementName”)
Elements[MyElementName] .Elements(”MyElementName”) Parameter is optional.
Attribute[MyAttributeName] .Attribute(”MyAttributeName”)
Descendents[ElementName] .Descendents(”ElementName”) Parameter is optional.
Value .Value Special case. (I explain below.)
Xml .ToString(SaveOptions.DisableFormatting)

The properties in the XAML column of this table are all exposed in XElement, but Value is also exposed in XAttribute. Based on the table above, you can deduce that the following XLinq code…

    var query = myXElement.Attribute(”MyAttribute”);

…has the following equivalent in XAML (assuming that the DataContext is the XElement myElement):

    <TextBlock Text=”{Binding Path=Attribute[MyAttribute]}” />

However, you may be wondering why we decided to define a PropertyDescriptor for Value, given that it is already a property (and not a method) of XElement (and XAttribute). It seems that we could simply bind to XElement and dot into it, right? Well, the reason for this is that we wanted to provide change notifications for Value. If you get a particular XElement as the result of a query, bind to it, and then change its value, we want that to be reflected in the UI. By adding a PropertyDescriptor for this property, we can provide change notifications in a non-intrusive way (without having to change the XElement class).

Similarly, it is important to keep in mind that anytime you bind to properties that don’t have a corresponding PropertyDescriptor (such as Root and HasAttributes in the samples I will show below), WPF will not be notified of property changes. If those properties happen to be in the middle of a query, they break the chain of notifications.

You can use .NET reflector to look into these PropertyDescriptors and understand how I came up with the table above. For example, search for XElementXmlPropertyDescriptor, look at the GetValue method, and you will see that it returns this.element.ToString(SaveOptions.DisableFormatting).

This is all there is to this new feature. Next I will show a few samples and a comparison between the XLinq code syntax, XLinq XAML syntax, and the XPath way of doing the same query.

Samples

Here is the data source used in these samples:

    <SolarSystemPlanets>
        <Planet Name=”Mercury”>
            <Orbit>57,910,000 km (0.38 AU)</Orbit>
            <Diameter>4,880 km</Diameter>
            <Mass>3.30e23 kg</Mass>
            <Details>The small and rocky planet Mercury is the closest planet to the Sun.</Details>
        </Planet>
        …
    </SolarSystemPlanets>

I defined the XML in an external file, added it as a Resource, and parsed it into an XDocument with the following code:

    XDocument planetsDocument = InitializeDataSource(”/Planets.xml”);

    private XDocument InitializeDataSource(string uriString)
    {
        StreamResourceInfo resourceInfo = Application.GetResourceStream(new Uri(uriString, UriKind.Relative));
        StreamReader reader = new StreamReader(resourceInfo.Stream);
        return XDocument.Load(reader);
    }

I also created an XmlDataProvider so that I could show queries equivalent to the XLinq ones, but using XPath:

    XmlDataProvider xdp = new XmlDataProvider();
    xdp.Source = new Uri(”Planets.xml”, UriKind.Relative);

First Planet’s Orbit

    
    // XLinq code
    var query2 = planetsDocument.Root.Element(”Planet”).Element(”Orbit”);
    Console.WriteLine(query2);
    
    // XLinq XAML
    <TextBlock Text=”{Binding Path=Root.Element[Planet].Element[Orbit]}” />
    
    // XPath
    <TextBlock Text=”{Binding XPath=/SolarSystemPlanets/Planet[1]/Orbit, Path=OuterXml}” />
    
    // Result
    //<Orbit>57,910,000 km (0.38 AU)</Orbit>

This working sample shows how easy it is to follow the table above to translate XLinq code into XAML. Please keep in mind that the range of useful query methods supported by XLinq is far greater than the ones we decided to support. For example, you could have written this same query in the following way:

    var query2 = planetsDocument.Element(”SolarSystemPlanets”).Element(”Planet”).Element(”Orbit”);

This can not be translated directly into XAML because the first Element(…) method is called on XDocument, and we only added the Element property descriptor for XElement.

Here are a couple of other examples of queries that produce the same result:

    var query2 = planetsDocument.Root.Elements().First().Element(”Orbit”);
    var query2 = planetsDocument.Root.Elements().ElementAt(0).Element(”Orbit”);

These can also not be translated into XAML because we don’t provide property descriptors for First() and ElementAt(…). So, if you have an XLinq query in code that you want to translate into XAML, you should try to first modify it to use only the methods we support. Of course, this will only work for simple queries - if you are using more advanced features of XLinq, your only option is to use code.

You may have noticed that the XmlDataProvider binding above uses both XPath and Path. This scenario is fully supported and sometimes very useful. The data binding engine will first resolve the XPath, and then it will call the OuterXml property on the resulting XmlElement. If I hadn’t called OuterXml, the query would return only the content of this element.

List of Planet Names

    
    // XLinq code
    var query3 = planetsDocument.Root.Elements();
    foreach (var element in query3)
    {
        Console.WriteLine(element.Attribute(”Name”).Value);
    }
    
    // XLinq XAML
    <StackPanel Name=”XLinqPanel3″>
        <StackPanel.Resources>
            <DataTemplate x:Key=”PlanetTemplate”>
                <TextBlock Text=”{Binding Path=Attribute[Name].Value}” />
            </DataTemplate>
        </StackPanel.Resources>
        …
        <ListBox ItemsSource=”{Binding Path=Root.Elements}” ItemTemplate=”{StaticResource PlanetTemplate}” … />
    </StackPanel>
    
    // XPath
    <StackPanel Name=”XMLPanel3″ Margin=”10,0,0,0″>
        <StackPanel.Resources>
            <DataTemplate x:Key=”PlanetTemplate”>
                <TextBlock Text=”{Binding XPath=@Name}” />
            </DataTemplate>
        </StackPanel.Resources>
        …
        <ListBox ItemsSource=”{Binding XPath=/SolarSystemPlanets/Planet}” ItemTemplate=”{StaticResource PlanetTemplate}” …/>                    
    </StackPanel>
    
    // Result
    //Mercury
    //Venus
    // …
    //Pluto

In this sample I show how to bind to a collection and how to display an attribute. Notice that in XLinq, if I only want to show the content of the attribute, I have to explicitly call its Value property. If I don’t call Value, each item in the ListBox will display something like ‘Name=”Mercury”‘. In XML, binding to the attribute @Name will give me the contents of the attribute automatically.

List of Planet Diameters

    
    // XLinq code
    var query4 = planetsDocument.Root.Descendants(”Diameter”);
    foreach (var element in query4)
    {
        Console.WriteLine(element.Value);
    }
    
    // XLinq XAML
    <ListBox ItemsSource=”{Binding Path=Root.Descendants[Diameter]}” DisplayMemberPath=”Value” …/>
    
    // XPath
    <ListBox ItemsSource=”{Binding XPath=/SolarSystemPlanets/*/Diameter}” …/>
    
    // Result
    //4,880 km
    //12,103.6 km
    // …
    //2274 km

I added this sample to show binding to the “Descendants” property, and the corresponding XML. Pretty simple.

Non-formatted XML

    
    // XLinq code
    var query5 = planetsDocument.Root.Element(”Planet”).ToString(SaveOptions.DisableFormatting);
    Console.WriteLine(query5);
    
    // XLinq XAML
    <TextBlock Text=”{Binding Path=Root.Element[Planet].Xml}” …/>
    
    // XPath
    <TextBlock Text=”{Binding XPath=/SolarSystemPlanets/Planet[1], Path=OuterXml}” …/>
    
    // Result
    // <Planet Name=”Mercury”><Orbit>57,910,000 km (0.38 AU)</Orbit><Diameter>4,880 km</Diameter><Mass>3.30e23 kg</Mass><Details xmlns=”http://myNamespace”>The small and rocky planet Mercury is the closest planet to the Sun.</Details></Planet>

The Xml property allows you to query for the unformatted version of the XML. This is the only property whose name is quite different from the corresponding method in code (as you can imagine, it would be quite cumbersome to keep them consistent). If you use the same XLinq query in XAML but without “.Xml” in the Path, you would get XML that is formatted with indentation.

HasAttributes of Mass

    
    // XLinq code
    var query6 = planetsDocument.Root.Element(”Planet”).Element(”Mass”).HasAttributes;
    Console.WriteLine(query6);
    
    // XLinq XAML
    <TextBlock Text=”{Binding Path=Root.Element[Planet].Element[Mass].HasAttributes}” />
    
    // XPath
    <TextBlock Text=”{Binding XPath=/SolarSystemPlanets/Planet[1]/Mass, Path=HasAttributes}” />
    
    // Result
    // false

HasAttributes is a property that exists both in XmlElement and XElement. You can see in this example that since XLinq uses Path, it’s easy to bind to properties exposed directly in XElement. This same scenario using XPath has to be implemented by combining XPath and Path.

First Planet’s Details

    
    // XLinq code
    XNamespace ns = “http://myNamespace”;
    var query7 = planetsDocument.Root.Element(”Planet”).Element(ns + “Details”).Value;
    Console.WriteLine(query7);
    
    // XLinq XAML
    <TextBlock Text=”{Binding Path=Root.Element[Planet].Element[{http://myNamespace}Details].Value}” />
    
    // XPath
    <TextBlock Text=”{Binding XPath=/SolarSystemPlanets/Planet[1]/mn:Details}” />
    
    // Result
    //The small and rocky planet Mercury is the closest planet to the Sun.

This sample shows how to embed namespaces in XAML. Below you can see that when I instantiated the XmlDataProvider, I also added an XmlNamespaceMapping that allows me to use the string “mn” instead of the full namespace. That is how the data binding engine knows how to resolve the namespace in “mn:Details” correctly.

    XmlDataProvider xdp = new XmlDataProvider();
    xdp.Source = new Uri(”Planets.xml”, UriKind.Relative);
    XmlNamespaceMapping mapping = new XmlNamespaceMapping(”mn”, new Uri(”http://myNamespace”));
    XmlNamespaceMappingCollection mappingCollection = new XmlNamespaceMappingCollection();
    mappingCollection.Add(mapping);
    xdp.XmlNamespaceManager = mappingCollection;

In XLinq, however, there is no concept of namespace prefixes. This decision was made in order to avoid confusion caused by the fact that prefixes depend on context to have meaning. For this reason, we need to include the full namespace directly in the XAML query as I show above. See what the XLinq team has to say about namespaces and prefixes.

First Planet’s info

    
    // XLinq code
    var query8 = planetsDocument.Root.Element(”Planet”).Elements();
    foreach (var element in query8)
    {
        Console.WriteLine(element.Value);
    }
    
    // XLinq XAML
    <StackPanel Name=”XLinqPanel8″>
        <StackPanel.Resources>
            …
            <DataTemplate DataType=”Mass”>
                <StackPanel Orientation=”Horizontal”>
                    <TextBlock FontWeight=”Bold” Text=”Mass: ” />
                <TextBlock Text=”{Binding Path=Value}” />
            </StackPanel>
            </DataTemplate>

            <DataTemplate DataType=”{}{http://myNamespace}Details”>
                <StackPanel Orientation=”Horizontal”>
                    <TextBlock FontWeight=”Bold” Text=”Details: ” />
                <TextBlock Text=”{Binding Path=Value}” />
            </StackPanel>
            </DataTemplate>
        </StackPanel.Resources>
        …
        <ItemsControl ItemsSource=”{Binding Path=Root.Element[Planet].Elements}” HorizontalAlignment=”Left” />
    </StackPanel>
    
    // XPath
    <StackPanel Name=”XMLPanel8″>
        <StackPanel.Resources>
            …
            <DataTemplate DataType=”Mass”>
                <StackPanel Orientation=”Horizontal”>
                    <TextBlock FontWeight=”Bold” Text=”Mass: ” />
                <TextBlock Text=”{Binding XPath=.}” />
            </StackPanel>
            </DataTemplate>

            <DataTemplate DataType=”mn:Details”>
                <StackPanel Orientation=”Horizontal”>
                    <TextBlock FontWeight=”Bold” Text=”Details: ” />
                <TextBlock Text=”{Binding XPath=.}” />
            </StackPanel>
            </DataTemplate>
        </StackPanel.Resources>
        …
        <ItemsControl ItemsSource=”{Binding XPath=/SolarSystemPlanets/Planet[1]/*}” HorizontalAlignment=”Left” />
    </StackPanel>
    
    // Result
    //57,910,000 km (0.38 AU)
    //4,880 km
    //3.30e23 kg
    //The small and rocky planet Mercury is the closest planet to the Sun.

In the previous sample, you may have noticed that XLinq namespaces are surrounded by curly braces in XAML. This worked well when used inside of an indexer, however, since curly braces already have meaning for the XAML parser, sometimes we need to escape them. For example, in the DataType of the Details DataTemplate in this sample, I had to escape the namespace with a leading {}. Remember that when binding to objects, the DataType property could be set to a value such as {x:Type local:MyDataObject}, so escaping the namespace allows the parser to distinguish between the two scenarios.

Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under WPF Data Binding | Comments (0)

November 25, 2007

How can I replace PowerPoint with WPF in my presentations?

If you’ve attended one of my presentations before, you’ve noticed that I never use PowerPoint - instead, I have my slides coded in WPF. I’ve decided to write my own presentation code because I want my slides to be interactive - instead of showing a concept in PowerPoint and switching to VS to demo it, I can demo it directly within the presentation. Several people have asked me for my presentation code in the past, so I’ve decided to share it here in my blog.

Use this project

You don’t have to read the whole blog post if all you want to do is use my code as a base for your presentation. Here are the steps to add your slides to this project:

  • You may want to delete the pages I created (01Title.xaml, 02Slide.xaml, 03Slide.xaml).
  • Add a new WPF Page for each new slide you want to create.
  • In the “Presentation.cs” file, add the name of your xaml slide to the “slides” string array, in the order you want them to appear.

Understand the code

Yeah, I thought all of you geeks would continue reading. I will explain below how I implemented each of the features in this project.

Navigation

I added two elements to the Window’s main grid:

  • A frame that displays each slide in turn. Notice that its Source is data bound to the CurrentSlide property of a data source. The CurrentSlide property will contain the name of the XAML Page that is being displayed. I will show what that data source looks like later in this section.
  • A TextBlock that contains the title of the slide. Notice that this TextBlock is data bound to the Title of the Frame’s content (which is really the Title of each slide Page).

These two elements are wrapped by a Viewbox. I find the Viewbox quite handy in this scenario because I want my slides to scale correctly for every screen resolution.

    <Grid x:Name=”LayoutRoot”>
        <Viewbox Margin=”10,20,10,40″ Stretch=”Uniform”>
            <StackPanel>
                <!– Title –>
                <TextBlock VerticalAlignment=”Top” Height=”84″ FontFamily=”Calibri” FontSize=”65″ FontWeight=”Bold” Text=”{Binding Path=Content.Title, ElementName=Frame, Mode=Default}” TextAlignment=”Center” TextWrapping=”Wrap”/>
                <!– Content of the slide –>
                <Frame Width=”1000″ Height=”600″ Source=”{Binding Path=CurrentSlide, Source={StaticResource presentation}}” x:Name=”Frame” NavigationUIVisibility=”Hidden” Background=”{x:Null}” Focusable=”False”/>
            </StackPanel>
        </Viewbox>
        …
    </Grid>
    

The data source that contains the slide information contains a private array with the names of the slides, and it exposes public properties for CurrentSlide and CurrentIndex.

    private string[] slides = { “01Title.xaml”, “02Slide.xaml”, “03Slide.xaml” };

In the XAML above, you can see that I disabled the usual frame navigation UI by setting NavigationUIVisibility to Hidden. Instead, I decided to add custom “previous” and “next” buttons in the shape of arrows. I designed the arrows in Blend, added IsMouseOver, IsPressed and IsEnabled triggers that cause changes in their appearance, and used those assets within the style of my “previous” and “next” buttons. I added GoBack() and GoNext() methods to the data source that decrement and increment the CurrentIndex, and made sure that those are called in the click handlers of the arrow buttons.

Last, I wanted to disable the “next” button upon reaching the end of the presentation, and disable the “previous” button at the start of the presentation. To do this, I added CanGoBack and CanGoNext properties to my data source, to which I bound the IsEnabled property of the arrow buttons:

    public bool CanGoBack
    {
        get { return this.currentIndex > 0; }
    }
    
    public bool CanGoNext
    {
        get { return this.currentIndex < this.slides.Length - 1; }
    }
    
    <Button IsEnabled=”{Binding Path=CanGoNext, Source={StaticResource presentation}}” … Style=”{DynamicResource NextButtonStyle}” />

This is really all it took to have a basic but functional presentation program.

Pick starting slide

Sometimes I like to stop the slide application to change some xaml or code in one of the slides, and then restart it to show the changes during a live demo. However, I don’t want to restart the whole slide show; I want to skip directly to the slide that I just edited. So I added the following code to the constructor of the Window in order to find the xaml file with the most recent modification time:

    public Window1()
    {
        …
        // When restarting this app, it will start the slide show in the last edited XAML page.
        int indexLastWritten = 0;
        DateTime latestDateTimeWritten = DateTime.MinValue;
        for (int i = 0; i < presentation.Slides.Length; i++)
        {
            string slide = presentation.Slides[i];
            DateTime dateLastWritten = File.GetLastWriteTime(@”..\..\” + slide);
            if (dateLastWritten.CompareTo(latestDateTimeWritten) > 0)
            {
                latestDateTimeWritten = dateLastWritten;
                indexLastWritten = i;
            }
        }
    
        presentation.CurrentIndex = indexLastWritten;
        …
    }

Hide cursor

I found that sometimes the cursor gets in the way during a presentation. I want it to be visible when I move the mouse, but I want it to disappear when I’m just talking about a slide or demo. I wrote some code that causes the cursor to disappear when I don’t move the mouse for 5 seconds, and causes it to reappear when I move the mouse again.

In the constructor for the Window, I added a DispatcherTimer that ticks every 5 seconds, and started it. In the tick handler for this timer, I set the cursor to Cursors.None, causing it to disappear. Then I added handlers for the mouse move and mouse click events, which restart the timer and make the cursor go back to its default look.

    private DispatcherTimer timer;
    
    public Window1()
    {
        …
        // Initializes timer that causes the mouse cursor to disappear after 5 seconds.
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(5);
        timer.Tick += new EventHandler(Timer_Tick);
        timer.Start();
    }
    
    private void Timer_Tick(object sender, EventArgs e)
    {
        this.Cursor = Cursors.None;
    }
    
    private void ShowCursor()
    {
        timer.Start();
        this.ClearValue(FrameworkElement.CursorProperty);
    }
    
    private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        ShowCursor();
        …
    }
    
    private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        ShowCursor();
    }

Animation when clicking

When attending a presentation, it is hard to know when the presenter is hovering over an element, and when she’s clicking on it. For some of my demos, it is actually important for the customers attending to recognize those gestures, so I started thinking of ways to make this distinction obvious.

I ended up deciding to add a small animation near the mouse pointer when I click anywhere within the Window. I used Blend to get the animation set up, to make sure it gets triggered on PreviewMouseLeftButtonDown, and to control the visibility of the canvas containing the animation (this canvas should be always collapsed except when I click on the Window). Then I wrote a little bit of code to make sure that the canvas is positioned at the mouse location when the click happens:

    private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        ShowCursor();
        Point currentPoint = e.GetPosition(this);
        this.clickCanvas.RenderTransform = new TranslateTransform(currentPoint.X, currentPoint.Y);
    }

Keyboard shortcuts

Finally, I realized that it was a bit impractical to always have to use the mouse to move to the next slide, so I decided to add support for KeyDown events. My event handler jumps to the previous or next slide when I press the left or right arrow key, and exits the presentation when I press the escape key.

    private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Left)
        {
            presentation.GoBack();
            e.Handled = true;
        }
        else if (e.Key == Key.Right)
        {
            presentation.GoNext();
            e.Handled = true;
        }
        else if (e.Key == Key.Escape)
        {
            Application.Current.Shutdown();
            e.Handled = true;
        }
    }

Conclusion

As you can see, my presentation program is quite simple but effective. Hopefully, those of you who do WPF presentations will find it as useful as I do.

Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under WPF General | Comments (0)

September 22, 2007

Are there any tricks that will help me improve TreeView’s performance? - Part III

In part I of my discussions on TreeView performance, I presented the three main limitations in the current implementation of TreeView that may lead to slower performance:

  • UI elements stay in memory even when collapsed.
  • There is no UI virtualization.
  • There is no data virtualization.

In part II, I talked about a solution where I introduced a middle tier between the UI and the data layer, that discards the data when a TreeViewItem is collapsed, and brings the data back into memory when a TreeViewItem is expanded. This solution completely fixes the first limitation of TreeView - the UI elements no longer stay in memory after expanding and collapsing a TreeViewItem. It also partially fixes the lack of data virtualization in TreeView because we only keep a small portion of the data in memory. I say “partially” because it virtualizes data on expand/collapse, but it does not take scrolling into account.

Today I will discuss a solution that builds on top of the previous one by providing UI virtualization.

With the current version of WPF, only two Controls offer UI virtualization: ListBox and ListView (actually, ListView gets it for free because it derives from ListBox). The work to make virtualization happen is done by VirtualizingStackPanel, which is the panel used by default in ListBox. It would be nice if we could simply tell TreeView to use a VirtualizingStackPanel to lay out its items, but unfortunately it’s not that simple. VirtualizingStackPanel understands only flat lists of items, so it is not capable of laying out the hierarchical data required for a TreeView.

On the other hand, styles and templates are among the most powerful features of WPF because they allow you to completely change the look of a control while retaining its behavior. For example, this post shows how a ListBox can easily be customized to look like a diagram of our solar system. With this in mind, Ben Carter (an awesome dev on the WPF team) had the brilliant idea of simply making a ListBox look like a TreeView. This allows us to use VirtualizingStackPanel for free, which offers UI virtualization. And you will see how easy it is to make a ListBox look like a TreeView, thanks to the power of styles and templates in WPF. We’ll need to make a few changes to the data side, but I will explain what they are.

I started by thinking about the theming portion of this scenario. To make my ListBox look like a TreeView, I need the toggle button that expands and collapses TreeViewItems. I used Blend, once again, to dig into the default style for the ToggleButton in TreeViewItem (which in the Aero theme looks like a triangle), and copied it to the window’s resources. This style contains triggers to change its look when the mouse is over it, and to rotate it when a user clicks on it. Then I added the following DataTemplate to the ItemTemplate property of my ListBox:

    …
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation=”Horizontal”>
                <ToggleButton x:Name=”tb” ClickMode=”Press” Style=”{StaticResource ExpandCollapseToggleStyle}”/>
                <TextBlock Text=”{Binding Path=ShortName}” />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
    …

I tested it with a rudimentary data source - a simple (non-hierarchical) ObservableCollection of RegistryKey items that contain a ShortName property. This helped me understand how I need my data to be presented to the ListBox.

Adding and removing items in the ObservableCollection

My first realization was that my data source can not be hierarchical this time, because ListBox only understands flat lists of data. So I will need to have a single ObservableCollection with the data for all the visible items, regardless of their depth in the original hierarchy. I will have to update this list of visible items any time the user expands or collapses an item. When the user expands an item, I will insert the item’s children just after it in the ObservableCollection. When the user collapses an item, I will remove its children from the ObservableCollection. Here is the code I wrote to make this happen:

    public class RegistryData3 : INotifyPropertyChanged
    {
        private ObservableCollection<RegistryKeyHolder3> allKeys;
        …
        
        public ObservableCollection<RegistryKeyHolder3> AllKeys
        {
            get { return allKeys; }
        }
        
        …
        public RegistryData3()
        {
            this.allKeys = new ObservableCollection<RegistryKeyHolder3>();
            this.AddNewKeyHolder(Registry.CurrentUser);
            this.AddNewKeyHolder(Registry.CurrentConfig);
            …
        }
        
        private void AddNewKeyHolder(RegistryKey registryKey)
        {
            RegistryKeyHolder3 newKeyHolder = new RegistryKeyHolder3(registryKey, 0);
            newKeyHolder.PropertyChanged += new PropertyChangedEventHandler(KeyHolder_PropertyChanged);
            this.allKeys.Add(newKeyHolder);
        }
        
        public void PopulateSubKeys(RegistryKeyHolder3 parentKeyHolder)
        {
            int indexParentKey = this.allKeys.IndexOf(parentKeyHolder);
            if (indexParentKey == this.allKeys.Count - 1 || this.allKeys[indexParentKey + 1].Level <= parentKeyHolder.Level)
            {
                string[] subKeyNames = parentKeyHolder.Key.GetSubKeyNames();
                for (int i = 0; i < subKeyNames.Length; i++)
                {
                    RegistryKeyHolder3 childKeyHolder = new RegistryKeyHolder3(parentKeyHolder.Key.OpenSubKey(subKeyNames[i]), parentKeyHolder.Level + 1);
                    childKeyHolder.PropertyChanged += new PropertyChangedEventHandler(KeyHolder_PropertyChanged);
                    allKeys.Insert(indexParentKey + i + 1, childKeyHolder);
                    …
                }
            }
        }
        …
        
        public void ClearSubKeys(RegistryKeyHolder3 parentKeyHolder)
        {
            int indexToRemove = this.allKeys.IndexOf(parentKeyHolder) + 1;
            while ((indexToRemove < this.allKeys.Count) && (this.allKeys[indexToRemove].Level > parentKeyHolder.Level))
            {
                this.allKeys.RemoveAt(indexToRemove);
                …
            }
        }
        
        …
    }

The PopulateSubKeys method is responsible for adding an item’s children to the ObservableCollection when the user expands that item. This method retrieves the children of the parent item, creates a RegistryKeyHolder3 instance for each item and inserts those instances starting at the index just after the parent. Don’t worry about the Level concept you see in this code; I will explain it in the next section. I will also explain and show the code for the property changed event handler later in this post.

The ClearSubKeys method removes an item’s children from the list, and is called when the user collapses the parent. It starts removing items from the list in the index after the parent’s and continues until the expected number of items has been removed.

These two methods allow us to keep a flat list with the items in the order we want the ListBox to display them. Adding items to and removing items from the flat list achieves partial data virtualization, just like the solution in my previous post.

Indentation

I also realized that I need to tag each RegistryKeyHolder3 data item with the level they belong to, which will help me figure out how much they have to be indented in the ListBox. I decided to add a property called “Level” to the RegistryKeyHolder3 class for that purpose. For the root keys the Level property will be set to 0, for the next level it will be set to 1, and so on. Notice that while constructing the children key holders, the code in PopulateSubKeys specifies that the level of the children is the parent’s level incremented by 1. Also, in the ClearSubKeys method, one of the conditions to stop removing children is encountering an item that has the same level as the one being collapsed.

To indent the items in the UI based on the Level value, I added a Border to the left of the expander and text and bound its Width property to the Level property in the source:

    …
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation=”Horizontal”>
                <Border Width=”{Binding Path=Level, Converter={StaticResource ConvertLevelToIndent}}” />
                <ToggleButton x:Name=”tb” ClickMode=”Press” … Style=”{StaticResource ExpandCollapseToggleStyle}”/>
                <TextBlock Text=”{Binding Path=ShortName}” />
            </StackPanel>
            …
        </DataTemplate>
    </ListBox.ItemTemplate>
    …

In order to convert a Level value to the Border’s Width, I defined the following converter:

    public class ConvertLevelToIndent : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return (int)value * 16;
        }
        
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException(”Not supported - ConvertBack should never be called in a OneWay Binding.”);
        }
    }

Parent item expansion

I decided to add an “IsExpanded” property to the RegistryKeyHolder3 class that will help me tie the children expansion on the data side with the visual rotation of the toggle button in the UI.

    …
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation=”Horizontal”>
                <Border Width=”{Binding Path=Level, Converter={StaticResource ConvertLevelToIndent}}” />
                <ToggleButton x:Name=”tb” ClickMode=”Press” IsChecked=”{Binding Path=IsExpanded}” Style=”{StaticResource ExpandCollapseToggleStyle}”/>
                <TextBlock Text=”{Binding Path=ShortName}” />
            </StackPanel>
            …
        </DataTemplate>
    </ListBox.ItemTemplate>
    …

If you take a look at the ToggleButton’s XAML, you will notice that its IsChecked property is bound to the IsExpanded property. The Mode of this binding is TwoWay - no Mode is defined explicitly, but I know that’s the default Mode for the IsChecked DP.

Also, if you look at the code that adds items to the list in the PopulateSubKeys method, you will notice that I added KeyHolder_PropertyChanged as the handler for the PropertyChanged event on RegistryKeyHolder3. Here is the code for that event handler:

    void KeyHolder_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == “IsExpanded”)
        {
            RegistryKeyHolder3 keyHolder = (RegistryKeyHolder3)sender;
            if (keyHolder.IsExpanded)
            {
                this.PopulateSubKeys(keyHolder);
            }
            else
            {
                this.ClearSubKeys(keyHolder);
            }
        }
    }

When the application starts, all the items appear collapsed because the IsExpanded property of each data item is initialized to false, and the IsChecked property is bound to IsExpanded. Here is what happens when the user expands an item:

1) When the user clicks on the ToggleButton to expand an item, IsChecked becomes true, and because of the TwoWay binding, the IsExpanded property for the corresponding data item is set to true.

2) RegistryKeyHolder3 raises a PropertyChanged event when its IsExpanded property changes, causing the code in the handler (the KeyHolder_PropertyChanged method in RegistryData3) to be executed.

3) Because IsExpanded is true for this data item, the PopulateSubKeys method on RegistryData3 is called, causing the children of this item to be added to the list and displayed in the ListBox.

You can imagine a similar sequence of events when the user clicks to collapse an item.

Visibility of the expander

Lastly, I wanted to make the expander for a particular item hidden whenever that item has no children. I was able to do this by adding a simple DataTrigger that causes the ToggleButton to be hidden whenever the Key’s SubKeyCount property is zero, and visible otherwise. You can see the complete XAML for the ItemTemplate’s DataTemplate here:

    …
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation=”Horizontal”>
                <Border Width=”{Binding Path=Level, Converter={StaticResource ConvertLevelToIndent}}” />
                <ToggleButton x:Name=”tb” ClickMode=”Press” IsChecked=”{Binding Path=IsExpanded}” Style=”{StaticResource ExpandCollapseToggleStyle}”/>
                <TextBlock Text=”{Binding Path=ShortName}” />
            </StackPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding=”{Binding Path=Key.SubKeyCount}” Value=”0″>
                    <Setter Property=”Visibility” TargetName=”tb” Value=”Hidden”/>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ListBox.ItemTemplate>
    …

Conclusion

This solution provides true UI virtualization, as you can see in the screenshot below, where I expanded the three first items (in depth first order). If you scroll the third TreeView (or should I say ListBox?), you will see that for a little while the number of UI elements in memory increases, but it quickly settles on a number much lower than the other two TreeViews. This delay happens because we queue in the dispatcher the operation to clean up those items with a low priority so that it doesn’t make the UI unresponsive.

And just like the solution in my previous post, this solution discards children elements on collapse and provides a partial data virtualization solution.

So, should you all switch your TreeViews to ListBoxes? Well, as with almost everything in life, there is a price to pay for the benefits of this solution: the programming model is more cumbersome than if you were using a TreeView. You will not be able to use HierarchicalDataTemplates to style your items, you’ll miss the convenience properties and methods of TreeView, you’ll have to introduce a slightly complex intermediate layer between your UI and your data, and you will have to work hard to minimize the inconsistencies in the UI. In short, you can make a ListBox look like a TreeView, but you can’t make a ListBox become a TreeView.

Whether this solution is right for you depends on how much you value the performance gain over the disadvantages it brings.

Here you can find the project with this code built using VS 2008 RTM.

Posted by Bea under TreeView, WPF Data Binding | Comments (25)

September 14, 2007

My blog just got a face lift!

As you may have noticed, I just moved my blog from Blogger to WordPress. Sorry if you got duplicate copies of old blog posts in your blog reader. If you have trouble with my web site, please let me know - I’ll do my best to fix it right away!

Here are some features of the new blog:

- You can search my site.

- Posts are tagged with categories and subcategories.

- Comments are threaded.

- You can now subscribe to the comments.

- To avoid spamming those who subscribe to comments, a new comment will appear only after I approve it. So don’t panic if your comment doesn’t show up right after you submit it.

And to make your life easier:

- Old links still work, but they will be redirected to the new pages. Please update any links to my blog.

- The RSS feed is the same - http://feeds.feedburner.com/BeatrizCosta.

Any feedback about the new blog is appreciated!

Posted by Bea under General | Comments (12)

September 10, 2007

Are there any tricks that will help me improve TreeView’s performance? - Part II

In my previous post, I discussed some of the performance limitations of TreeView. In particular, I mentioned the three facts about our current implementation that may lead to performance issues, depending on your scenario:

- UI elements stay in memory even when collapsed.
- There is no UI virtualization.
- There is no data virtualization.

Today I will talk about a trick that avoids the first problem and partially fixes the third. In my previous post, I always kept the data for all levels of the TreeView in memory. In this project, I only load subkey data items when their parent key node is expanded, and I discard those data items when their parent node is collapsed. You can think of the class that manages which portions of the data source stay in memory as an intermediate custom source, which sits between your UI and the complete source of your items. In this case, the complete source for the registry keys consists of the APIs used to load them into memory, but you can easily imagine how this could be a SQL database or a webservice. If you’ve read John Gossman’s thoughts on the Model-View-ViewModel pattern, you can think of this intermediate class as the “ViewModel” section.

Let’s start by looking at the custom data source. Similarly to my previous post, I have a RegistryKeyHolder2 class that contains a ShortName property and another property that holds the SubKeys collection of type ObservableCollection<RegistryKeyHolder2>. I also have a PopulateSubKeys() method that fills the SubKeys collection with instances of the children keys, which I showed in my previous post. The only new method I added to this class is ClearSubKeys(), which I will use to discard items from memory when I collapse a TreeViewItem:

    public int ClearSubKeys()
    {
        int subKeyCount = CountSubKeys(this);
        this.subKeys.Clear();
        return subKeyCount;
    }

Just like last week’s sample, I have a RegistryData2 class that contains a RootKeys property of type ObservableCollection<RegistryKeyHolder2>. This will hold the first level of keys displayed in the TreeView, as well as the whole hierarchy of keys that is displayed underneath it. However, unlike my previous post, this class has methods that will populate and clear the keys of just one level, and not the whole hierarchy. I implemented it this way because I will populate or clear items only when the TreeViewItems are expanded and collapsed, and that will only ever affect one level at a time.

    public void PopulateSubKeys(RegistryKeyHolder2 keyHolder)
    {
        int itemsAddedCount = keyHolder.PopulateSubKeys();
        …
    }
    
    public void ClearSubKeys(RegistryKeyHolder2 keyHolder)