Friday, December 12, 2008

How to sort a BindingList using CollectionViewSource

If you've ever tried to sort a BindingList (or any other collection implementing IBindingList) using CollectionViewSource then you've most probably not had much luck. The BindingListCollectionView used for this task lacks the functionality of ListCollectionView and on top of that has a bug making it even more onerous to use. This post describes a workaround so you can use ListCollectionView instead.

BindingListCollectionView unfortunately doesn't have the sorting capabilities of ListCollectionView. It only uses the in-built sorting defined in IBindingList instead of doing it itself and if that's not enabled it will throw an exception. Not very useful.

On top of that, if your BindingList doesn't implement ITypedList then the sorting fails silently even if you have implemented the sorting!

Here's a nice trick to adapt a BindingList to an ObservableCollection and therefore make the CollectionViewSource use the ListCollectionView implementation for sorting:

class ExtendedCollectionViewSource : CollectionViewSource
{
    private BindingListAdapter mAdapter;

    static ExtendedCollectionViewSource()
    {
        CollectionViewSource.SourceProperty.OverrideMetadata(
            typeof(ExtendedCollectionViewSource),
            new FrameworkPropertyMetadata(null, CoerceSource));
    }

    private static object CoerceSource(DependencyObject d, object baseValue)
    {
        ExtendedCollectionViewSource cvs = (ExtendedCollectionViewSource)d;
        if (cvs.mAdapter != null)
        {
            cvs.mAdapter.Dispose();
            cvs.mAdapter = null;
        }
        IBindingList bindingList = baseValue as IBindingList;
        if (bindingList != null)
        {
            cvs.mAdapter = new BindingListAdapter(bindingList);
            return cvs.mAdapter;
        }
        return baseValue;
    }
}

private class BindingListAdapter : ObservableCollection<object>, IDisposable
{
    private readonly IBindingList mBindingList;
    private bool mIsDisposed;

    public BindingListAdapter(IBindingList bindingList)
    {
        if (bindingList == null)
        {
            throw new ArgumentNullException("bindingList");
        }

        mBindingList = bindingList;

        foreach (object item in mBindingList)
        {
            Items.Add(item);
        }

        mBindingList.ListChanged += BindingList_ListChanged;
    }

    private void BindingList_ListChanged(object sender, ListChangedEventArgs e)
    {
        if (e.ListChangedType == ListChangedType.ItemAdded)
        {
            InsertItem(e.NewIndex, mBindingList[e.NewIndex]);
        }
        else if (e.ListChangedType == ListChangedType.ItemChanged)
        {
            SetItem(e.NewIndex, mBindingList[e.NewIndex]);
        }
        else if (e.ListChangedType == ListChangedType.ItemDeleted)
        {
            RemoveItem(e.NewIndex);
        }
        else if (e.ListChangedType == ListChangedType.ItemMoved)
        {
            MoveItem(e.OldIndex, e.NewIndex);
        }
        else if (e.ListChangedType == ListChangedType.Reset)
        {
            Items.Clear();

            foreach (object item in mBindingList)
            {
                Items.Add(item);
            }
            
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Reset));
        }
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!mIsDisposed)
        {
            if (disposing)
            {
                mBindingList.ListChanged -= BindingList_ListChanged;
            }
        }
        mIsDisposed = true;
    }

    #endregion
}



ExtendedCollectionViewSource.cs



Thursday, December 11, 2008

How to set the size and position of a scroll bar thumb

The WPF scrollbars seem to be unduly difficult to use. This post describes why and defines some extension methods to make life a little easier.

The ScrollBar class has these properties to specify the thumb location and the thumb size:
  • Value - represents the thumb position
  • ViewportSize - represents the thumb location
Value varies between Maximum and Minimum. So if Maximum=100, Minimum=0 and Value=50. Then the thumb is in the middle, this is definitely the more sensible of the two properties. However, it's not great: it doesn't directly represent the absolute position of the thumb on the track.

ViewportSize varies between 0 and double.MaxValue. What!? What use is this to anyone?

What you nearly always have when you are implementing your own scrolling is the length of the thumb and the absolute position of the thumb. Here are some extension methods that convert these to and from the WPF properties.
public static class ScrollBarExtensions
{
    public static double GetThumbCenter(this ScrollBar s)
    {
        double thumbLength = GetThumbLength(s);
        double trackLength = s.Maximum - s.Minimum;        

        return thumbLength / 2 + s.Minimum + (s.Value - s.Minimum) * 
            (trackLength - thumbLength) / trackLength;
    }

    public static void SetThumbCenter(this ScrollBar s, double thumbCenter)
    {
        double thumbLength = GetThumbLength(s);
        double trackLength = s.Maximum - s.Minimum;

        if (thumbCenter >= s.Maximum - thumbLength / 2)
        {
            s.Value = s.Maximum;
        }
        else if (thumbCenter <= s.Minimum + thumbLength / 2)
        {
            s.Value = s.Minimum;
        }
        else if (thumbLength >= trackLength )
        {
            s.Value = s.Minimum;
        }
        else
        {
            s.Value = s.Minimum + trackLength  * 
                ((thumbCenter - s.Minimum - thumbLength / 2) 
                / (trackLength - thumbLength));
        }
    }

    public static double GetThumbLength(this ScrollBar s)
    {
        double trackLength = s.Maximum - s.Minimum;
        return trackLength * s.ViewportSize / 
            (trackLength + s.ViewportSize);
    }

    public static void SetThumbLength(this ScrollBar s, double thumbLength)
    {
        double trackLength = s.Maximum - s.Minimum;

        if (thumbLength < 0)
        {
            s.ViewportSize = 0;
        }
        else if (thumbLength < trackLength )
        {
            s.ViewportSize = trackLength * thumbLength / (trackLength - thumbLength);
        }
        else
        {
            s.ViewportSize = double.MaxValue;
        }
    }
}


ScrollBarExtensions.cs



Observable collections independent of WPF

I've recently been working with a client who has made the decision to keep the business logic assembly of their WPF application independent of WPF. This is fair enough, keeping the business logic separate from the presentation code and independent of WPF will promote decoupled design and reduce the pain down the line if the business logic needs to be transplanted elsewhere.

The client had the further requirement that the collections within the businsess logic should be observable by WPF in order to avoid the overhead of creating an entire View-Model layer to support the UI. Even though I'm unsure as to whether there's anyone else out there being so strict about their dependencies, implementing this turns out to be less straightforward than you'd expect, so I thought I'd write a post about it anyway.

First of all, here's a quick reminder of the interfaces that WPF data binding generally uses to observe the contents of a collection:
  • IList - used to traverse the items in a collection.
  • INotifyPropertyChanged - used to inform listeners that the value of a property of a collection has changed i.e. the count and the indexer properties.
  • INotifyCollectionChanged - used to inform listeners that a collection has changed in some way e.g. an item has been added, removed, moved etc.

The problem here is that INotifyCollectionChanged is part of WPF as it is defined in WindowsBase and therefore can't be used in the proposed structure. An alternative to using INotifyCollectionChanged is to use the somewhat older interface, System.ComponentModel.IBindingList, which has been around since .NET 1.0 supporting data binding in Windows Forms. IBindingList is also supported by WPF data binding and this post focuses on it as an alternative to INotifyCollectionChanged thus removing the dependency on WPF.

As well as change notification, IBindingList contains functionality for Adding, Removing, Sorting and Searching (it's a bit of a bizarre interface really, containing all this different functionality). In this implementation, sorting will be disabled since it was desired that it is handled in the view as it is for collections implementating INotifyCollectionChanged. Adding, removing and searching can all be implemented as you see fit but in this example I will be disabling them.

Implementing IBindingList is straightforward enough. If you were to implement it on your collection, it should end up looking something like this:
class MyObservableCollection<T> : Collection<T>, IBindingList
{
    protected override void ClearItems()
    {
        base.ClearItems();
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset), -1);
    }

    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
    }

    protected override void RemoveItem(int index)
    {
        base.RemoveItem(index);
        OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
    }

    protected override void SetItem(int index, T item)
    {
        base.SetItem(index, item);
        OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
    }

    protected virtual void OnListChanged(ListChangedEventArgs e)
    {
        var handler = ListChanged;
        if (handler != null) handler(this, e);
    }

    // IBindingList Members 
    public void AddIndex(PropertyDescriptor property) {}
    public object AddNew() {}
    public void ApplySort(PropertyDescriptor property, ListSortDirection direction) {}
    public int Find(PropertyDescriptor property, object key) {}
    public void RemoveIndex(PropertyDescriptor property) {}
    public void RemoveSort() {}
    public event ListChangedEventHandler ListChanged;
    public bool SupportsChangeNotification { get { return true; } } // Must return true
    public bool AllowEdit { get { return false; } }
    public bool AllowNew { get { return false; } }
    public bool AllowRemove { get { return false; } }
    public bool IsSorted { get { return false; } }
    public ListSortDirection SortDirection
    { get { throw new NotSupportedException(); } }
    public PropertyDescriptor SortProperty
    { get { throw new NotSupportedException(); } }
    public bool SupportsChangeNotification { get { return true; } }
    public bool SupportsSearching { get { return false; } }
    public bool SupportsSorting { get { return false; } }
}
So far, so good. We now have a collection that implements IBindingList and can therefore be used for data binding. So let's try it by binding a ListBox to the new collection:
<Window ...>
    <StackPanel>
        <Button Content="Add" Click="ButtonAdd_Click"/>
        <ListBox ItemsSource="{Binding}"/>
    </StackPanel>
</Window>
public partial class Window1 : Window
{
    private readonly MyObservableCollection<int> mCollection;
    private int mCounter;

    public Window1()
    {
        InitializeComponent();
        mCollection = new MyObservableCollection<int>();
        DataContext = mCollection;
    }

    private void ButtonAdd_Click(object sender, RoutedEventArgs e)
    {
        mCollection.Add(mCounter++);
    }
}
It works! You can click on the Add button and the listbox correctly updates. Now let's try sorting a view of the collection using a CollectionViewSource:
<Window ...>
    <Window.Resources>
        <CollectionViewSource x:Key="cvs" Source="{Binding}">
            <CollectionViewSource.SortDescriptions>
                <compModel:SortDescription Direction="Descending"/>
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
    </Window.Resources>
    <StackPanel>
        <Button Content="Add" Click="ButtonAdd_Click"/>
        <ListBox ItemsSource="{Binding Source={StaticResource cvs}}"/>
    </StackPanel>
</Window>
Hmmm...this isn't nearly so successful. The window now throws an exception on startup:

InvalidOperationException: 'System.Windows.Data.BindingListCollectionView' view does not support sorting.

This is because the BindingListCollectionView, the implementation of ICollectionView created by the CollectionViewSource for viewing collections implementing IBindingList is using the internal sorting mechanism provided by the IBindingList interface. Since SupportsSorting is set to false, an exception now gets thrown when an attempt is made to sort the collection. SupportsSorting can't be set to true as then sorting would have to actually be implemented by the collection and the requirement is for it to be handled in the view as it is for collections implementing INotifyCollectionChanged.

This poses a bit of a problem. Luckily, we have a dirty workaround to rememdy the situation.

CollectionViewSource can be subclassed and its Source property coerced into using an adapted version of original collection which implements INotifyCollectionChanged. This adapter maps the change events from the IBindingList interface to the equivalents on the INotifyCollectionChanged interface.

But...there's another problem. There isn't enough information from IBindingList's ListChangedEventArgs to fully populate INotifyCollectionChanged's NotifyCollectionChangedEventArgs. So we'll also need to subclass the ListChangedEventArgs class before raising the change events from the collection.

Here's a rundown of all the bits that are required. Firstly, here's the new CollectionViewSource which coerces an instance of the new BindingListAdapter class when its Source property gets set:
class ExtendedCollectionViewSource : CollectionViewSource
{
    private BindingListAdapter mAdapter;

    static ExtendedCollectionViewSource()
    {
        CollectionViewSource.SourceProperty.OverrideMetadata(
            typeof(ExtendedCollectionViewSource),
            new FrameworkPropertyMetadata(null, CoerceSource));
    }

    // This class should be kept internal as the Coerce is a little dodgy. 
    // Consumers of this class could reasonably expect the Source property
    // to return the same instance that was set to it.
    
    private static object CoerceSource(DependencyObject d, object baseValue)
    {
        ExtendedCollectionViewSource cvs = (ExtendedCollectionViewSource)d;
        if (cvs.mAdapter != null)
        {
            cvs.mAdapter.Dispose();
            cvs.mAdapter = null;
        }
        IBindingList bindingList = baseValue as IBindingList;
        if (bindingList != null)
        {
            cvs.mAdapter = new BindingListAdapter(bindingList);
            return cvs.mAdapter;
        }
        return baseValue;
    }
}
Next, the BindingListAdapter class, which adapts a class implementing IBindingList into one which implements INotifyCollectionChanged.
class BindingListAdapter : IList, IDisposable, INotifyPropertyChanged, 
    INotifyCollectionChanged
{
    private readonly IBindingList mBindingList;

    public BindingListAdapter(IBindingList bindingList)
    {
        mBindingList = bindingList;
        mBindingList.ListChanged += mBindingList_ListChanged;
    }

    private void mBindingList_ListChanged(object sender, ListChangedEventArgs e)
    {
        ExtendedListChangedEventArgs ee = (ExtendedListChangedEventArgs)e;
        
        if (e.ListChangedType == ListChangedType.ItemAdded)
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Add, mBindingList[e.NewIndex],
                e.NewIndex));
        }
        else if (e.ListChangedType == ListChangedType.ItemChanged)
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Replace, mBindingList[e.NewIndex],
                ee.Item));
        }
        else if (e.ListChangedType == ListChangedType.ItemDeleted)
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Remove, ee.Item, e.NewIndex));
        }
        else if (e.ListChangedType == ListChangedType.ItemMoved)
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Move, ee.Item, e.NewIndex, e.OldIndex));
        }
        else if (e.ListChangedType == ListChangedType.Reset)
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Reset, null, -1));
        }
    }
    
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }
    
    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        var handler = CollectionChanged;
        if (handler != null) handler(this, e);
    }

    // I've excluded the implementation of these interfaces here. 
    // Source code is available at the foot of the post.
    
    // IList Members
    // ICollection Members
    // IEnumerable Members
    // INotifyPropertyChanged Members
    // INotifyCollectionChanged Members
    // IDisposable
}
...and the ExtendedListChangedEventArgs class which contains extra information required to populate the INotifyCollectionChangedEventArgs.
class ExtendedListChangedEventArgs : ListChangedEventArgs
{
    public object Item { get; private set; }

    public ExtendedListChangedEventArgs(ListChangedType listChangedType, object item, 
        int newIndex) : base(listChangedType, newIndex)
    {
        Item = item;
    }

    public ExtendedListChangedEventArgs(ListChangedType listChangedType, object item, 
        int newIndex, int oldIndex) : base(listChangedType, newIndex, oldIndex)
    {
        Item = item;
    }
}
...and finally, the original collection, updated to use ExtendedListChangedEventArgs:
class MyObservableCollection<T> : Collection<T>, IBindingList
{
    protected override void ClearItems()
    {
        base.ClearItems();
        OnListChanged(
            new ExtendedListChangedEventArgs(ListChangedType.Reset, null, -1));
    }

    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        OnListChanged(
            new ExtendedListChangedEventArgs(ListChangedType.ItemAdded, item, index));
    }

    protected override void RemoveItem(int index)
    {
        T item = base[index];
        base.RemoveItem(index);
        OnListChanged(new ExtendedListChangedEventArgs(
            ListChangedType.ItemDeleted, item, index));
    }

    protected override void SetItem(int index, T item)
    {
        T oldItem = base[index];
        base.SetItem(index, item);
        OnListChanged(new ExtendedListChangedEventArgs(
            ListChangedType.ItemChanged, oldItem, index));
    }

    protected virtual void OnListChanged(ListChangedEventArgs e)
    {
        var handler = ListChanged;
        if (handler != null) handler(this, e);
    }

    // IBindingList Members (not shown here)
}
...and that (believe it or not) actually works. The ExtendedCollectionViewSource successfully coerces the collection into an adapted version of the original before passing it on to CollectionViewSource. The sorting is now entirely handled in the view as it is for collections implementing INotifyCollectionChanged.

As far as I know this approach has no disadvantages to using INotifyCollectionChanged (except that you have to write all of this rhubarb to make it work of course.)

Download Full Source Code



Monday, December 1, 2008

Setting the Context Menu on an editable ComboBox

There's a bug in the .NET 3.5 SP1 ComboBox template which stops you setting the context menu on the TextBox part of an editable ComboBox using a Style. This post describes the problem and outlines two workarounds.

This code snippet recreates the issue:
<Window.Resources>
    <ContextMenu x:Key="contextMenu">
        <ContextMenu.Items>
            <MenuItem Header="A Menu Item"/>
        </ContextMenu.Items>
    </ContextMenu>

    <Style TargetType="{x:Type TextBox}">
        <Setter Property="ContextMenu" Value="{StaticResource contextMenu}"/>
    </Style>

    <Style TargetType="{x:Type ComboBox}">
        <Setter Property="ContextMenu" Value="{StaticResource contextMenu}"/>
    </Style>
</Window.Resources>

<ComboBox IsEditable="True"/>

The context menu on the button part of the ComboBox gets set correctly, but the TextBox part is left with its default context menu:



The reason for this is that there is a missing TemplateBinding between the ComboBox and the contained TextBox in the default template. The TextBox style in the code snippet also fails to set the context menu; this is because the ComboBox itself internally sets a style to the TextBox, and it not permissable to have two different styles set to any one instance of a FrameworkElement.

The first workaround to the problem is to subclass the ComboBox and add the required template binding:
class ExtendedComboBox : ComboBox
{
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        
        // Use Snoop to find the name of the TextBox part
        // http://wpfmentor.blogspot.com/2008/11/understand-bubbling-and-tunnelling-in-5.html
        TextBox textBox = (TextBox)Template.FindName("PART_EditableTextBox", this);
        
        // Create a template-binding in code
        Binding binding = new Binding("ContextMenu");
        binding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
        BindingOperations.SetBinding(textBox, 
            FrameworkElement.ContextMenuProperty, binding);
        
    }
}
This approach does the job. The TextBox's ContextMenu property is now bound to the parent ComboBox and displays correctly.

The obvious problem with this approach is that all the ComboBoxes that require the new context menu now need to be replaced with this new class. This might well not be acceptable, perhaps the ComboBox has been subclassed already - you would need to create new versions for those too.

This second workaround avoids subclassing by using an Attached Behavior. If you are unfamiliar with the Attached Behavior pattern, go here for a great article by Josh Smith.

The Attached Behavior uses an Attached Property which can be set by a Style to create the Template Binding.
In order to create this, first write a new static class with an attached property thus:
public static class ComboBoxContextMenuBehavior
{
    public static bool GetIsContextMenuBound(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsContextMenuBoundProperty);
    }

    public static void SetIsContextMenuBound(DependencyObject obj, bool value)
    {
        obj.SetValue(IsContextMenuBoundProperty, value);
    }

    public static readonly DependencyProperty IsContextMenuBoundProperty =
        DependencyProperty.RegisterAttached(
        "IsContextMenuBound",
        typeof(bool),
        typeof(ComboBoxContextMenuBehavior),
        new UIPropertyMetadata(false, IsContextMenuBoundChanged));
)]
...and add a method to handle changes in the attached property value:
private static void IsContextMenuBoundChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    ComboBox comboBox = d as ComboBox;

    bool oldValue = e.OldValue is bool && (bool)e.OldValue;
    bool newValue = e.NewValue is bool && (bool)e.NewValue;

    if (comboBox != null && oldValue != newValue)
    {
        // Use Dispatcher with Loaded priority to ensure template has been
        // applied before applying the TemplateBinding
        comboBox.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, 
            (DispatcherOperationCallback)delegate
        {
            TextBox textBox = (TextBox)comboBox.Template.FindName(
                "PART_EditableTextBox", comboBox);

            if (textBox != null)
            {
                if (!oldValue && newValue)
                {
                    // Create TemplateBinding
                    Binding binding = new Binding("ContextMenu");
                    
                    binding.RelativeSource = new RelativeSource(
                        RelativeSourceMode.TemplatedParent);
                    
                    BindingOperations.SetBinding(textBox, 
                        FrameworkElement.ContextMenuProperty, 
                        binding);
                }
                else
                {
                    // Clear TemplateBinding
                    BindingOperations.ClearBinding(textBox, 
                        FrameworkElement.ContextMenuProperty);
                }
            }
            
            return null;
        }, null);
    }
}
The Attached Behavior can now be assigned to the ComboBox using a Style:
<Window.Resources>
    <ContextMenu x:Key="contextMenu">
        <ContextMenu.Items>
            <MenuItem Header="A Menu Item"/>
        </ContextMenu.Items>
    </ContextMenu>

    <Style TargetType="{x:Type ComboBox}">
        <Setter Property="ContextMenu" Value="{StaticResource contextMenu}"/>
        <Setter Property="local:ComboBoxContextMenuBehavior.IsContextMenuBound"
            Value="True"/>
    </Style>
</Window.Resources>

<ComboBox IsEditable="True"/>

...and that's all you need. You have now successfully added a TemplateBinding to the ComboBox without subclassing it, and without individually referencing each ComboBox as a Style is used to fix the problem.

So, in conclusion, this bug is annoying but can be easily worked around whether it is acceptable or not to subclass the ComboBox.

Download sample app with source code