如何确保,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,但您可以更好地实现它,例如通过提供方法
ShowNextMessage
和IsMessageAvailable
,然后视图可以决定何时准备好显示新消息并请求它。 这将是一个更好的设计。 即使用户可以更快地隐藏某些消息,您也只需要将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获得许多项目的情况下,我将整个日志转储到文件。