XAML:为什么我不能将T的子类添加到T的集合中?

时间:2016-02-01 16:50:25

标签: c# xaml winrt-xaml windows-10 uwp

编辑:如果您有兴趣,可以从GitHub克隆应用程序的源代码。

https://github.com/jamesqo/Sirloin

以下是重现的步骤:

  • git clone网址
  • 打开VS中的Sirloin.Example项目(在src / Sirloin.Example下)
  • 双击Package.appxmanifest,转到Packaging,然后生成.pfx文件(因为它被gitignored所需,更多信息here
  • 在x86下运行项目,你应该点击异常

原帖

我正在开发适用于Windows 10的应用。我遇到了一个问题,由于某些原因,我无法将Object的子类添加到收藏中对象。这是代码:

ObservableList.cs (基本上是实现IObservableVector的List的包装器)

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));
    }
}

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 (我在哪里使用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>

出于某种原因,当我运行该应用时,我收到此错误:

  

无法添加类型&#39; Sirloin.MenuItem&#39;到#Sir; Sirloin.ObservableList&#39;类型的集合。

看到ObservableList本质上是一个对象的集合,当然MenuItemObject的子类,我不明白为什么会这样。这些类型是否必须完全匹配?

不幸的是,我不能在这里使用泛型,因为我将前两个文件作为winmd组件的一部分公开,这(奇怪)意味着没有公共泛型类型。因此,我必须制作一些通用的对象集合。

2 个答案:

答案 0 :(得分:2)

您需要在构造函数中实例化LowerItems集合,才能接收任何子项。

public AppView()
{
    this.InitializeComponent();

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

当然,通过这样做,你还需要给LowerItems setter (@BerinLoritsch也在 Comment 部分中指出)。

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

为了进行测试,我将ItemsSource="{Binding LowerItems, ElementName=Self}"添加到 AppView.xaml 中的 lowerView ListView(不要忘记给UserControl 1}} x:Name="Self"使ElementName绑定起作用。)

完成这些更改后,您会看到图标显示在页面的左下角。

答案 1 :(得分:0)

Justin XL's answer不是解决方案,但它让我思考。 ItemControl的Items属性只有一个getter而不是setter,那么如何在不暴露setter的情况下初始化属性呢?

所以我看了Reference Source并导航到ItemControl.Items,结果发现it isn't even a dependency property!它只是一个普通的CLR属性,实际上是有意义的如果您考虑一下,因为如果它是只读的,您就不需要在XAML中绑定它。

基本上,我所做的就是删除依赖属性和ObservableList cruft并将其替换为:

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

之后一切都很完美。