修改WPF中另一个线程的列表?

时间:2022-09-25 21:01:55

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