在我的WPF应用程序中,我有一个ItemsControl,其项目值依赖于前一个项目显示。
ViewModel是一个音频文件分割成可变长度的部分,我需要以这样的方式显示它,DateTime显示在右边,这就是我需要计算的(我只知道每个部分的长度,我需要计算它开始和结束的实际时间,以及ItemsControl上的位置)。
--
----
------------
--
--------------------
我的第一个方法是使用ObservableCollection<MyviewModel>
,但很快就发生了一些可怕的事情:
5路多绑定,其中IMultiValueConverter
,我将计算返回的值,并设置DataContext的属性为该值,因为我只知道前一个元素在运行时。
前一个元素是通过绑定Relativesource.PreviousData
发送的。
现在我的问题是,从转换器设置一个值(这显然是一件坏事),实际上让它工作后,一个常规的集合没有在其元素的顺序的概念,所以当我想在其余的中间添加一个音频部分时,显示是混乱的。
此外,当我要实现更多的业务逻辑时,我可能需要访问在这个转换器中计算的音频部分的开始和结束,如果它还没有显示呢?
所以这种方法在很多层面上都是错误的。
这就是我开始谷歌和发现关于LinkedList
。现在我想做一个类,基本上是一个可观察的LinkedList(我不需要它是通用的):
public class ObservableSegmentLinkedList : LinkedList<MyViewModel>, INotifyCollectionChanged
{
//Overrides ???
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
#endregion
}
和问题的核心是,我不能重写的方法修改集合(Addfirst, AddLast等),所以我不能调用OnNotifyCollectionChanged正确…
所以我想我可以为这些方法中的每一个重载,但这听起来很讨厌…
简而言之:我需要某种集合,其中每一项都知道前一项的详细信息,以便计算它自己的一个属性。
什么线索吗?这是一个好的解决方案吗?
谢谢!
附录,ViewModel看起来像:
public class MyViewModel : INotifyPropertyChanged
{
private DateTime m_SegmentLength;
public DateTime SegmentLength
{
get { return m_SegmentLength; }
set
{
m_SegmentLength = value;
NotifyPropertyChanged("SegmentLength");
}
}
private DateTime m_SegmentAdvert;
public DateTime SegmentAdvert
{
get { return m_SegmentAdvert; }
set
{
m_SegmentAdvert = value;
NotifyPropertyChanged("SegmentAdvert");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String prop)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
编辑:我想我会尝试结合托马斯和威尔的答案:我将使用组合(即我保持LinkedList的实例在我的自定义对象,而不是从它继承)和重新定义的方法是要使用(AddAfter, AddFirst等),我将调用OnNotifyPropertychanged后调用实际的LinkedList方法。这是一点工作,但我想不会有任何优雅的解决方案来解决我的问题…
现在,我制作了一个支持IEnumerable
的自定义泛型类,并将其用作LinkedList<T>
,唯一的区别是WPF会收到更改通知。
请注意,这个解决方案只适用于一个相当小的集合,我只需要管理大约30个元素的最大值,所以这对我来说很好,但每次你修改这个集合,它被认为是"重置"。
解决方案如下:
/// <summary>
/// This class is a LinkedList that can be used in a WPF MVVM scenario. Composition was used instead of inheritance,
/// because inheriting from LinkedList does not allow overriding its methods.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableLinkedList<T> : INotifyCollectionChanged, IEnumerable
{
private LinkedList<T> m_UnderLyingLinkedList;
#region Variables accessors
public int Count
{
get { return m_UnderLyingLinkedList.Count; }
}
public LinkedListNode<T> First
{
get { return m_UnderLyingLinkedList.First; }
}
public LinkedListNode<T> Last
{
get { return m_UnderLyingLinkedList.Last; }
}
#endregion
#region Constructors
public ObservableLinkedList()
{
m_UnderLyingLinkedList = new LinkedList<T>();
}
public ObservableLinkedList(IEnumerable<T> collection)
{
m_UnderLyingLinkedList = new LinkedList<T>(collection);
}
#endregion
#region LinkedList<T> Composition
public LinkedListNode<T> AddAfter(LinkedListNode<T> prevNode, T value)
{
LinkedListNode<T> ret = m_UnderLyingLinkedList.AddAfter(prevNode, value);
OnNotifyCollectionChanged();
return ret;
}
public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)
{
m_UnderLyingLinkedList.AddAfter(node, newNode);
OnNotifyCollectionChanged();
}
public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value)
{
LinkedListNode<T> ret = m_UnderLyingLinkedList.AddBefore(node, value);
OnNotifyCollectionChanged();
return ret;
}
public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
{
m_UnderLyingLinkedList.AddBefore(node, newNode);
OnNotifyCollectionChanged();
}
public LinkedListNode<T> AddFirst(T value)
{
LinkedListNode<T> ret = m_UnderLyingLinkedList.AddFirst(value);
OnNotifyCollectionChanged();
return ret;
}
public void AddFirst(LinkedListNode<T> node)
{
m_UnderLyingLinkedList.AddFirst(node);
OnNotifyCollectionChanged();
}
public LinkedListNode<T> AddLast(T value)
{
LinkedListNode<T> ret = m_UnderLyingLinkedList.AddLast(value);
OnNotifyCollectionChanged();
return ret;
}
public void AddLast(LinkedListNode<T> node)
{
m_UnderLyingLinkedList.AddLast(node);
OnNotifyCollectionChanged();
}
public void Clear()
{
m_UnderLyingLinkedList.Clear();
OnNotifyCollectionChanged();
}
public bool Contains(T value)
{
return m_UnderLyingLinkedList.Contains(value);
}
public void CopyTo(T[] array, int index)
{
m_UnderLyingLinkedList.CopyTo(array, index);
}
public bool LinkedListEquals(object obj)
{
return m_UnderLyingLinkedList.Equals(obj);
}
public LinkedListNode<T> Find(T value)
{
return m_UnderLyingLinkedList.Find(value);
}
public LinkedListNode<T> FindLast(T value)
{
return m_UnderLyingLinkedList.FindLast(value);
}
public Type GetLinkedListType()
{
return m_UnderLyingLinkedList.GetType();
}
public bool Remove(T value)
{
bool ret = m_UnderLyingLinkedList.Remove(value);
OnNotifyCollectionChanged();
return ret;
}
public void Remove(LinkedListNode<T> node)
{
m_UnderLyingLinkedList.Remove(node);
OnNotifyCollectionChanged();
}
public void RemoveFirst()
{
m_UnderLyingLinkedList.RemoveFirst();
OnNotifyCollectionChanged();
}
public void RemoveLast()
{
m_UnderLyingLinkedList.RemoveLast();
OnNotifyCollectionChanged();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged()
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return (m_UnderLyingLinkedList as IEnumerable).GetEnumerator();
}
#endregion
}
正如@AndrewS在评论中提到的,LinkedListNode应该被替换为一个自定义类,从它的List属性返回一个ObservableLinkedList。
LinkedList<T>
不是为继承而设计的:它的大多数方法不是虚拟的,所以没有什么可以重写的。如果您想重用它的实现并实现INotifyCollectionChanged
,请使用组合,而不是继承。
但无论如何,实现一个可观察的链表是没有意义的,因为链表不支持通过索引随机访问,CollectionChanged
通知只有在指定索引时才有用(除非你只提出NotifyCollectionChangedAction.Reset
通知,但这不是很有效)
这是一个很好的解决方案,你只需要创建你自己的LinkedList实现。
LinkedList<T>
没有实现任何链接列表接口,因此您可以自行选择方法/属性。我认为复制LinkedList<T>
的公共方法和属性是一个很好的指南。这将允许您使用集合的实际实例作为后备存储。
我认为最终更简单的解决方案是提前计算开始和结束时间,并将它们作为属性添加到ViewModel上。特别是因为您说您可能在业务逻辑中需要此值。
听起来你有两个不同的问题。一个是管理要显示的项目列表,另一个是允许一个项目访问它的前一个和后一个项目。
这就是我如何处理它:将Previous
和Next
属性添加到项目类,在最初填充集合时设置它们,然后在我从列表中插入和删除项目时更新它们。
如果你真的想发疯,做一个通用的解决方案,你可以实现一个ILinkedListNode
接口,然后子类ObservableCollection<T> where T : ILinkedListNode
,覆盖各种插入和删除方法来更新项目的Previous
和Next
属性。如果我需要可重用的解决方案,我就会这样做。
但如果没有,我只是做一个视图模型类包装集合,将其暴露为Items
属性,然后实现插入和删除命令,UI可以绑定到。
如果您创建了这样的类:
public class ObservableSegmentLinkedList<T> : LinkedList<T>, INotifyCollectionChanged
{
...
public new void AddFirst(T value)
{
.. do something to make it your own - you can still call the base method so in effect you override it with the new keyword.
}
}
用new关键字重写方法应该没有任何问题。
注意,类本身有一个与链表类型匹配的TYPE说明符。
在这里是通用的但是你可以在