Tuesday, August 28, 2012

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

This is evolution of my previous post: http://blog.pmunin.com/2012/02/xaml-explicit-binding-to-interface.html
I've decided not to overwrite the old one, but create new post instead.

So eventually I decided to resolve issue using separate binding class, which is inherited from Binding, but contains all necessary improvements.

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;
[assemblyXmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation""PMunin.com")]
namespace PMunin.com
{
    /// 
    /// Inherited from Binding, have some improvements:    ///  - does not crash VS2010 WPF designer if Path contains Interface.    ///  - ValidateOnDataErrors and ValidateOnExceptions are true by default.    /// 

    public class XamlExtBinding : Binding
    {
        public XamlExtBinding():base()
        {
            NotifyOnValidationError = true;
            ValidatesOnDataErrors = true;
            ValidatesOnExceptions = true;
        }
        public XamlExtBinding(string path):base(path)
        {
            NotifyOnValidationError = true;
            ValidatesOnDataErrors = true;
            ValidatesOnExceptions = true;
        }
        [TypeConverter(typeof(ExtPropertyPathConverter))]
        public new object Path
        {
            get
            {
                return base.Path;
            }
            set
            {
                base.Path = value as PropertyPath;
            }
        }
        /// 
        /// Required to fix bug "Prefix 'local' does not map to a namespace"        /// when Path with namespace used in DataTemplate like here (http://devexpress.com/Support/Center/p/Q352753.aspx)        /// 
        public class ExtPropertyPathConverter : TypeConverter        {
            static PropertyPathConverter pathConverter = new PropertyPathConverter();
            static bool IsInDesignMode(ITypeDescriptorContext context)
            {
                bool isInDesignMode = DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow);
                return isInDesignMode;
            }
            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
                bool res = true;
                try                {
                    res = pathConverter.CanConvertFrom(context, sourceType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            {
                var res = true;
                try                {
                    res = pathConverter.CanConvertTo(context, destinationType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
            {
                object res = null;
                try                {
                    res = pathConverter.ConvertFrom(context, culture, value);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                object res = null;
                try                {
                    res = pathConverter.ConvertTo(context, culture, value, destinationType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
        }
    }
}


You should put this class in separate shared class library, that is referenced by you project with XAML files you need to edit. This will allow you to use in very simple way you get used to - anywhere you have {Binding ....}, or just replace it with {XamlExtBinding ...} or . No namespace declaration required.
This code was written long time ago, so I'm posting it now, but I'm not sure that included everything. Please feel free to comment - if you face any issues.

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


Friday, January 27, 2012

XAML DataTemplates binding to interface

Some times it is much convenient to use interface as a View Model for a View. In WinForms there was no problem with it (using BindingSource for instance), but in WPF DataBinding engine is limited.
Let's say you have any TreeView (the same works for ListBox, any ItemsControl or ContentControl, ContentPresenter - whatever has *TemplateSelector property). And you want nodes of this TreeView to display different content using different DataTemplates. And you want these DataTemplates to by mapped be bounded to Interface, instead of class. Another words View Model of each node datatemplate is defined by interface.

So here is the solution to achieve it.

I've created an MarkupExtension, that allows to assign very easily TemplateSelector, which will select appropriate datatemplate based on compatibility of interface.
The XAML will look like this:

        <ListBox
            ItemsSource="{Binding Source={StaticResource nodes}}"
            ItemTemplateSelector="{local:InterfaceTemplateSelector nodeDataTemplate}"
            >
            <ListBox.Resources>

                <HierarchicalDataTemplate
                    x:Key="nodeDataTemplate"
                    DataType="{x:Type local:INode}"
                    ItemsSource="{Binding Path=(local:INode.Children)}"
                    >...
                HierarchicalDataTemplate>
            ListBox.Resources>
        ListBox>

If you want to have several templates for different nodes than you will need the following XAML modifications:
For ListBox you will have to change property ItemTemplateSelector="{local:InterfaceTemplateSelector 'rootNodeDataTemplate,nodeDataTemplate'}"
and create second data template:
                <HierarchicalDataTemplate
                   x:Key="rootNodeDataTemplate"
                   DataType="{x:Type local:IRootNode}"
                   ItemsSource="{Binding Path=(local:IRootNode.Children)}">....
InterfaceTemplateSelector has two properties:
1) ResourceKeys (also first constructor parameter)
2) ResourceKeysSeparator (also second optional constructor parameter) - default value ","
InterfaceTemplateSelector initialization parameter is CSV-string representing keys of resources which are data templates targeted to Interface.

This selector will take all available resource DataTemplates and check DataType each of it (which should have interface assigned) against type of content Item that is about to render.
Keep in mind that sequence of keys in InterfaceTemplateSelector defines priority of associating DataTemplate for content Item. Let's say you have Item that implement both interfaces (INode and IRootNode). But in our case it will get rootNodeDataTemplate assigned, because its key mentioned first.

Here is the implementation the markup extension, to make it work accordingly:

    public class InterfaceTemplateSelectorExtension : System.Windows.Markup.MarkupExtension
    {
        public InterfaceTemplateSelectorExtension()
        {
            ResourceKeysSeparator = ",";
        }
 
        public string ResourceKeysSeparator { getset; }
 
        public InterfaceTemplateSelectorExtension(string resourceKeysCSV)
        {
            ResourceKeys = resourceKeysCSV;
        }
 
        public InterfaceTemplateSelectorExtension(string resourceKeys, string separator)
        {
            ResourceKeys = resourceKeys;
            ResourceKeysSeparator = separator;
        }
 
 
        /// 
        /// Comma separated resource keys specifying keys of DataTemplates that binds to interface
        /// 
        public string ResourceKeys { getset; }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return new InterfaceTemplateSelector(ResourceKeys.Split(new string[]{ResourceKeysSeparator}, StringSplitOptions.RemoveEmptyEntries));
        }
 
        public class InterfaceTemplateSelector:DataTemplateSelector
        {
            string[] resourceKeys;
            public InterfaceTemplateSelector(string[] resourceKeys)
            {
                this.resourceKeys = resourceKeys;
            }
 
            public override DataTemplate SelectTemplate(object item, DependencyObject container)
            {
                var c = (FrameworkElement)container;
                var dataTemplates = (from rk in resourceKeys
                                let resource = c.TryFindResource(rk)
                                where resource is DataTemplate
                                where (resource as DataTemplate).DataType is Type
                                select resource).Cast<DataTemplate>()
                                ;
 
                var itemType = item.GetType();
 
                var result = dataTemplates.FirstOrDefault(dt => 
                    (dt.DataType as Type).IsInstanceOfType(item)
                    );
 
                
                return result??base.SelectTemplate(item, container);
                
            }
        }
        
    }