限制每秒绑定更新

本文关键字:绑定 更新 | 更新日期: 2023-09-27 17:53:00

我目前正在创建一个程序,该程序读取通过COM端口发送的数据,然后将其绘制在图表中。数据是使用MVVM原理显示的,当数据以10Hz左右的频率发送时,它可以很好地工作。然而,正在读取数据的设备可以达到1 kHz的刷新率,这意味着每分钟1000个数据集。这对于显示和更新简单的文本框很好,但是它会破坏图表,因为更新发生得太快了。

我认为我现在需要做的是限制发送到订阅的类和页面的更新事件的数量,这样只有有限数量的数据被发送,这给了图表一个正确绘制的机会。是否有一种方法可以自动限制这一点,或者您建议手动进行哪些代码调整?

我的集合更改事件中的一小段代码:

void dataItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    NotifyPropertyChanged("dataItems");
    NotifyPropertyChanged("lastItem");
    // update any charts
    NotifyPropertyChanged("AccelXData");
    NotifyPropertyChanged("AccelYData");
    NotifyPropertyChanged("AccelZData");
}
// handle property changes
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
    var handler = this.PropertyChanged;
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}

每个数据集也有一个ID,也许可以用来检查何时手动更新,作为一个想法。

限制每秒绑定更新

一个更好的方法是在数据发生变化时删除对NotifyPropertyChanged的调用。

创建计时器并在计时器上刷新。这样您就可以控制刷新率,并且它不绑定到数据到达的速率。

这不是一个完整的答案,但需要注意:

我看到你从你的CollectionChanged处理程序中做NotifyPropertyChanged("dataItems")。我认为您不希望这样做,而且它可能会导致性能问题。dataItems似乎是ObservableCollection<T>类型的属性。当集合被更改时,集合本身发送一个CollectionChanged事件。在您的UI中,ItemsControl (ComboBox, ListBox等)可能绑定到dataItems属性。当集合引发其CollectionChanged事件时,您无法保证调用事件处理程序的顺序。如果您的UI首先处理事件,它可能会尝试为集合中的新/旧项分配/释放容器和UI元素。当您手动调用NotifyPropertyChanged("dataItems")时,UI可能会丢弃所有UI元素并重新构建它们(取决于UI元素是否足够智能以识别值没有更改,也取决于容器回收逻辑)。这显然是低效的。永远不要发送PropertyChanged通知,除非属性的返回值/对象发生了变化。

现有的两个答案都是有效的,因为这是一个dup起源,是时候添加更多的上下文,可能会使它与未来的重复引用更相关

在MVVM世界中,这是一个常见的模式,特别是当您定义了一些解析表达式的只读属性并且不受属性支持时。在正常使用下,对NotifyPropertyChanged()的强制调用可能不会引起问题,但是当您增量加载大型记录集或对集合执行操作时,在操作结束之前禁用更新可能会很有用:

/// <summary>
/// Example of a readonly expression backed property,
/// created to simplify MVVM bindings.
/// </summary>
public object lastItem { get => dataItems?.Last(); }
/// <summary>Flag to disable <see cref="NotifyPropertyChanged(string)"/> for collection related fields</summary>
private bool suppressCollectionUpdates = false;
void dataItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (!suppressCollectionUpdates)
    {
        NotifyPropertyChanged(nameof(dataItems));
        NotifyPropertyChanged(nameof(lastItem));
        // update any charts
        NotifyPropertyChanged(nameof(AccelXData));
        NotifyPropertyChanged(nameof(AccelYData));
        NotifyPropertyChanged(nameof(AccelZData));
    }
}

/// <summary>
/// A long running operation that causes the UI to update too frequently
/// </summary>
void Operation()
{
    suppressCollectionUpdates = true;
    try
    {
        ... Do long running or incremental changes to the dataItems
    }
    finally
    {
        // leave it back in the default state
        suppressCollectionUpdates = false;
        // Call the change event manually, use the Reset value to indicate a dramatic change ocurred.
        // You could also send null because our code does not use these references anyway ;)
        dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
    }
}
// handle property changes
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
{
    handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

如果这是MVVM,你可能会绑定某种进度旋转器或其他形式的视觉反馈到这个suppressCollectionUpdates,在这种情况下,你将标志命名为更合适的东西,如IsBusyIsLoading,并设置与后台字段和调用NotifyPropertyChanged

对于长时间运行的操作和高频率更改的另一个选择是,您可以引入一个计时器来定期调用刷新:

private void notifyCollectionChangeTimer_Tick(object sender, EventArgs e)
{
    suppressCollectionUpdates = false;
    dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
    suppressCollectionUpdates = true;
}
/// <summary>
/// A long running operation that causes the UI to update too frequently
/// </summary>
/// <remarks>A timer will call refresh periodically</remarks>
void Operation()
{
    suppressCollectionUpdates = true;
    DispatcherTimer timer = new DispatcherTimer();
    try
    {
        timer.Interval = TimeSpan.FromSeconds(1); // how often to refresh the UI
        timer.Tick += notifyCollectionChangeTimer_Tick;
        timer.Start();

        ... Do long running or incremental changes to the dataItems
    }
    finally
    {
        _refreshTimer.Stop(); // stop timer
        _refreshTimer.Tick -= OnTick; // unsubscribe from timer's ticks (just in case you move the timer to a parent scope ;)
        // leave it back in the default state
        suppressCollectionUpdates = false;
        // Call the change event manually, use the Reset value to indicate a dramatic change ocurred.
        // You could also send null because our code does not use these references anyway ;)
        dataItems_CollectionChanged(dataItems, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
    }
}

类似问题和进一步阅读:

  • WPF绑定:限制绑定列表的更新
  • 解除和限制调度程序事件
    • 这是我最喜欢的gotos之一,有一段时间不需要它了,但有很多美好的回忆