I have a WPF application with a MainWindow that will be handling the output of messages. I've set up a list of messages in the Window like this:
我有一个带有MainWindow的WPF应用程序,它将处理消息的输出。我在窗口中设置了一个消息列表,如下所示:
public partial class MainWindow : Window
{
public ConcurrentBag<ViewMessage> MessageList
{
get { return (ConcurrentBag<ViewMessage>)GetValue(MessageListProperty); }
set { SetValue(MessageListProperty, value); }
}
public static readonly DependencyProperty MessageListProperty = DependencyProperty.Register("MessageList", typeof(ConcurrentBag<ViewMessage>), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
}
public void ShowMessage(string header, string message)
{
ViewMessage message = new ViewMessage("Sucess", "Example Message");
MessageList.Add(message);
}
}
The style of the messages is set to fade out after some time (3 seconds), and I want to remove the item from the collection after that time. I don't know if it's possible to do it in XAML only, if it is, how do I do it?
消息的样式设置为在一段时间(3秒)后淡出,我想在此之后从集合中删除该项。我不知道是否可以在XAML中进行,如果是,我该怎么做?
I tried doing it programatically like this:
我尝试以编程方式执行此操作:
public MainWindow()
{
Timer timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(MessageCheckTick);
timer.Start();
//Tried a Thread.Start() as well
}
private void MessageCheckTick(object sender, ElapsedEventArgs e)
{
for (int i = MessageList.Count - 1; i >= 0; i++)
{
ViewMessage message = null;
if (MessageList.TryPeek(out message))
{
if (message.DateMessage >= DateTime.Now.AddMilliseconds(3000))
{ MessageList.TryTake(out message); }
}
}
}
But I get errors of The calling thread cannot access this object because a different thread owns it
. I tried the solutions of similar questions in SO, like adding the ConcurrentBag or using the Dispatcher
, but they didn't work. In the case of the Dispatcher
I can't call the Thread.Sleep
since it's the main thread of the program.
但我得到错误调用线程无法访问此对象,因为不同的线程拥有它。我在SO中尝试了类似问题的解决方案,比如添加ConcurrentBag或使用Dispatcher,但它们不起作用。在Dispatcher的情况下,我无法调用Thread.Sleep,因为它是程序的主线程。
How can I make this work?
我怎样才能做到这一点?
3 个解决方案
#1
DispatcherTimer
is a great idea. I would write the handler akin to this:
DispatcherTimer是一个好主意。我会写处理程序类似于:
// remove outdated
var dtThreshold = DateTime.Now.AddSeconds(-3);
foreach (var el in _localObservable.Where(i => i.DateMessage < dtThreshold).ToList())
_localObservable.Remove(el);
// add new
ViewMessage message = null;
while (MessageList.TryTake(out message))
_localObservable.Add(message);
The observable collection must be manipulated only from the UI thread – you therefore need to keep the ConcurrentBag (to add elements from the non-UI thread).
必须仅从UI线程操纵可观察集合 - 因此需要保留ConcurrentBag(以从非UI线程添加元素)。
Few things to note:
几点注意事项:
-
.ToList() in the remove section: this ensures that all to-be-removed candidates are determined before the first of them is actually removed (in this case, enumeration of the _localObservable ends before it's modified)
删除部分中的.ToList():这确保在实际删除第一个候选者之前确定所有要删除的候选者(在这种情况下,_localObservable的枚举在修改之前结束)
-
No need to do
TryPeek
in advance -TryTake
will take an element and remove it from the bag, giving you the element.不需要事先做TryPeek - TryTake将把一个元素从包中取出,给你元素。
If you don't use ConcurrentBag then each Add
operation must be posted to UI thread. A big number of Add
events may pollute the UI thread, making everything slow & inefficient.
如果您不使用ConcurrentBag,则必须将每个Add操作发布到UI线程。大量的Add事件可能会污染UI线程,使一切变得缓慢而低效。
#2
Just to add this for completeness, there is BindingOperations.EnableCollectionSynchronization
which does more or less what you ask - dispatch modifications to the collection on the correct thread.
If you want to use it go ahead, I've used it successfully many times (and before 4.5 did it manually); just take the time to understand everything that's going on, it definitely helps :)
只是为了完整性添加它,有BindingOperations.EnableCollectionSynchronization,它或多或少地执行你所要求的 - 在正确的线程上调度对集合的修改。如果你想继续使用它,我已成功使用它多次(并且在4.5之前手动完成);花点时间了解正在发生的一切,这绝对有帮助:)
#3
Use DispatcherTimer, which will elapse on the correct thread
使用DispatcherTimer,它将在正确的线程上经过
You don't need to use ConcurrentBag. Looks like it should be an ObservableCollection
您不需要使用ConcurrentBag。看起来它应该是一个ObservableCollection
#1
DispatcherTimer
is a great idea. I would write the handler akin to this:
DispatcherTimer是一个好主意。我会写处理程序类似于:
// remove outdated
var dtThreshold = DateTime.Now.AddSeconds(-3);
foreach (var el in _localObservable.Where(i => i.DateMessage < dtThreshold).ToList())
_localObservable.Remove(el);
// add new
ViewMessage message = null;
while (MessageList.TryTake(out message))
_localObservable.Add(message);
The observable collection must be manipulated only from the UI thread – you therefore need to keep the ConcurrentBag (to add elements from the non-UI thread).
必须仅从UI线程操纵可观察集合 - 因此需要保留ConcurrentBag(以从非UI线程添加元素)。
Few things to note:
几点注意事项:
-
.ToList() in the remove section: this ensures that all to-be-removed candidates are determined before the first of them is actually removed (in this case, enumeration of the _localObservable ends before it's modified)
删除部分中的.ToList():这确保在实际删除第一个候选者之前确定所有要删除的候选者(在这种情况下,_localObservable的枚举在修改之前结束)
-
No need to do
TryPeek
in advance -TryTake
will take an element and remove it from the bag, giving you the element.不需要事先做TryPeek - TryTake将把一个元素从包中取出,给你元素。
If you don't use ConcurrentBag then each Add
operation must be posted to UI thread. A big number of Add
events may pollute the UI thread, making everything slow & inefficient.
如果您不使用ConcurrentBag,则必须将每个Add操作发布到UI线程。大量的Add事件可能会污染UI线程,使一切变得缓慢而低效。
#2
Just to add this for completeness, there is BindingOperations.EnableCollectionSynchronization
which does more or less what you ask - dispatch modifications to the collection on the correct thread.
If you want to use it go ahead, I've used it successfully many times (and before 4.5 did it manually); just take the time to understand everything that's going on, it definitely helps :)
只是为了完整性添加它,有BindingOperations.EnableCollectionSynchronization,它或多或少地执行你所要求的 - 在正确的线程上调度对集合的修改。如果你想继续使用它,我已成功使用它多次(并且在4.5之前手动完成);花点时间了解正在发生的一切,这绝对有帮助:)
#3
Use DispatcherTimer, which will elapse on the correct thread
使用DispatcherTimer,它将在正确的线程上经过
You don't need to use ConcurrentBag. Looks like it should be an ObservableCollection
您不需要使用ConcurrentBag。看起来它应该是一个ObservableCollection