WPF:DataGrid在值更改时排序

时间:2016-03-10 00:03:04

标签: c# wpf xaml sorting datagrid

我会尽量做到尽可能详细,整个下午都在搜索,我找不到任何与我的问题相似的东西来获得解决方案。

我的应用程序的简要说明

我正在制作的应用程序提供了一个警报类型系统,用于在游戏FFXIV中提供收集项目时。可以在游戏世界的时间内(Eorzea Time)在特定时间收集特定项目。我的应用程序显示收集项目的列表,收集它们的开始和结束时间,以及下一个Spawn计算(多久可以再次使用)

我的代码

我正在尝试尽可能接近MVVM模式。我的视图包含DataGrid

AlarmView.XAML

        <DataGrid Grid.Row="1"
                      Name="dgAlarms"
                      ItemsSource="{Binding AlarmsListCollection, UpdateSourceTrigger=PropertyChanged}"
                      SelectedValue="{Binding SelectedAlarm}"
                      AutoGenerateColumns="False"
                      IsReadOnly="True"
                      IsSynchronizedWithCurrentItem="True"
                      CanUserAddRows="False"
                      CanUserDeleteRows="False"
                      CanUserReorderColumns="False"
                      CanUserResizeColumns="False"
                      CanUserResizeRows="False"
                      CanUserSortColumns="True"
                      SelectionMode="Single"
                      >
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Item List"
                                Binding="{Binding Name}" 
                                        Width="auto"/>
                    <DataGridTextColumn Header="Next Spawn"
                                Binding="{Binding NextSpawn, UpdateSourceTrigger=PropertyChanged}" 
                                        Width="auto"
                                        SortMemberPath="{Binding NextSpawn, UpdateSourceTrigger=PropertyChanged}"
                                        SortDirection="Ascending"/>
                    <DataGridTextColumn Header="Start"
                                Binding="{Binding StartTime}" 
                                        Width="auto"/>
                    <DataGridTextColumn Header="End"
                                Binding="{Binding EndTime}" 
                                        Width="auto"/>
                </DataGrid.Columns>
            </DataGrid>

如您所见,ItemsSource的{​​{1}}绑定到DataGrid

在AlarmView的ViewModel中,我启动了AlarmsListCollection这样

AlarmListCollection

//========================================================= // Private Fields //========================================================= private ObservableCollection<Model.AlarmItem> _alarmsListCollection; //========================================================= // Properties //========================================================= public ObservableCollection<Model.AlarmItem> AlarmsListCollection { get { return this._alarmsListCollection; } set { if (this._alarmsListCollection == value) return; this._alarmsListCollection = value; } } //========================================================= // Constructor //========================================================= public AlarmsViewModel(DataGrid dgReference) { if (_alarmItemRepository == null) _alarmItemRepository = new AlarmItemRepository(); // Initilize the AlarmsListCollection this.AlarmsListCollection = new ObservableCollection<Model.AlarmItem>(_alarmItemRepository.GetAlarmItems()); } 只返回包含对象的_alarmItemRepository.GetAlarmItems()。这里要知道的重要一点是List<Model.AlarmItem>包含一个名为Model.AlarmItem的属性。此属性为NextSpawn,它存储了String生成的时间表示。

Model.AlarmItem属性字符串我们在NextSpawn

Elapsed事件中每1秒更新一次
System.Timers.Timer

此代码运行后, struct AlarmInfo { public TimeSpan StartTime; public TimeSpan NextSpawn; public bool Armed; public bool IssueEarlyWarning; } private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // Go through each of the alarm items foreach(Model.AlarmItem alarmItem in this.AlarmsListView) { // Get the current eorzea time span TimeSpan currentEorzeaTimeSpan = this.EorzeaClock.GetEorzeaTimeSpan(); // Get info about the alarm item AlarmInfo alarmInfo = new AlarmInfo(); TimeSpan.TryParse(alarmItem.StartTime, out alarmInfo.StartTime); alarmInfo.Armed = alarmItem.Armed; alarmInfo.IssueEarlyWarning = alarmItem.EarlyWarningIssued; TimeSpan.TryParse(alarmItem.NextSpawn, out alarmInfo.NextSpawn); #region CalculateTimeTillSpawn // Get the time difference between the alarm time and eorzea time TimeSpan timeDiff; TimeSpan nextEorzeaSpawn; if (alarmInfo.StartTime.Equals(new TimeSpan(0, 0, 0))) { timeDiff = (new TimeSpan(24, 0, 0)).Subtract(currentEorzeaTimeSpan); } else { timeDiff = alarmInfo.StartTime.Subtract(currentEorzeaTimeSpan); } if (alarmInfo.StartTime > currentEorzeaTimeSpan) { nextEorzeaSpawn = alarmInfo.StartTime.Subtract(currentEorzeaTimeSpan); } else { //alarm.TimeTillSpawnEorzea = ((TimeSpan)new TimeSpan(23, 59, 59)).Subtract(currentEorzeaTimeSpan.Subtract(alarm.StartTime)); nextEorzeaSpawn = ((TimeSpan)new TimeSpan(23, 59, 59)).Subtract(currentEorzeaTimeSpan.Subtract(alarmInfo.StartTime)); } long earthTicks =nextEorzeaSpawn.Ticks / (long)Utilities.ClockController.EORZEA_MULTIPLIER; alarmInfo.NextSpawn = new TimeSpan(earthTicks); #endregion CalculateTimeTillSpawn // Push the alarmInfo back into the alarmItem alarmItem.Armed = alarmInfo.Armed; alarmItem.EarlyWarningIssued = alarmInfo.IssueEarlyWarning; alarmItem.NextSpawn = alarmInfo.NextSpawn.ToString(@"h\h\:m\m\:s\s", System.Globalization.CultureInfo.InvariantCulture); } this.UpdateTimer.Start(); } 属性更新后,更新的信息会反映回NextSpawn,没有任何问题。我可以坐下来观察datagrid的NextSpawn列中的值,因为它们在更新时每秒都会发生变化。但是,这导致了我遇到的问题。

问题

为了便于使用,我希望用户能够单击DataGrid的Next Spawn列标题,并根据此列进行排序。这按预期工作100%。但是,当DataGrid的{​​{1}}属性值更新时,列的排序不会更新以反映任何更改。

我已经尝试了所有我能想到的东西,并且无穷无尽地寻找解决方案。我已经尝试在计时器的已用事件中使用DataGrid上的Dispatcher.Invoke(),但这只会导致用户界面因为调用频率而陷入困境。

我创建了一个gif来展示我在说什么。在这个gif中,NextSpawn列按升序排序,您可以看到值更新。一旦他们到达0h:0m:0s,之后1秒,他们会更新到他们的新值,此时,应该进行排序以向上移动较小的值。

http://gfycat.com/RealisticInferiorAmericanriverotter

对此的任何帮助都会受到极大的赞赏。

1 个答案:

答案 0 :(得分:3)

答案很简单,感谢@KornMuffin对ICollectionViewLiveShaping.IsLiveSortin的引用。

https://msdn.microsoft.com/en-us/library/system.componentmodel.icollectionviewliveshaping.islivesorting(v=vs.110).aspx

以下是我实施和解决此问题的步骤。

在我的AlarmView.xaml代码后面,我添加了一个静态变量,其中包含所创建的AlarmView实例。

这是AlarmView的构造函数

public partial class AlarmsView : UserControl
{
    public static AlarmsView View;
    public AlarmsView()
    {
        InitializeComponent();
        View = this;
        this.DataContext = new ViewModel.AlarmsViewModel();

    }
}

我创建了静态视图,因为我需要从ViewModel访问一个事件的datagrid(稍后会讨论)

在AlarmViewModel.cs中有ObservableCollection

    public CollectionViewSource ViewSource { get; set; }
    public ObservableCollection<Model.AlarmItem> Collection { get; set; }

然后,在AlarmViewModel的构造函数中,我实例化ObservableCollection并使用ICollectionViewLiveShaping.IsLiveSorting设置为true。在这里,我还使用后面的AlarmView代码中创建的静态变量来访问datagrid,以便我们可以挂钩.Sorting事件。

    public AlarmsViewModel()
    {

        if (_alarmItemRepository == null)
            _alarmItemRepository = new AlarmItemRepository();


        this.Collection = new ObservableCollection<Model.AlarmItem>(_alarmItemRepository.GetAlarmItems());
        ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Collection);
        collectionView.SortDescriptions.Add(new SortDescription("NextSpawn", ListSortDirection.Ascending));
        var view = (ICollectionViewLiveShaping)CollectionViewSource.GetDefaultView(this.Collection);
        view.IsLiveSorting = true;

        //  Bind to the sorting event of the datagrid in the AlarmView
        AlarmsView.View.dgAlarms.Sorting += DgAlarms_Sorting;

        //  Other code
        // ...
        // ...


    }

然后,在排序事件处理中,当用户单击列标题对列进行排序时,我们会更新排序说明。

    private void DgAlarms_Sorting(object sender, DataGridSortingEventArgs e)
    {

        ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Collection);
        collectionView.SortDescriptions.Add(new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection.GetValueOrDefault()));

    }