c# - 在修改 C# WPF 应用程序中的绑定(bind)属性之前,如何确保我与 UI 同步以基于绑定(bind)更改样式?

标签 c# wpf mvvm

我正在制作一个 MVVM 应用程序,该应用程序显示一个随时间增长的列表,并标记先前更新列表时未列出的新元素。我试图确保我使用异步方法,以便 GUI 感觉响应并且不会锁定。

我绑定(bind)到列表项的 ResultDateLastCheckedTime在 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)到标志属性而不是使用比较转换器。
  • 除了“上次检查”时间之外,还存储“上次检查”时间。使用“先前检查”时间在 View 中进行比较。您将使用“上次检查”时间的唯一方法是在每次检索数据时更新“上次检查”时间。在这种情况下,您仍然可以使用比较转换器的多重绑定(bind);只是您将与“先前检查”的时间值而不是“上次检查”进行比较。

  • 我注意到,根据您的描述,这听起来好像 LastCheckedTime窗口对象模型中的属性(即 DataContext 对象)不会在属性值更改时自动引发属性更改通知。这本身就是可疑的。如果您使用上述技术之一,您将能够继续并在每个属性 setter 中对所有模型属性实现属性更改通知。如果您这样做,您将确保所有绑定(bind)将在任何绑定(bind)属性更改时立即更新,这反过来将确保代码的一致且无错误的行为,而用户不会看到任何与时间相关的问题。

    如果上面看起来有点模糊,那只是因为你的问题没有包含一个好的 Minimal, Complete, and Verifiable example可靠地重现了问题。如果您想要更具体的建议,包括一个很好的代码示例,可以准确显示上述选项的外观,请在问题中添加一个很好的代码示例。

    关于c# - 在修改 C# WPF 应用程序中的绑定(bind)属性之前,如何确保我与 UI 同步以基于绑定(bind)更改样式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36280391/

    相关文章:

    c# - 根据 DataContext 类型添加行为或样式

    c# - Wpf 将动态位图添加到图像

    c# - 每当聚焦新窗口时触发事件?

    javascript - 空数组绑定(bind)元素在页面加载时消失

    c# - 使用 MVVM 的 WPF ComboBox 双向绑定(bind)问题

    c# - 使用 C# 读取数百万个小文件

    c# - 限制某些用户的服务的最佳方式是什么

    c# - 单击取消按钮时如何回滚 WPF 子窗口中更改的数据

    c# - WinForms 中 C# 中的 OpenGL

    c# - 如何更改 OxyPlot Y 轴字符串格式?