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

luk*_*szk 5 c# data-binding wpf mvvm

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

View通过NotifyPropertyChanged接口获取通知但似乎它有效减慢并且在View绑定新值并绘制之前它会更改次数因此它会错过某些值.

我也尝试绑定View到队列,然后ViewModel可以Enqueue,并View可以通过出队绘制.

不幸的是,发生了另一个问题:之后RaisePropertyChanged(() => queue); View没有被告知它被改变了.

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

你有什么主意吗?

示例代码ViewModel:

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

    public void ProcessNotificationMessage(Message message)
    {   
        MessageOftenBeingChanged = message;
        RaisePropertyChanged(() => MessageOftenBeingChanged );
    }
}
Run Code Online (Sandbox Code Playgroud)

View绑定到MessageOftenBeingChanged.

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

public void ProcessNotificationMessage(Message message)
{
    Messages.Enqueue(message);
    RaisePropertyChanged(() => Messages);
}
Run Code Online (Sandbox Code Playgroud)

View:

<controls:RichTextBoxMonitor Messages="{Binding Messages} 
Run Code Online (Sandbox Code Playgroud)

Control:

public class BindableRichTextBox : RichTextBox
{

    public static readonly DependencyProperty MessagesProperty = DependencyProperty.Register("Messages",
     typeof(ConcurrentQueue<Message>), typeof(BindableRichTextBox ), new FrameworkPropertyMetadata(null, OnQueueChangedChanged));


    public ConcurrentQueue<Message> CyclicMessages
    {
        get { return (ConcurrentQueue<Message>)GetValue(MessagesProperty ); }

        set { SetValue(MessagesProperty , value); }
Run Code Online (Sandbox Code Playgroud)

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

我计划在事件中控制OnQueueChangedChanged出队,并将项目作为段落的新内联绘制.

Woj*_*lik 1

您可以实现生产者-消费者

看看这个简化版本。

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

    public class MyViewModel : INotifyPropertyChanged
    {
        public ConcurrentQueue<string> 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<string>();
            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
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

然而,在您的代码中,应该刷新整个绑定(因为您通知整个集合已更改),但我认为这种机制更智能,它会检查引用是否相等,如果相等则不会发生刷新。可能是一个恶作剧,将其设置为null和返回会刷新它:-)。