我会尽量做到尽可能详细,整个下午都在搜索,我找不到任何与我的问题相似的东西来获得解决方案。
我的应用程序的简要说明
我正在制作的应用程序提供了一个警报类型系统,用于在游戏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
对此的任何帮助都会受到极大的赞赏。
答案 0 :(得分:3)
答案很简单,感谢@KornMuffin对ICollectionViewLiveShaping.IsLiveSortin
的引用。
以下是我实施和解决此问题的步骤。
在我的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()));
}