【问题标题】:Reverse Sort ObservableCollection?反向排序 ObservableCollection?
【发布时间】:2014-03-23 19:16:11
【问题描述】:

专家,

短版:

ObservableCollection 的排序方式最后是最新的,我需要它完全相反,以便在 WPF DataGrid 中显示。现在,一切都很好,但是新条目添加到末尾,因此用户看不到添加的新行。

精简版:

如果需要排序,我在 Entry 类上有一个 DateTime 字段,但老实说,如果我可以在获得它们时将它们添加到顶部,我什至不需要 em> 排序!我只需要:

*添加到集合中的每个项目都添加到top,而不是默认的bottom。*

稍长的版本:

我根本找不到将新元素引入集合“顶部”的方法……“为什么?”好吧,我正在 WPF 表单中显示数据行,我希望最新的数据在顶部,按对象中的日期字段排序。

如果和IList一样,那为什么这么难?

太复杂了?让我简化一下:

超长版:

一开始,有一个类将组成 WPF DataGrid 中的一行。该类称为“Entry”,下面唯一重要的两个属性是:

Class Entry
[...]
Public Property TsCreated As Nullable(Of DateTime)
Public Property EntryRaw As String
    Set(value As String)
        If value <> _entryRaw Then
            _entryRaw = value
            OnPropertychanged("EntryRaw")
        End If
    End Set
    Get
        Return _entryRaw
    End Get
End Property
Private _entryRaw As String
[...]
End Class

接下来是这些Entry的ObservableCollection...

Public Class SysEvents
    Inherits ObservableCollection(**Of Entry**)

    Private Shared _list As New SysEvents

    Public Sub New()
    End Sub
End Class

ObservableCollection 有一个不可覆盖 Sub Add(),所以我无法编写自己的自己的 Sub Add() 并正确排序添加后...

所以在使用中,这个 WPF Window 类是这样的(再次,让它变得非常简单):

Class MainWindow

    Private RawEvents As New toag.syslog.SysEvents

    Sub New()
        grdEntryRaw.ItemsSource = RawEvents ' grid that holds rows
    End Sub

    Sub AddRow()
        Me.RawEvents.Add(sysEvent)
    End Sub

End Class

好像很重要,XAML(我可以在 XAML 中排序吗?):

<DataGrid x:Name="grdEntryRaw" [...]>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding EntryRaw}" Header="Entry Raw" />
    </DataGrid.Columns>
</DataGrid>

好的。没有人做到这一点版本:

由于我无法拦截 Binding 正在执行的 .Add(),所以似乎我无法使用某种排序算法进入那里......

当我想到这么多的时候,我以为战斗已经结束了……但现在看来,成功已经从我手中夺走了 1 码线!哦,Visual Studio.. .你是一个残酷的情妇......

TIA!!!

【问题讨论】:

  • 而不是添加,您尝试过 Insert/InsertItem 吗?如果它们是基于数据/时间的(并且您正在实时获取/添加它们),而不是在开头添加(到末尾)插入。
  • 我已经访问了 NotifyCollectionChangedEventArgs 事件,但我只处理集合的副本,不能执行 ByRef: Public Sub CollChanged(sender As Object, e As System.Collections .Specialized.NotifyCollectionChangedEventArgs) 处理 Me.CollectionChanged 这没关系,但我仍在处理副本... :( SortedList.Move(Me.iTotalElements, 0)
  • @Plutonix,绑定和/或集合自动调用 Add(),我无法拦截它。如果我使用上述事件,我只会弄乱集合的副本(发件人)... :( 我希望 LOVE 使用 Insert 或任何其他有帮助的 ObservableCollection 运算符...但是如何?注意:添加到最后是它现在的方式,我似乎无法改变它。
  • 我假设用户将数据添加到网格时,网格会调用AddRow sub。如果该假设正确,是否可以更改该 sub 以使用 insert :Me.RawEvents.Insert(0, sysEvent)

标签: wpf vb.net wpfdatagrid observablecollection


【解决方案1】:

除了实现INotifyCollectionChangedINotifyPropertyChanged 之外,ObservableCollection 没有什么特别之处。

我建议您使用所需的行为创建自己的ObservableCollection

Public Class ObservableStack(Of T)
    Implements IEnumerable, ICollection, IList
    Implements IEnumerable(Of T), ICollection(Of T), IList(Of T)
    Implements INotifyCollectionChanged, INotifyPropertyChanged

    Public Sub New()
        Me.list = New List(Of T)
    End Sub

   '...

    Public Sub Add(item As T) Implements ICollection(Of T).Add
        'TODO: Validate.
        Me.list.Insert(0, item) 'Insert at top of the list.
        Me.RaisePropertyChanged("Count")
        Me.RaisePropertyChanged("Item")
        Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, item, 0)
    End Sub

    Private Function _Add(obj As Object) As Integer Implements IList.Add
        Me.Add(TryCast(obj, T))
        Return 0
    End Function

   '...

    Private ReadOnly list As List(Of T)

End Class

示例

Public Class ObservableStack(Of T)
    Implements IEnumerable, ICollection, IList
    Implements IEnumerable(Of T), ICollection(Of T), IList(Of T)
    Implements INotifyCollectionChanged, INotifyPropertyChanged

    Public Sub New()
        Me.list = New List(Of T)
    End Sub

    Public Event CollectionChanged As NotifyCollectionChangedEventHandler Implements INotifyCollectionChanged.CollectionChanged
    Protected Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public ReadOnly Property Count() As Integer Implements ICollection.Count, ICollection(Of T).Count
        Get
            Return Me.list.Count
        End Get
    End Property

    Default Public Property Item(index As Integer) As T Implements IList(Of T).Item
        Get
            Return Me.list.Item(index)
        End Get
        Set(value As T)
            Me.Replace(index, value)
        End Set
    End Property

    Private ReadOnly Property _IsFixedSize() As Boolean Implements IList.IsFixedSize
        Get
            Return CType(Me.list, IList).IsFixedSize
        End Get
    End Property

    Private ReadOnly Property _IsReadOnly() As Boolean Implements IList.IsReadOnly, ICollection(Of T).IsReadOnly
        Get
            Return CType(Me.list, IList).IsReadOnly
        End Get
    End Property

    Private ReadOnly Property _IsSynchronized() As Boolean Implements ICollection.IsSynchronized
        Get
            Return CType(Me.list, ICollection).IsSynchronized
        End Get
    End Property

    Private Property _Item(index As Integer) As Object Implements IList.Item
        Get
            Return Me.Item(index)
        End Get
        Set(value As Object)
            Me.Item(index) = DirectCast(value, T)
        End Set
    End Property

    Private ReadOnly Property _SyncRoot() As Object Implements ICollection.SyncRoot
        Get
            Return CType(Me.list, ICollection).SyncRoot
        End Get
    End Property

    Public Sub Add(item As T) Implements ICollection(Of T).Add
        Me.Insert(0, item)
    End Sub

    Public Sub Clear() Implements IList.Clear, ICollection(Of T).Clear
        If (Me.Count > 0) Then
            Me.list.Clear()
            Me.RaisePropertyChanged("Count")
            Me.RaisePropertyChanged("Item")
            Me.RaiseCollectionReset()
        End If
    End Sub

    Public Function Contains(item As T) As Boolean Implements ICollection(Of T).Contains
        Return Me.list.Contains(item)
    End Function

    Public Sub CopyTo(array() As T, index As Integer) Implements ICollection(Of T).CopyTo
        Me.list.CopyTo(array, index)
    End Sub

    Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
        Return Me.list.GetEnumerator()
    End Function

    Public Function IndexOf(item As T) As Integer Implements IList(Of T).IndexOf
        Return Me.list.IndexOf(item)
    End Function

    Public Sub Insert(index As Integer, item As T) Implements IList(Of T).Insert
        'TODO: Validate item.
        Me.list.Insert(index, item)
        Me.RaisePropertyChanged("Count")
        Me.RaisePropertyChanged("Item")
        Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, item, index)
    End Sub

    Public Sub Move(ByVal oldIndex As Integer, ByVal newIndex As Integer)
        Me.MoveItem(oldIndex, newIndex)
    End Sub

    Protected Overridable Sub MoveItem(ByVal oldIndex As Integer, ByVal newIndex As Integer)
        Dim item As T = Me.Item(oldIndex)
        Me.list.RemoveAt(oldIndex)
        Me.list.Insert(newIndex, item)
        Me.RaisePropertyChanged("Item")
        Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex)
    End Sub

    Protected Overridable Sub OnCollectionChanged(e As NotifyCollectionChangedEventArgs)
        RaiseEvent CollectionChanged(Me, e)
    End Sub

    Protected Overridable Sub OnPropertyChanged(e As PropertyChangedEventArgs)
        RaiseEvent PropertyChanged(Me, e)
    End Sub

    Private Sub RaiseCollectionChanged(action As NotifyCollectionChangedAction, item As T, index As Integer)
        Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, item, index))
    End Sub

    Private Sub RaiseCollectionChanged(ByVal action As NotifyCollectionChangedAction, ByVal item As Object, ByVal index As Integer, ByVal oldIndex As Integer)
        Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, item, index, oldIndex))
    End Sub

    Private Sub RaiseCollectionChanged(action As NotifyCollectionChangedAction, oldItem As T, newItem As T, index As Integer)
        Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, newItem, oldItem, index))
    End Sub

    Private Sub RaiseCollectionReset()
        Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
    End Sub

    Private Sub RaisePropertyChanged(propertyName As String)
        Me.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
    End Sub

    Public Function Remove(item As T) As Boolean Implements ICollection(Of T).Remove
        Dim index As Integer = Me.IndexOf(item)
        If (index <> -1) Then
            Me.RemoveAt(index)
            Return True
        End If
        Return False
    End Function

    Public Sub RemoveAt(index As Integer) Implements IList.RemoveAt, IList(Of T).RemoveAt
        Dim item As T = Me.Item(index)
        Me.list.RemoveAt(index)
        Me.RaisePropertyChanged("Count")
        Me.RaisePropertyChanged("Item")
        Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, item, index)
    End Sub

    Public Sub Replace(index As Integer, newItem As T)
        'TODO: Validate item.
        Dim oldItem As T = Me.Item(index)
        Me.list.Item(index) = newItem
        Me.RaisePropertyChanged("Item")
        Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem, newItem, index)
    End Sub

    Private Function _Add(obj As Object) As Integer Implements IList.Add
        Me.Add(DirectCast(obj, T))
        Return 0
    End Function

    Private Function _Contains(obj As Object) As Boolean Implements IList.Contains
        Return Me.Contains(DirectCast(obj, T))
    End Function

    Private Sub _CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo
        CType(Me.list, ICollection).CopyTo(array, index)
    End Sub

    Private Function _GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return Me.GetEnumerator()
    End Function

    Private Function _IndexOf(obj As Object) As Integer Implements IList.IndexOf
        Return Me.IndexOf(DirectCast(obj, T))
    End Function

    Private Sub _Insert(index As Integer, obj As Object) Implements IList.Insert
        Me.Insert(index, DirectCast(obj, T))
    End Sub

    Private Sub _Remove(obj As Object) Implements IList.Remove
        Me.Remove(DirectCast(obj, T))
    End Sub

    Private ReadOnly list As List(Of T)

End Class

【讨论】:

  • 我正在尝试这个,但似乎非标准类的实现接口有冲突的方法和子名称。我需要将上面的列表从 8 中删减……现在想弄清楚要保留哪个! :)
  • @PatTrainor 很高兴您找到了可行的解决方案。关于我的解决方案:首先实现IEnumerableICollectionIList(按此顺序)。几乎所有字段都应该是私有的,您可以使用下划线命名它们,例如Private Function _Add(obj As Object) As Integer Implements IList.Add。现在,像CountRemoveAt 这样的一些字段应该是公共的,并且可以被像Public ReadOnly Property Count() As Integer Implements ICollection.Count, ICollection(Of T).Count 这样的其他接口“共享”。我附上了一个例子。请查看我的编辑。
【解决方案2】:

@Bjørn-Roger Kringsjå 无疑为我指明了正确的方向。

我把事情精简到最低限度。

我没有创建自己的集合类,而是在 WPF Window 类中创建了集合:

WPF MainWindow 类顶部的声明:

Private RawEvents As ObservableCollection(Of Entry)

然后,我必须在类的实例化机制中实例化它,并为 DataGrid 设置 ItemsSource:

RawEvents = New ObservableCollection(Of Entry)
grdEntryRaw.ItemsSource = RawEvents ' source for main window (events)

唯一剩下的就是将新事件放入集合中(我从消息队列中获取新事件,但这并不重要:

Public Sub PeekQ(ByVal sender As System.Object, ByVal e As System.Messaging.PeekCompletedEventArgs) Handles Q.PeekCompleted
    [..]    
    ' send to main display (newest entries on top)
    Me.Dispatcher.Invoke(CType(Sub() **Me.RawEvents.Insert(0, someEvent)**, Action))
    '
    Me.CountryLookupQ.BeginPeek()

End Sub

...就是这样!我什至不需要额外的类来保存事件......我只是使用了在 WPF 窗口中创建的 ObservableCollection。 XAML 非常简单,最好的部分是没有排序算法:

[...]
<DockPanel x:Name="EntryRawDockPanel"  HorizontalAlignment="Left" LastChildFill="False" Width="517" Margin="0,26,0,41">
    <DataGrid x:Name="grdEntryRaw" Grid.Column="1" Margin="0,0,10,43" AutoGenerateColumns="False" HorizontalContentAlignment="Stretch" CanUserAddRows="False" CanUserDeleteRows="True" AlternatingRowBackground="#FFDEFFE4">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding EntryRaw}" Header="Entry Raw" IsReadOnly="False"/>
        </DataGrid.Columns>
    </DataGrid>
</DockPanel>
[...]

老实说,这就是整个解决方案。 Entry() 类在任何方面都不特殊。

希望这对其他人有所帮助,是的,我见过不止一种方法可以做到这一点,比如在 XAML 中排序,甚至在 XAML 中实例化一个类,但对于我的写作方式来说,这是最简单的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-14
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-16
    相关资源
    最近更新 更多