There is following case:
ViewModel
has an object which changes very fast. (via different threads)
View
gets informed via NotifyPropertyChanged
interface but it seems it works to slow and before View bind new value and draw it then it changes more times therefore It misses some values.
I also tried to bind View
to queue then ViewModel
could Enqueue
it and View
could draw via dequeueing.
Unfortunately another problem occurred: after RaisePropertyChanged(() => queue);
View
is not informed that it was changed.
In such case the implementation of the INotifyPropertyChanged
interface did not worked.
Do you have any idea?
Example code of the 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 );
}
}
View
binds to MessageOftenBeingChanged
.
Another option would be to prepare snapshot as was suggested in comments:
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<Message>), typeof(BindableRichTextBox ), new FrameworkPropertyMetadata(null, OnQueueChangedChanged));
public ConcurrentQueue<Message> CyclicMessages
{
get { return (ConcurrentQueue<Message>)GetValue(MessagesProperty ); }
set { SetValue(MessagesProperty , value); }
but then, unfortunately the RaisePropertyChanged()
method does not trigger that changes happened.
I planned in control in event OnQueueChangedChanged
try dequeueing and just draw items as new Inlines for Paragraph.
You could implement Producer-Consumer.
Look at this simplified version.
RunProducer
is only for tests, in your case ProcessNotificationMessage
will work in a similar way.RunConsumer
is a method which constantly checks for new messages and sets Message
with some delay, otherwise a user wouldn't be able to read it. ShowNextMessage
and IsMessageAvailable
, then the view could decide when is ready to display a new message and request for it. It would be a better design. Even a user could hide some messages faster then, you'd need only to bind ShowNextMessage
to Click
event.Full source code
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
}
In case of empty queue you could use ManualResetMonitor
to avoid unnecessary iterations.
Remarks to your code:
If a collection can be changed then for binding purpose you should use only ObservableCollection<T>
(or something that implements INotifyCollectionChanged
), because it tracks changes and doesn't reload everything.
However in your code a whole binding should be refreshed (as you notified that whole collection has been changed), but I think this mechanism is smarter and checks if references are equal, if so then no refresh occurs. Probably a hax to set it to null
and back would refresh it :-).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With