如何确保,ViewModel属性在再次更改它之前已经在视图上绑定了?

有以下情况: ViewModel有一个变化非常快的对象。 (通过不同的线程)

View通过NotifyPropertyChanged接口获得通知,但它看起来很慢并且在View绑定新值并绘制之前它会更改次数因此它会错过某些值。

我还尝试将View绑定到队列,然后ViewModel可以将其排队, View可以通过dequeueing绘制。

不幸的是发生了另一个问题:在RaisePropertyChanged(() => queue); View未被告知已更改。

在这种情况下, INotifyPropertyChanged接口的实现不起作用。

你有什么主意吗?

ViewModel示例代码:

 public class ExamplaryViewModel { public ExamplaryViewModel() { Messenger.Default.Register<NotificationMessage>(this, m => ProcessNotificationMessage(m.Content)); } public void ProcessNotificationMessage(Message message) { MessageOftenBeingChanged = message; RaisePropertyChanged(() => MessageOftenBeingChanged ); } } 

View绑定到MessageOftenBeingChanged

另一种选择是按照评论中的建议准备快照:

 public void ProcessNotificationMessage(Message message) { Messages.Enqueue(message); RaisePropertyChanged(() => Messages); } 

View

 <controls:RichTextBoxMonitor Messages="{Binding Messages} 

Control

 public class BindableRichTextBox : RichTextBox { public static readonly DependencyProperty MessagesProperty = DependencyProperty.Register("Messages", typeof(ConcurrentQueue), typeof(BindableRichTextBox ), new FrameworkPropertyMetadata(null, OnQueueChangedChanged)); public ConcurrentQueue CyclicMessages { get { return (ConcurrentQueue)GetValue(MessagesProperty ); } set { SetValue(MessagesProperty , value); } 

但是,不幸的是, RaisePropertyChanged()方法不会触发发生的变化。

我计划在事件中控制OnQueueChangedChanged尝试出列并只是绘制项目作为段落的新内联。

您可以实现Producer-Consumer 。

看看这个简化版本。

  • RunProducer仅用于测试,在您的情况下, ProcessNotificationMessage将以类似的方式工作。
  • RunConsumer是一种不断检查新消息并设置Message有一些延迟的方法,否则用户将无法读取它。
  • 它只是概念的快速certificate,但您可以更好地实现它,例如通过提供方法ShowNextMessageIsMessageAvailable ,然后视图可以决定何时准备好显示新消息并请求它。 这将是一个更好的设计。 即使用户可以更快地隐藏某些消息,您也只需要将ShowNextMessage绑定到Click事件。
  • 完整的源代码

     public class MyViewModel : INotifyPropertyChanged { public ConcurrentQueue Queue { get; set; } #region Message private string _message; public string Message { get { return _message; } set { if (_message != value) { _message = value; OnPropertyChanged(); } } } #endregion public MyViewModel() { Queue = new ConcurrentQueue(); RunProducer(); RunConsumer(); } public void RunProducer() { Task.Run(() => { int i = 0; while (true) { if (Queue.Count < 10) Queue.Enqueue("TestTest " + (i++).ToString()); else Task.Delay(500).Wait(); } }); } public void RunConsumer() { Task.Run(() => { while (true) { if (Queue.Count > 0) { string msg = ""; if (Queue.TryDequeue(out msg)) Message = msg; } else { Task.Delay(500).Wait(); } Task.Delay(100).Wait(); } }); } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName]string propertyName = null) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } 

如果是空队列,您可以使用ManualResetMonitor来避免不必要的迭代。

备注你的代码:
如果可以更改集合,那么出于绑定目的,您应该只使用ObservableCollection (或实现INotifyCollectionChanged东西),因为它跟踪更改并且不会重新加载所有内容。

但是在您的代码中,应该刷新整个绑定(因为您已通知整个集合已被更改),但我认为这种机制更智能并检查引用是否相等,如果是,则不进行刷新。 可能是一个hax将它设置为null并返回将刷新它:-)。

通过许多调查我决定通过任何装饰器绑定到RichText框,带有额外DependencyProperty和Converter的自定义控件效率不高。

我的结论表明,构建自定义的richTextbox毫无价值 – 并确保在更改之前显示新值。

我辞职直接绑定。

我在缓冲区 – 队列中收集任何新消息。

我决定使用类似于消费者的东西(正如Wojciech Kulik建议的那样)

我将我的消费者基于TimeDispatcher,它在Tick的间隔中检查队列中是否存在任何新消息。 如果为真,那么它会出列并收集它,最后是RaiseMonitorItemsAdd。

查看事件上方的句柄:

  if (dataContext is IMonitorable) { Context = dataContext as IMonitorable; Context.MonitorViewModel.RaiseMonitorItemAdd += MonitorViewModelOnRaiseMonitorItemAdd; Context.MonitorViewModel.RaiseMonitorCleared += MonitorViewModel_RaiseMonitorCleared; } private void MonitorViewModelOnRaiseMonitorItemAdd(object sender, MonitorEventArgs monitorEventArgs) { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { _paragraph.Inlines.AddRange(MonitorItemConverter.ConvertToInlines(monitorEventArgs.MonitorItem)); _richTextBox.ScrollToEnd(); })); } 

更多在RichTextBox获得许多项目的情况下,我将整个日志转储到文件。