我正在制作一个 MVVM 应用程序,该应用程序显示一个随时间增长的列表,并标记先前更新列表时未列出的新元素。我试图确保我使用异步方法,以便 GUI 感觉响应并且不会锁定。
我绑定(bind)到列表项的 ResultDate
和 LastCheckedTime
在 View 模型中,通过转换器发送它们,如果 ResultDate 比 LastCheckedTime 更新,则相应地设置样式:
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource CompareIfNewerConverter}">
<Binding Path="ResultDate" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.LastCheckedTime"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="#FFF90E0E"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
在我的 ViewModel 中,我运行一个异步 SQL 请求,它手动触发
PropertyChanged
对于 LastCheckedTime
结果出来后,UI 会相应地标记新项目,然后才更新 LastCheckedTime
,并且不会触发 PropertyChanged
(仅在下一次被解雇)。但是,如果我不添加延迟,样式会在 LastCheckedTime
之后完成.我不喜欢依赖时间,因为一个人不知道它是否会在别人的电脑上坏掉。这是我解决问题的技巧-这对我有用:private async Task DoTheFilter(CancellationToken token)
{
MatchingResultList = await Connectivity.ReturnResultsForPanicViewAsync(token);
DataGridHeader = (MatchingResultList != null) ? $"Records found: {MatchingResultList.Count}" : "Cancelled.";
if(MatchingResultList != null && MatchingResultList.Any((r) => r.ResultDate > LastCheckedTime)) System.Media.SystemSounds.Exclamation.Play(); //If any result is newer then the last listing time, warn user with sound
OnPropertyChanged("LastCheckedTime"); //update visual indicator for new results manually.
await Task.Delay(133); //allow GUI redraw - Timing based and prone to error?
LastCheckedTime = DateTime.Now; //update the last checked time only then.
}
如果我不添加延迟,则在 ViewModel 中更新 LastTimeChecked 后应用样式,这意味着没有新项目显示为新项目。我已经看到,即使是
1
的延迟毫秒解决了这个问题,但我做了 133
只是为了填充它。但是,我真的很想知道是否有一个完全强大和可靠的修复,或者我是否可以采取完全不同的方法。编辑:我用两种方法解决了这个问题——这是第一种:
OnPropertyChanged("LastCheckedTime"); //update visual indicator for new results manually.
Application.Current.Dispatcher.Invoke(new Action(() => LastCheckedTime = DateTime.Now), DispatcherPriority.ContextIdle, null); //only update LastCheckedTime after context becomes idle.
在我手动通知 ui 上次更改后,我仅在上下文空闲后更新 LastCheckedTime。与等待任意时间相比,这似乎是一种更稳健的方式。
然而,更好的解决方案似乎是“缓冲”
LastCheckedTime
使用第二个 DateTime
正如Peter Duniho(解决方案#3)所建议的那样。这是代码...命令修改为
LastCheckedTime
在调用 DoTheFilter()
之前先更新FilterTestCommand = AsyncCommand.Create((token) => { LastCheckedTime = NewCheckedTime; return DoTheFilter(token); });
我更新了
NewCheckedTime
里面 DoTheFilter()
.我为 LastCheckedTime
开启了自动通知功能。属性(property)变化,顺便说一句。也就是说,这可能会在新列表到达之前强制对当前列表进行不必要的重新更新,但这不会造成性能问题。该解决方案有效而简单,我没有想到它有点尴尬,但我确实学到了一些我可以在其他地方使用的新技巧(调度程序)。
最佳答案
I'd really like to know if there's a fully robust and dependable fix, or whether there's an entirely different approach that I may take
就在这里。您对您当前的方法持怀疑态度是正确的。它本质上是损坏的,最终会产生不正确的结果。您的实现似乎依赖于 WPF 永远不会自行重新检查
LastCheckedTime
的假设。值,并且只有在调用 OnPropertyChanged()
时才这样做直接方法。确实,它可能不会这样做,但是没有契约(Contract)要求它不这样做,我可以很容易地想象 WPF 决定出于某种原因需要刷新其所有绑定(bind)并最终检索最新值的场景LastCheckedTime
即使你没有明确告诉它。唯一可靠的方法是首先确保您有可靠的视觉显示输入。您可以通过我能想到的至少三种方式之一来完成此操作:
bool
View 模型的属性,指示该项目是否是新的。在检索当前数据之前,请清除所有项目上的标志。当您检索数据时,无需重写整个项目集,只需添加新项目,在这些新项目上设置“是新项目”标志。无需使用比较转换器进行多重绑定(bind),只需直接绑定(bind)到此标志属性即可。 LastCheckedTime
之前值,浏览项目列表并根据该项目的“结果”时间是否比 LastCheckedTime
更近来适本地设置标志值与否。同样,绑定(bind)到标志属性而不是使用比较转换器。 我注意到,根据您的描述,这听起来好像
LastCheckedTime
窗口对象模型中的属性(即 DataContext
对象)不会在属性值更改时自动引发属性更改通知。这本身就是可疑的。如果您使用上述技术之一,您将能够继续并在每个属性 setter 中对所有模型属性实现属性更改通知。如果您这样做,您将确保所有绑定(bind)将在任何绑定(bind)属性更改时立即更新,这反过来将确保代码的一致且无错误的行为,而用户不会看到任何与时间相关的问题。如果上面看起来有点模糊,那只是因为你的问题没有包含一个好的 Minimal, Complete, and Verifiable example可靠地重现了问题。如果您想要更具体的建议,包括一个很好的代码示例,可以准确显示上述选项的外观,请在问题中添加一个很好的代码示例。
关于c# - 在修改 C# WPF 应用程序中的绑定(bind)属性之前,如何确保我与 UI 同步以基于绑定(bind)更改样式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36280391/