WPF DataGrid默认排序不起作用

12

我有一个DataGrid,它的XAML列如下:

<DataGridTextColumn Header="Time" Binding="{Binding Date, StringFormat='yyyy-MM-dd  HH:mm:ss'}" SortMemberPath="Date" SortDirection="Descending" Width="130" CanUserResize="True" />
<DataGridTextColumn Header="Level" Binding="{Binding Level}" Width="60" CanUserResize="True" />
<DataGridTextColumn Header="Source" Binding="{Binding Logger}" Width="150" CanUserResize="True" />
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="*" CanUserResize="True" />

我将其绑定到一个ObservableCollection<EalsLogEvent>,其中EalsLogEvent.Date的类型为DateTime

public ObservableCollection<EalsLogEvent> LogEvents 
{
    get
    {
        return _logEvents;
    }
}

网格视图模型使用定时器来刷新自身,在加载应用程序时,网格看起来一切正常,除了Time列在首次加载时似乎是降序排序,但实际上是升序排序。

为了正确排序,我必须点击两次列标题;第一次将顺序更改为升序,这与列的内容匹配。第二次单击列标题会将其排序顺序返回到降序,并且这次它会正确地对列内容进行排序,即按降序排序。

如果我在_logEvents刷新时使用LINQ对集合进行排序,则会失去用户通过单击标题设置的任何顺序。如果我必须让视图告诉模型LINQ排序应该使用哪个顺序,那么就有点糟糕了。


1
你尝试过使用 CollectionView 进行排序吗? - dymanoid
@dymanoid,我想我曾经在很久以前尝试过,但今天会再试一次。所有可用的参考资料都告诉我,DataGrid 应该维护自己的 CollectionView 或其三个派生类之一。 - ProfK
@dymanoid,当ObservableCollection是泛型类型而CollectionView及其子类ListCollectionViewBindingListCollectionView都不是泛型类型时,我该如何将ObservableCollection替换为任何一个CollectionView或其子类? - ProfK
1个回答

19
您可以在XAML中使用CollectionViewSource来定义默认排序。
假设我们有一个视图模型:
public class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Item> Items { get; private set; }
}

我们可以为“Items”集合创建一个自定义的“CollectionView”:
<Window xmlns:l="clr-namespace:YourNamespace"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase">
    <Window.DataContext>
        <l:ViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <CollectionViewSource Source="{Binding Items}" x:Key="GridItems">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="Date" Direction="Descending"/>
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
    </Window.Resources>
    <DataGrid ItemsSource="{Binding Source={StaticResource GridItems}}" AutoGenerateColumns="False">
        <DataGrid.Columns>                    
            <DataGridTextColumn Header="Time" Binding="{Binding Date, StringFormat='yyyy-MM-dd  HH:mm:ss'}" Width="130" CanUserResize="True" />
            <DataGridTextColumn Header="Level" Binding="{Binding Level}" Width="60" CanUserResize="True" />
            <DataGridTextColumn Header="Source" Binding="{Binding Logger}" Width="150" CanUserResize="True" />
            <DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="*" CanUserResize="True" />
        </DataGrid.Columns>
    </DataGrid>
</Window>

采用这种方法,您的基础源集合(在此示例中为Items)不会受到影响,排序仅在视图中发生。
正如您可以在MSDN中阅读到的那样:

您可以将集合视图视为位于绑定源集合之上的层,它允许您根据排序、筛选和分组查询来导航和显示集合,而无需操纵基础源集合本身。如果源集合实现了INotifyCollectionChanged接口,则由CollectionChanged事件引发的更改将传播到视图。

您还应注意以下内容:

所有集合都有一个默认的CollectionView。WPF始终绑定到视图而不是集合。如果直接绑定到集合,则WPF实际上会绑定到该集合的默认视图。

因此,使用CollectionViewSource,您只是为集合定义了一个自定义视图。

这里的一个复杂情况是,我在视图模型中有一个计时器(System.Timer),它定期轮询 EalsLogEvent 数据源(一个 DbSet),以便随着从各个地方通过 NLog 添加到该表中的行,更新自身。为了让计时器线程更新 DataGrid,我通过构造函数传递了视图的 Dispatcher_viewModel = new LogViewModel(Dispatcher)。在视图内部创建视图模型无法让我这样做。我正在忙于尝试看看 ViewModelLocator 是否能帮助我。 - ProfK
@ProfK,您不必在视图的XAML中实例化viewmodel,那只是一个示例。 - dymanoid
2
太棒了,@dymanoid!非常感谢。然而,让我有点烦恼的是,我咨询的许多资源似乎认为在行本身上使用普通的SortMemberPathSortDirection属性就足够了。难道当DataGrid构建自己的默认CollectionView时,不应该使用这些XAML属性吗? - ProfK
如果您希望网格的初始可视状态反映活动排序(即在列标题中显示一个小箭头),则还需要在相关列上设置“SortDirection”属性。这个解决方案是有效的,但我希望我能够理解为什么需要这样做。也许当网格为空并且稍后进行数据绑定时,排序会最初应用? - dlf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接