Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinRT DependencyProperty with IEnumerable not firing at all

I've scoured and scoured since I know there has been a lot posted about Dependency Properties but I just haven't quite seen anything out there that has a solution that works. I'm trying to bind an ObservableCollection from my ViewModel to my AutoCompleteBox. My ViewModel is returning the data, the Getter is being hit. However, after that, the control's SetValue or OnItemsSourcePropertyChanged doesn't fire. Any thoughts as to what might be wrong?

I've got a control like so:

[ContentProperty(Name = "ItemsSource")]
public partial class AutoCompleteBox : Control
{
    //local stuff
    private ListBox lb;
    private List<Person> _items;
    private ObservableCollection<Person> _view;

    public AutoCompleteBox() : base()
    {
        DefaultStyleKey = typeof(AutoCompleteBox);
        Loaded += (sender, e) => ApplyTemplate();
    }
    protected override void OnApplyTemplate()
    {
        this.lb = this.GetTemplateChild("Selector") as ListBox;
        base.OnApplyTemplate();

    }
    #region ItemsSource

    public IEnumerable ItemsSource
    {
        get { return GetValue(ItemsSourceProperty) as ObservableCollection<Person>; }
        set { SetValue(ItemsSourceProperty, value); } //Never gets called
    }

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register(
            "ItemsSource",
            typeof(IEnumerable),
            typeof(AutoCompleteBox),
            new PropertyMetadata(null, OnItemsSourcePropertyChanged));

    private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
       //Never gets called :(
    }
    #endregion

    public String SearchText
    {
        get { return GetValue(SearchTextProperty) as String; }
        set
        {
            SetValue(SearchTextProperty, value);
        }
    }


    public static readonly DependencyProperty SearchTextProperty =
        DependencyProperty.Register(
            "SearchText",
            typeof(String),
            typeof(AutoCompleteBox),
            new PropertyMetadata(null, OnSearchTextPropertyChanged));

    private static void OnSearchTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
         //gets fired when loaded, with data being bound
    }

}

Below is the how it is being used the control:

<toolkit:AutoCompleteBox Grid.Row="5" Grid.Column="2" ItemsSource="{Binding Persons,Mode=TwoWay}" SearchText="WhatTheHell"/>

As a test and for simplicity sake, I created a String DependencyProperty for SearchText. It works fine if I bind the SearchText, the OnSearchTextPropertyChanged is called:

<toolkit:AutoCompleteBox Grid.Row="5" Grid.Column="2" ItemsSource="{Binding Persons,Mode=TwoWay}" SearchText="{Binding SearchText}"/>

Anybody else experience these issues with WinRT? Or see anything wrong with what I am doing?

like image 891
Mike Avatar asked May 11 '12 01:05

Mike


2 Answers

There are 2 quick solutions

(Here BaseClass is for example an abstract class or an interface that you derive from and instances of which you are trying to bind to a dependency property)

Solution 1:

You need to change typeof(BaseClass) to typeof(object) when registering a dependency property. Getter and setter may continue use your BaseClass, but be aware of InvalidCastException if you set a different type instance to this dependency property.

        public BaseClass Item
        {
            get { return (BaseClass)GetValue(ItemProperty); }
            set { SetValue(ItemProperty, value); }
        }
        public static readonly DependencyProperty ItemProperty =
            DependencyProperty.Register("Item", typeof(object), typeof(MyUserControl), new PropertyMetadata(null));

Solution 2:

Create a dummy dependency property somewhere in your code (for example right after the dependency property you are trying to create) that will have a type of all your derived types. Like this:

#region DummyProperty

public DerivedClass1 Dummy
{
    get { return (DerivedClass1)GetValue(DummyProperty); }
    set { SetValue(DummyProperty, value); }
}

public static readonly DependencyProperty DummyProperty =
    DependencyProperty.Register("Dummy", typeof(DerivedClass1), typeof(MyUserControl), new PropertyMetadata(default(DerivedClass1)));

#endregion

Here DerivedClass1 is derived from BaseClass.

Why?

I suppose the reason is that XAML doesn't know anything about your derived types and only knows about the BaseClass. So creating a dummy dependency property for each type should solve the problem.

like image 93
Artemious Avatar answered Jan 01 '23 11:01

Artemious


I would try checking if the binding ever fires with a DebugConverter - check the BindingDebugConverter in the WinRT XAML Toolkit - set it as a converter for your binding and see if and when it breaks (is it in Convert or ConvertBack, what value is being set etc.).

If that does not help - check if your DataContext is set correctly.

*EDIT 1

If what you are looking is changes in the items of the bound collection - you can check if ItemsSource is an INotifyCollectionChanged and if true - subscribe to the CollectionChanged event to see the changes. Otherwise - you could modify your control to inherit from ItemsControl and override methods like GetContainerForItemOverride and IsItemItsOwnContainerOverride.

*EDIT 2

It seems like there is a bug in the framework in Windows 8 Consumer Preview related to using IEnumerable as the type of a dependency property. Using object as the type solves the problem. If you enable native code debugging you will see this binding exception:

Error: Converter failed to convert value of type 'System.Collections.ObjectModel.ObservableCollection`1[[ACBDP.Person, ACBDP, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' to type 'IEnumerable'; BindingExpression: Path='Persons' DataItem='ACBDP.BlankPageViewModel, ACBDP, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'; target element is 'ACBDP.AutoCompleteBox' (Name='null'); target property is 'ItemsSource' (type 'IEnumerable').

like image 26
Filip Skakun Avatar answered Jan 01 '23 12:01

Filip Skakun