3

EDIT: If you're interested, you can clone the source code of the application from GitHub.

https://github.com/jamesqo/Sirloin

Here are the steps to repro:

  • git clone the URL
  • open up the Sirloin.Example project (under src/Sirloin.Example) in VS
  • double-click the Package.appxmanifest, go to Packaging, and generate the .pfx file (necessary since it gets gitignored, more info here)
  • run the project under x86 and you should hit the exception

Original Post

I'm developing an app for Windows 10. I'm running into a problem where, for some reason, I'm unable to add a subclass of Object to a collections of objects. Here is the code:

ObservableList.cs (basically a wrapper for List which implements IObservableVector)

public sealed class ObservableList : IObservableVector<object>, IReadOnlyList<object>, INotifyPropertyChanged
{
    private const string IndexerName = "Item[]";

    // This is non-generic so we can expose it thru the .winmd component
    private readonly List<object> list;

    public event PropertyChangedEventHandler PropertyChanged;
    public event VectorChangedEventHandler<object> VectorChanged;

    public ObservableList()
    {
        this.list = new List<object>();
    }

    public ObservableList(IEnumerable<object> items)
    {
        this.list = new List<object>(items);
    }

    public int Count => list.Count;

    int IReadOnlyCollection<object>.Count => Count;

    public bool IsReadOnly => false;

    public object this[int index]
    {
        get { return list[index]; }
        set { list[index] = value; }
    }

    object IReadOnlyList<object>.this[int index] => this[index];

    public int IndexOf(object item) => list.IndexOf(item);

    public void Insert(int index, object item)
    {
        list.Insert(index, item);
        OnPropertyChanged(nameof(Count));
        OnPropertyChanged(IndexerName);
        OnVectorChanged(CollectionChange.ItemInserted, (uint)index);
    }

    public void RemoveAt(int index)
    {
        list.RemoveAt(index);
        OnPropertyChanged(nameof(Count));
        OnPropertyChanged(IndexerName);
        OnVectorChanged(CollectionChange.ItemRemoved, (uint)index);
    }

    public void Add(object item) =>
        Insert(this.Count, item);

    public void Clear()
    {
        list.Clear();
        OnPropertyChanged(nameof(Count));
        OnVectorChanged(CollectionChange.Reset, 0);
    }

    public bool Contains(object item) => list.Contains(item);

    public void CopyTo(object[] array, int arrayIndex) => 
        list.CopyTo(array, arrayIndex);

    public bool Remove(object item)
    {
        int index = this.IndexOf(item);
        if (index == -1)
            return false;

        this.RemoveAt(index);
        OnPropertyChanged(nameof(Count));
        OnVectorChanged(CollectionChange.ItemRemoved, (uint)index);
        return true;
    }

    public IEnumerator<object> GetEnumerator() => list.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    private void OnVectorChanged(CollectionChange change, uint index)
    {
        VectorChanged?.Invoke(this, new VectorChangedArgs(change, index));
    }

    private void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

Relevant portion of AppView.xaml.cs:

public sealed partial class AppView : UserControl
{
    // Some generic extensions I wrote to minimize boilerplate
    public ObservableList LowerItems =>
        this.GetValue<ObservableList>(LowerItemsProperty);

    public static DependencyProperty LowerItemsProperty { get; } =
        Dependency.Register<ObservableList, AppView>(nameof(LowerItems), LowerItemsPropertyChanged);

    private static void LowerItemsPropertyChanged(AppView o, IPropertyChangedArgs<ObservableList> args)
    {
        var src = args.NewValue;
        var dest = o.lowerView.Items;

        dest.Clear();

        foreach (var item in src) dest.Add(item);
    }
}

MainPage.xaml (where I use the AppView)

<Page
    x:Class="Sirloin.Example.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="using:Sirloin">

    <s:AppView>
        <!--This is the part that's failing-->
        <s:AppView.LowerItems>
            <s:MenuItem Symbol="Contact"/>
            <s:MenuItem Symbol="Contact"/>
        </s:AppView.LowerItems>
    </s:AppView>
</Page>

For some reason, when I run the app, I'm getting this error:

Cannot add instance of type 'Sirloin.MenuItem' to a collection of type 'Sirloin.ObservableList'.

Seeing is how ObservableList is essentially a collection of objects, and of course MenuItem is a subclass of Object, I don't see why this is the case. Do the types have to exactly match or something?

Unfortunately, I can't use generics here since I'm exposing the first two files as part of a winmd component, which (bizarrely) means no public generic types. So I have to make everything that would be generic a collection of objects.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • Just a question, but wouldn't ObservableCollection work just fine? https://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx If you want to add sorting and filtering, it works much better to use a CollectionViewSource http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.aspx – Berin Loritsch Feb 01 '16 at 17:28
  • @BerinLoritsch Unfortunately, exposing an `ObservableCollection` (even of objects) results in [this error.](http://i.imgur.com/Sg6TvtI.png) – James Ko Feb 01 '16 at 17:37
  • 1
    I don't have a platform I can test UWP right now (I do at home). In standard WPF it works just fine. The only lead I can give right now is that the generated code-behind in your `s:AppView.LowerItems` will attempt to replace your default instance of ObservableList with a new one. Your property change callback is called after the fact. BTW, your generic extensions are really nice. – Berin Loritsch Feb 01 '16 at 18:04
  • It appears your `LowerItems => GetValue()` call doesn't have a corresponding setter, so it could be erroring out on that. – Berin Loritsch Feb 01 '16 at 18:05
  • I can't see what else could be wrong except your generic extension method for defining dependency properties. Try replacing it with the normal form and see if it works. – Justin XL Feb 01 '16 at 23:38

2 Answers2

2

You need to instantiate the LowerItems collection in the constructor for it to take in any children.

public AppView()
{
    this.InitializeComponent();

    this.Loaded += (o, e) => Current = this;
    LowerItems = new ObservableVector();
}

Of course by doing this you also need to give LowerItems a setter (also pointed out by @BerinLoritsch in the Comment section).

public ObservableVector LowerItems
{
    get { return this.GetValue<ObservableVector>(LowerItemsProperty); }
    set { this.SetValue(LowerItemsProperty, value); }
}

For testing, I added ItemsSource="{Binding LowerItems, ElementName=Self}" to your lowerView ListView inside your AppView.xaml (don't forget to give the UserControl an x:Name="Self" for the ElementName binding to work).

After these changes you will see the icons appear on the bottom left of the page.

Justin XL
  • 38,763
  • 7
  • 88
  • 133
  • Thank you for answering! I didn't mark this as the answer because it wasn't the final solution for me. However, it did help me solve the problem, so have an upvote. – James Ko Feb 02 '16 at 22:50
  • Nws, probably you should consider posting your solution here. :) – Justin XL Feb 02 '16 at 22:51
0

Justin XL's answer wasn't the solution, but it got me thinking. ItemControl's Items property only has a getter and not a setter, so how was it able to initialize the property without exposing the setter?

So I took at the look at the Reference Source and navigated to ItemControl.Items, and it turns out that it isn't even a dependency property! It's just a regular CLR property, which actually makes sense if you think about it, since you wouldn't need to bind to it in XAML if it's readonly.

So basically, all I did was remove the dependency property and ObservableList cruft and replace it with this:

public ItemCollection LowerItems => lowerView.Items;
public ItemCollection UpperItems => upperView.Items;

After that everything worked perfectly.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    There's a good reason that why the Items is a normal CLR readonly property and we just don't use it directly in xaml - It's meant to store *logical* data and you won't be able to define or update the collection via data-binding from your viewmodel. Having said it, if you know you never ever need to change the items defined in xaml, this is fine (unless you go code-behind and do manual update... which is just not mvvm friendly). – Justin XL Feb 03 '16 at 00:09
  • 2
    Your solution might be "working", but it is wrong. @JustinXL is correct. – Jerry Nixon Feb 03 '16 at 05:02
  • @JerryNixon-MSFT Fair enough. I've accepted his answer. – James Ko Feb 04 '16 at 01:05