Monday, February 20, 2012

XAML binding to CompositeCollection

Some times there is a need to display in your ItemsControl (like TreeView or ListBox) some compound collection, which consists from several collections of elements.
Well, microsoft provides us CompositeCollection and CollectionContainer classes for this purpose. However it is a real pain to use data binding with this classes in XAML. Here is one of solution suggested, which looks a bit ugly if you ask me: http://wilberbeast.com/2011/05/31/compositecollection-binding-problem/

I'll show you alternative you can use to make it looks a bit better and neat.

For example I have a view model of Survey, which has properties:

public class SurveyViewModel{
       public InstructionsModel Instructions {get;}
       public ObservableCollection<QuestionModel> Children {get;}
       public ReviewModel Review {get;}
}

And I need to display in a listbox the following items:
[Instructions]
[Question1]

[Question2]
...
[QuestionN]
[Review]

I keep in mind that Children collection can be updated - in that case ListBox must  be automagically refreshed (new element might be added/removed by some action buttons).
I want to each item to be displayed in a different manner based on its type and associated DataTemplate.In order to do that, i need to assign in myListBox.ItemsSource to CompositeCollection with CollectionContainers inside. But if you will try it you will see how many gabage you will have to put in different parts of your XAML. Here is the alternative approach how I do it now:

<TreeView
    x:Name="tvSurvey">
    <TreeView.ItemsSource>
        <MultiBinding>
            <MultiBinding.Converter>
                <local:CompositeCollectionConverter />
            MultiBinding.Converter>
            <Binding Path="ViewModel.Instructions" />
            <Binding Path="ViewModel.Children" />
            <Binding Path="ViewModel.Review" />
        </MultiBinding>
    </TreeView.ItemsSource>
...
     TreeView.Resources with datatemplates for:
     InstructionsModel, QuestionModel, ReviewModel
...
</TreeView>

As you already may see there is no magic here: just use MultiBinding, which supports current DataContext (unlike CollectionContainer) plus CompositeCollectionConverter (IMultiValueConverter) for converting multibinding to composite collection.

Here is the code of CompositeCollectionConverter:


    public class CompositeCollectionConverter:IMultiValueConverter
    {
 
        public object Convert(object[] values
            , Type targetType
            , object parameter
            , System.Globalization.CultureInfo culture)
        {
            var res = new CompositeCollection();
            foreach (var item in values)
                if (item is IEnumerable) 
                    res.Add(new CollectionContainer() 
                            { 
                                Collection = item as IEnumerable 
                            });
                else res.Add(item);
            return res;
        }
 
        public object[] ConvertBack(object value
            , Type[] targetTypes
            , object parameter
            , System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }



Enjoy :-)

XAML Explicit binding to Interface - Object 'null' cannot be used as an accessor parameter for a PropertyPath


Object 'null' cannot be used as an accessor parameter for a PropertyPath. An accessor parameter must be DependencyProperty, PropertyInfo, or PropertyDescriptor.

Note: Implementation of this post is obsolete. Please see new version here: http://blog.pmunin.com/2012/08/xaml-explicit-binding-to-interface.html


Everyone who works with MVVM pattern in WPF had met this problem with data binding to interface property. When you use bindin with explicit PropertyPath (like Path=(local:IMyInterface.MyProperty)) your designer is no longer working (neither Visual Studio XAML, nor Expression Blend).

The interesting/weird thing is that it screw up only if you use this binding in visual tree of your user control or windows, but not in DataTemplate/HierarchicalDataTemplate. Explicit binding inside DataTemplate works fine for me.


Finally I've found some workaround solution that allows you to use binding to interfaces' properties without breaking you Visual Studio and Expression Blend designers in main visual tree. But you will have to add something to your XAML. Basically instead of writing  Path=(local:IMyInterface.MyProperty), you will have to write  XamlExt.Path=(local:IMyInterface.MyProperty).

This is how you can achieve it:
1) Create Attached Property to Binding class.
2) Register Xml namespace mapping.
3) Enjoy full MVVM pattern in your Xaml applications with working designer

Here is code of XAML Markup Extension in separate class library:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Data;
using System.Diagnostics;
using System.ComponentModel;
 
namespace MyWPFApplication.Extensions
{
    /// 
    /// Binding extensions required to bind to interface property 
    /// without breaking designer
    /// 
    public static partial class XamlExt
    {
 
 
        public static object GetPath(System.Windows.Data.Binding obj)
        {
            return obj.Path;
        }
        public static void SetPath(System.Windows.Data.Binding obj, object value)
        {
 
            try
            {
                obj.Path = (PropertyPath)value;
            }
            catch (Exception e)
            {
                PresentationTraceSources
                   .DataBindingSource
                   .TraceData(TraceEventType.Error, 0, e);
                throw;
            }
        }
 
        public static readonly DependencyProperty PathProperty =
            DependencyProperty.RegisterAttached("Path"
                        typeof(PropertyPath)
typeof(XamlExt)
new PropertyMetadata());
 
    }
 
}


In order to use it conveniently in you XAML, you just need to add attribute in Assembly.cs:

[assemblyXmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"MyWPFApplication.Extensions")]



Now add modification to your old code.
Old code:

<Button
   x:Name="btnAdd"
   Content="Add"
   Command="{Binding Path=(local:IViewModel.AddCommand)}" />


New code:


<Button
   x:Name="btnAdd"
   Content="Add"
   Command="{Binding XamlExt.Path=(local:IViewModel.AddCommand)}" />




Note: Implementation of this post is obsolete. Please see new version here: http://blog.pmunin.com/2012/08/xaml-explicit-binding-to-interface.html