Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF
Article

WPF: How To Bind to Generic Methods

Rate me:
Please Sign up or sign in to vote.
4.93/5 (23 votes)
6 Dec 2008CPOL6 min read 116K   596   50   32
An article which shows how to bind to generic methods

Contents

Introduction

I have done quite a lot of work with WPF and C#, and I am a big fan of Generics within .NET though there have been times when I wish there was better support for generics within WPF/XAML. It is on its way, I know I have watched the XAML 2009 videos and seen some of the cool stuff that is coming our way. But we must live in the present which is where there isn't really any support for generics within XAML.

This article will outline a problem domain, and a solution to this problem. The problem will be that of binding to a generic method using WPF to obtain a collection of items, where the items will be bound to some control. I have chosen the ComboBox control to demonstrate this, as it is an easy to understand control that has an ItemsSource property which can be set to any IEnumerable source.

The Problem

This small article stems from a common anger I seem to have not just with WPF, and it's a lack of support for generics, but also strangely with generics themselves. I love both, but on occasions I do find myself really fed up that I have a Type of an object, but yet I am unable to call a generic method, even if I know the Type. Let's say I have a method something like:

C#
public ObservableCollection<T> GetForType<T>()

And I may have another method which accepts a Type of an object, and is intended to call the previous generic method somehow, something like:

C#
public ObservableCollection<T> GetObjects(Type t)
{
	.....
}

Now if I know the Type of the object I want, wouldn't it be nice if I could just go GetForType<typeof(t)>(), but oh no, I have to use GetForType<SomeClass>(), I think this is a shame. Perhaps I am missing something but that would have been a nice feature I feel, and would have made it all even more flexible.

Anyway apart from that small rant, I am now going to outline a problem, and below we will try and look at a solution. There will be other solutions, but this is one possible solution.

The Problem

I have various bits of static data that are used throughout the system that are held as a collection(s) (I am using ObservableCollection<T>) of certain data objects. The WPF UI should be able to bind to these collections, without the need for loads of get {...} properties. Ideally there would be a single method that returned the correct type of static data, as requested by the method. I mean there may be a lot of static data, and sure we could do this by exposing lots of properties over the static data collections, but that somehow seems old fashioned to me. We could of course also just take an Object and return a ObservableCollection<Object>, but that too seemed wrong, not enough typing more my liking there.

The problem seems to warrant a more generic solution. Wait, doesn't .NET support generics. Hell yeah, ok cool. So possibly we should be trying for something like the following after all:

C#
public ObservableCollection<T> GetObjects(Type t)
{
	.....
}

But unfortunately, I do not know a way of dealing with generic method calls in XAML. I know there is an ObjectDataProvider object which has a Method property and allows parameters to be built up in XAML. But this would mean we would need one new ObjectDataProvider with parameters for each type we intended to use the above method for. That's pretty poor. There must be a better way, surely.

Shown below is one possible solution to calling generic methods like that shown above from within XAML.

A Solution

Now before I start with the solution, let me outline in a bit more detail what the attached solution is actually doing. I stated that there was a bunch of static data within the system, that could potentially be used all over the place. To this end, there is a singleton class called StaticData, which holds the static data. This class looks like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace GenericBinding
{
    /// <summary>
    /// A small singleton class to demonstrate one possible way that
    /// WPF can support binding to generic methods within XAML
    /// </summary>
    public sealed class StaticData
    {
        static readonly StaticData instance = new StaticData();
        private ObservableCollection<Person> people = null;
        private ObservableCollection<Animal> animals = null;
        private Dictionary<Type, Object> typeToCollectionLookup = 
            new Dictionary<Type, Object>();

        static StaticData()
        {
        }

        private StaticData()
        {
            //create some dummy data
            people = new ObservableCollection<Person>
            {
                new Person { Age=10, FirstName="Sam", LastName="Davis" },
                new Person { Age=34, FirstName="Dan", LastName="Smith" },
                new Person { Age=17, FirstName="Joe", LastName="Boynes" },
                new Person { Age=12, FirstName="Max", LastName="Jones" }
            };

            animals = new ObservableCollection<Animal>
            {
                new Animal { Breed="German Shephard" },
                new Animal { Breed="Pitbull" },
                new Animal { Breed="Great Dane" },
                new Animal { Breed="Rottweiler" },
                new Animal { Breed="Bull Dog" }
            };

            typeToCollectionLookup.Add(typeof(Person), people);
            typeToCollectionLookup.Add(typeof(Animal), animals);
        }

        public static StaticData Instance
        {
            get
            {
                return instance;
            }
        }

        [ItemsSourceLookUpMethodAttribute()]
        public ObservableCollection<T> GetForType<T>()
        {
            return (ObservableCollection<T>)
				typeToCollectionLookup[typeof(T)];
        }
    }
}

Basically the StaticData class is a singleton, that holds a Dictionary<Type, Object> of all the system's static data. And there is a single generic method, which you have already seen. (Don't worry about the ItemsSourceLookUpMethodAttribute just yet, I'll get to that later on.)

C#
public ObservableCollection<T> GetObjects(Type t)
{
	.....
}

This method is used to return the correct static data, so ideally the XAML would hook into this generic method to bind a ComboBox.ItemSource (the control I picked for demo purposes) to, but as we know XAML just will not let us do this (yet, as we don't have XAML 2009).

Fortunately what WPF does let us do though is use Dependency Properties, which are great. What we can do is actually use an attached DP, to add an extra bit of functionality to our existing ComboBox controls, so we end up with something like this in the XAML:

XML
<Window x:Class="GenericBinding.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:GenericBinding;assembly="          
    Title="Window1" Height="300" Width="300">
    <Grid>
	          ......
            <ComboBox local:ComboBoxProps.BoundCollectionType="local:Person"/>
        </DockPanel>

    </Grid>
</Window>

Ok, so we can use an attached property, so how does that help any?

Well, if we take a look at the ComboBoxProps class that holds the BoundCollectionTypeProperty attached property, this may shed some light on this for us.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Reflection;
using System.Collections;

namespace GenericBinding
{
    /// <summary>
    /// Provides a mechanism for binding a ComboBox.ItemSource against
    /// a generic method within the StaticData singleton. Where the generic
    /// method has a signature as follows:
    /// 
    /// public ObservableCollection<T> GetForType<T>()
    /// </summary>
    public class ComboBoxProps
    {

        /// <summary>
        /// BoundCollectionType Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty BoundCollectionTypeProperty =
            DependencyProperty.RegisterAttached("BoundCollectionType", 
            typeof(Type), typeof(ComboBoxProps),
                new FrameworkPropertyMetadata(null,
                    new PropertyChangedCallback(OnBoundCollectionTypeChanged)));

        /// <summary>
        /// Gets the BoundCollectionType property.  
        /// </summary>
        public static Type GetBoundCollectionType(DependencyObject d)
        {
            return (Type)d.GetValue(BoundCollectionTypeProperty);
        }

        /// <summary>
        /// Sets the BoundCollectionType property.  
        /// </summary>
        public static void SetBoundCollectionType(DependencyObject d, Type value)
        {
            d.SetValue(BoundCollectionTypeProperty, value);
        }

        /// <summary>
        /// Handles changes to the BoundCollectionType property.
        /// Uses Reflection to obtain the method within the StaticData singleton class
        /// that has the generic method that we need to use to get the values from.
        /// The method will be marked with a custom ItemsSourceLookUpMethodAttribute
        /// to indicate which method is to be used, to create a Dynamic call to
        /// using the correct generic parameter.
        /// </summary>
        private static void OnBoundCollectionTypeChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            ComboBox cbSource = d as ComboBox;
            Type t = (Type)e.NewValue;
            Type[] types = new Type[] { t };

            MethodInfo[] methods = 
                typeof(StaticData).GetMethods(
			BindingFlags.Public | BindingFlags.Instance);

            foreach (MethodInfo method in methods)
            {
                //Didn't like looking up MethodInfo.Name based on a string as it could
                //change, so use a custom attribute to look for on the method instead
                ItemsSourceLookUpMethodAttribute[] attribs =
                    (ItemsSourceLookUpMethodAttribute[])
                        method.GetCustomAttributes(
                            typeof(ItemsSourceLookUpMethodAttribute), true);
                
                //is this the correct MethodInfo to invoke
                if (attribs.Length > 0)
                {
                    // create the generic method
                    MethodInfo genericMethod = method.MakeGenericMethod(types);
                    cbSource.ItemsSource = 
                        (IEnumerable)genericMethod.Invoke(StaticData.Instance, 
                        BindingFlags.Instance, null, null, null);
                }
            }
        }
    }
}

Mainly this is just bulk standard BoundCollectionTypeProperty attached property stuff, with a change event. The interesting thing is what happens in the changed event. What actually happens is that we obtain the Type from the actual BoundCollectionTypeProperty attached DP property, and then we get a reference to all the MethodInfos within the StaticData class (via the System.Reflection APIs). Next we loop through all these MethodInfos, and look for the one that has an ItemsSourceLookUpMethodAttribute applied. Here, the ItemsSourceLookUpMethodAttribute is nothing more than a way of adorning the correct method, such that we don't need to look up methods based on a hard coded string. I don't like that. Someone may change the name of the method, so a custom attribute seemed better to me.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenericBinding
{
    /// <summary>
    /// Dummy attribute which is simply used to mark a method within the
    /// StaticData singleton. The reason this attribute is used is to allow
    /// the correct MethodInfo to be picked up using Reflection rather
    /// than checking the method name against a string. That's just nasty.
    /// Much better to check for a known attribute.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class ItemsSourceLookUpMethodAttribute : Attribute
    {
        #region Ctor
        public ItemsSourceLookUpMethodAttribute()
        {

        }
        #endregion
    }
}

This allows us to know that we have found the correct MethodInfo. So at that point we have a pointer to a MethodInfo, and we have the correct type to call this generic method with. But the MethodInfo we have is not a generic one, we need to first make a generic method based on the current MethodInfo. This is simply a case of making a generic MethodInfo from the current MethodInfo, and passing in the correct type to satisfy the generic method, which when done will end up with the original generic method:

C#
public ObservableCollection<T> GetObjects(Type t)
{
	.....
}

So at this point, it's just a case of doing a dynamic invoke on this new generic MethodInfo object, and returning the results, which are then used to set the ComboBox.ItemsSource property.

So despite the fact that WPF/XAML doesn't currently play well with generics, I say sod it, where there is a will there is a way. Take that WPF.

Other Small Things

The rest of the code, is made up as follows:

  • Person.cs: A small data object
  • Animal.cs: A small data object
  • DummyViewModel.cs: A tiny ViewModel to be used as the DataContext property for the View, which contains things like CurrentPerson and CurrentAnimal binding properties.

That's It

Well it's a small article, but it helped me, and I hope it helps you. Enjoy. If you like it, please leave a vote for it. Thanks.

History

  • 6th December, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
AnswerRe: How to Assign MultiBinding using C# Pin
Sacha Barber16-Apr-09 21:48
Sacha Barber16-Apr-09 21:48 
GeneralRe: How to Assign MultiBinding using C# [modified] Pin
tinatran3017-Apr-09 16:02
tinatran3017-Apr-09 16:02 
GeneralRe: How to Assign MultiBinding using C# Pin
Sacha Barber17-Apr-09 22:09
Sacha Barber17-Apr-09 22:09 
GeneralRe: How to Assign MultiBinding using C# Pin
Sacha Barber17-Apr-09 22:15
Sacha Barber17-Apr-09 22:15 
QuestionHow to assign MultiBinding object to a property of the control? Pin
tinatran3016-Apr-09 17:13
tinatran3016-Apr-09 17:13 
AnswerRe: How to assign MultiBinding object to a property of the control? Pin
Sacha Barber16-Apr-09 21:39
Sacha Barber16-Apr-09 21:39 
GeneralExcellent work Pin
Dr.Luiji12-Dec-08 11:50
professionalDr.Luiji12-Dec-08 11:50 
GeneralRe: Excellent work Pin
Sacha Barber12-Dec-08 23:06
Sacha Barber12-Dec-08 23:06 
GeneralAnother Winner Pin
jackmos9-Dec-08 10:47
professionaljackmos9-Dec-08 10:47 
GeneralRe: Another Winner Pin
Sacha Barber9-Dec-08 21:53
Sacha Barber9-Dec-08 21:53 
GeneralDude! You need to write a book. Pin
azamsharp9-Dec-08 3:31
azamsharp9-Dec-08 3:31 
GeneralRe: Dude! You need to write a book. Pin
Sacha Barber9-Dec-08 4:59
Sacha Barber9-Dec-08 4:59 
GeneralRe: Dude! You need to write a book. Pin
azamsharp9-Dec-08 5:03
azamsharp9-Dec-08 5:03 
GeneralRe: Dude! You need to write a book. Pin
Sacha Barber9-Dec-08 5:21
Sacha Barber9-Dec-08 5:21 
GeneralRe: Dude! You need to write a book. Pin
Dr.Luiji12-Dec-08 11:58
professionalDr.Luiji12-Dec-08 11:58 
GeneralRe: Dude! You need to write a book. Pin
Sacha Barber12-Dec-08 23:07
Sacha Barber12-Dec-08 23:07 
GeneralTo my Guru Pin
Abhijit Jana6-Dec-08 21:59
professionalAbhijit Jana6-Dec-08 21:59 
GeneralRe: To my Guru Pin
Sacha Barber6-Dec-08 22:20
Sacha Barber6-Dec-08 22:20 
GeneralYou never fail to surprise... Pin
Yogesh Jagota6-Dec-08 10:59
Yogesh Jagota6-Dec-08 10:59 
GeneralRe: You never fail to surprise... Pin
Sacha Barber6-Dec-08 21:19
Sacha Barber6-Dec-08 21:19 
GeneralSon of a beech Pin
Pete O'Hanlon6-Dec-08 8:50
subeditorPete O'Hanlon6-Dec-08 8:50 
GeneralRe: Son of a beech Pin
Sacha Barber6-Dec-08 21:18
Sacha Barber6-Dec-08 21:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.