【问题标题】:WPF: DataGrid Sort When Values ChangeWPF:值更改时的 DataGrid 排序
【发布时间】:2016-06-24 15:02:25
【问题描述】:

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

简要说明我的应用程序做了什么

我正在制作的应用程序提供了一个警报类型系统,用于在游戏 FFXIV 中收集物品何时可用。特定物品可以在游戏世界时间(Eorzea Time)内的特定时间收集。我的应用程序显示收集项目的列表、收集它们的开始和结束时间,以及 Next 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>

如您所见,DataGridItemsSource 绑定到 AlarmsListCollection

在AlarmView的ViewModel中,我初始化了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&lt;Model.AlarmItem&gt;。这里要知道的重要一点是Model.AlarmItem 包含一个名为NextSpawn 的属性。该属性是一个String,它存储了Model.AlarmItem 将在多长时间内产生的表示。

NextSpawn 属性字符串在 System.Timers.TimerElapsed 事件中每 1 秒更新一次

    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% 有效。但是,随着Model.AlarmItemsNextSpawn 属性值的更新,列的排序不会更新以反映任何更改。

我已经尝试了所有我能想到的方法并进行了详尽的搜索以找到解决方案。我已经尝试在计时器的经过事件中在 DataGrid 上使用 Dispatcher.Invoke() ,但这只会导致 UI 因调用频率而陷入困境。

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

http://gfycat.com/RealisticInferiorAmericanriverotter

我们将非常感谢您对此提供任何帮助。

【问题讨论】:

  • .Net 4.5 引入了 ICollectionViewLiveShaping.IsLiveSorting (msdn.microsoft.com/en-us/library/…),如果您还没有看到的话。顺便说一句...漂亮的应用程序
  • @KornMuffin 你是我现在看到的最漂亮的人,这很完美。非常感谢
  • 感谢分享您的解决方案并乐于提供帮助

标签: c# wpf xaml sorting datagrid


【解决方案1】:

答案非常简单,感谢@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 访问数据网格以获取事件(稍后会介绍)

​​>

在 AlarmViewModel.cs 中有 ObservableCollection

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

然后,在AlarmViewModel 的构造函数中,我实例化了ObservableCollection 并使用ICollectionViewLiveShaping.IsLiveSorting 设置为true。在这里,我还利用后面的 AlarmView 代码中创建的静态变量来访问数据网格,以便我们可以挂钩到 .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()));

    }

【讨论】:

    猜你喜欢
    • 2014-08-28
    • 1970-01-01
    • 2012-07-15
    • 2013-12-31
    • 1970-01-01
    • 1970-01-01
    • 2018-04-15
    • 2013-05-13
    • 2011-09-04
    相关资源
    最近更新 更多