XAML: 为什么我不能将 T 的子类添加到 T 的集合中?
XAML: Why can't I add a subclass of T to a collection of T?
编辑: 如果您有兴趣,可以从 GitHub.
克隆应用程序的源代码
https://github.com/jamesqo/Sirloin
重现步骤如下:
git clone
URL
- 在 VS
中打开 Sirloin.Example 项目(在 src/Sirloin.Example 下)
- 双击 Package.appxmanifest,转到打包,并生成 .pfx 文件(必需,因为它会被 gitignored,更多信息 here)
- 运行 x86下的项目,你应该会遇到异常
原版Post
我正在为 Windows 10 开发一个应用程序。我 运行 遇到了一个问题,由于某种原因,我无法添加 [=15= 的子类] 对象的集合。这是代码:
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>
出于某种原因,当我 运行 应用程序时,出现此错误:
Cannot add instance of type 'Sirloin.MenuItem' to a collection of type 'Sirloin.ObservableList'.
一看就知道ObservableList
本质上是对象的集合,当然MenuItem
是Object
的子类,不明白为什么会这样。类型是否必须完全匹配?
不幸的是,我不能在这里使用泛型,因为我将前两个文件公开为 winmd
组件的一部分,这(奇怪地)意味着没有 public 泛型类型。所以我必须把所有通用的东西都变成一个对象集合。
您需要在 构造函数 中实例化 LowerItems
集合,以便它接收任何子项。
public AppView()
{
this.InitializeComponent();
this.Loaded += (o, e) => Current = this;
LowerItems = new ObservableVector();
}
当然这样做你还需要给LowerItems
一个setter(@BerinLoritsch在中也指出评论节)。
public ObservableVector LowerItems
{
get { return this.GetValue<ObservableVector>(LowerItemsProperty); }
set { this.SetValue(LowerItemsProperty, value); }
}
为了测试,我在 AppView.xaml 中添加了 ItemsSource="{Binding LowerItems, ElementName=Self}"
到 lowerView ListView
(不要忘记给 UserControl
一个 x:Name="Self"
以使 ElementName 绑定起作用。
进行这些更改后,您会看到图标出现在页面的左下方。
不是解决方案,但它让我开始思考。 ItemControl 的 Items
属性 只有 getter 而没有 setter,那么它是如何在不暴露 setter 的情况下初始化 属性 的呢?
所以我看了一下 Reference Source and navigated to ItemControl.Items
, and it turns out that it isn't even a dependency property! 它只是一个普通的 CLR 属性,如果你仔细想想这实际上是有道理的,因为你不需要绑定到它XAML 如果它是只读的。
所以基本上,我所做的就是删除依赖项 属性 和 ObservableList
cruft 并将其替换为:
public ItemCollection LowerItems => lowerView.Items;
public ItemCollection UpperItems => upperView.Items;
之后一切正常。
编辑: 如果您有兴趣,可以从 GitHub.
克隆应用程序的源代码https://github.com/jamesqo/Sirloin
重现步骤如下:
git clone
URL- 在 VS 中打开 Sirloin.Example 项目(在 src/Sirloin.Example 下)
- 双击 Package.appxmanifest,转到打包,并生成 .pfx 文件(必需,因为它会被 gitignored,更多信息 here)
- 运行 x86下的项目,你应该会遇到异常
原版Post
我正在为 Windows 10 开发一个应用程序。我 运行 遇到了一个问题,由于某种原因,我无法添加 [=15= 的子类] 对象的集合。这是代码:
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>
出于某种原因,当我 运行 应用程序时,出现此错误:
Cannot add instance of type 'Sirloin.MenuItem' to a collection of type 'Sirloin.ObservableList'.
一看就知道ObservableList
本质上是对象的集合,当然MenuItem
是Object
的子类,不明白为什么会这样。类型是否必须完全匹配?
不幸的是,我不能在这里使用泛型,因为我将前两个文件公开为 winmd
组件的一部分,这(奇怪地)意味着没有 public 泛型类型。所以我必须把所有通用的东西都变成一个对象集合。
您需要在 构造函数 中实例化 LowerItems
集合,以便它接收任何子项。
public AppView()
{
this.InitializeComponent();
this.Loaded += (o, e) => Current = this;
LowerItems = new ObservableVector();
}
当然这样做你还需要给LowerItems
一个setter(@BerinLoritsch在中也指出评论节)。
public ObservableVector LowerItems
{
get { return this.GetValue<ObservableVector>(LowerItemsProperty); }
set { this.SetValue(LowerItemsProperty, value); }
}
为了测试,我在 AppView.xaml 中添加了 ItemsSource="{Binding LowerItems, ElementName=Self}"
到 lowerView ListView
(不要忘记给 UserControl
一个 x:Name="Self"
以使 ElementName 绑定起作用。
进行这些更改后,您会看到图标出现在页面的左下方。
Items
属性 只有 getter 而没有 setter,那么它是如何在不暴露 setter 的情况下初始化 属性 的呢?
所以我看了一下 Reference Source and navigated to ItemControl.Items
, and it turns out that it isn't even a dependency property! 它只是一个普通的 CLR 属性,如果你仔细想想这实际上是有道理的,因为你不需要绑定到它XAML 如果它是只读的。
所以基本上,我所做的就是删除依赖项 属性 和 ObservableList
cruft 并将其替换为:
public ItemCollection LowerItems => lowerView.Items;
public ItemCollection UpperItems => upperView.Items;
之后一切正常。