从后台工作线程访问UI的问题

6

我正在将一个项目从winforms转换到WPF。当我的代码基于WinForms时,我使用(this.InvokeRequired)来检查线程是否具有访问权限。现在我使用以下基于我的Mainform的代码:

    // this is the delegate declaration to Allow Background worker thread to write to Log Output
    delegate void LogUpdateCallBack(String LogMessage);

    // method to update the Log Window from the Background Thread
    public void LogUpdate(String LogMessage)
    {
        Console.WriteLine("Entering");
        if (!Application.Current.Dispatcher.CheckAccess())
        {
            Console.WriteLine("Thread doesn't have UI access");
            LogUpdateCallBack callback = new LogUpdateCallBack(LogUpdate);
            Application.Current.Dispatcher.Invoke(callback, LogMessage);
        }
        else
        {
            Console.WriteLine("Thread has UI access");
            listBox_Output.Items.Add(LogMessage);
            Console.WriteLine(LogMessage);
            // listBox_Output.TopIndex = listBox_Output.Items.Count - 1;
        }
        Console.WriteLine("Exiting");
    }

我遇到的问题是Listbox没有更新。没有错误或异常,我尝试更新其他UI控件。LogMessage正在写入输出窗口,所以我很困惑。
这是样例控制台输出:
Entering
Thread doesn't have UI access
Entering
Thread has UI access
My LogMessage is output here 
Exiting
Exiting
Entering
Thread doesn't have UI access
Entering
Thread has UI access
My LogMessage is output here 
Exiting
Exiting

我尝试更新其他UI控件,只是为了检查是否与我的Listbox有问题,但没有成功。

除了切换到CheckAccess()之外,在新的WPF代码中我所做的唯一其他重大更改是将在后台工作器中运行的所有代码都基于另一个类。我不确定这是否可能是问题的一部分?

--

@JonRaynor

我尝试了你的想法:

Application.Current.Dispatcher.BeginInvoke(new LogUpdateCallBack(LogUpdate), LogMessage)

然而,我的列表框仍未更新,如果我输出
Console.WriteLine(listBox_Output);

我看到列表框数组在增长:

System.Windows.Controls.ListBox Items.Count:2020
System.Windows.Controls.ListBox Items.Count:2021
System.Windows.Controls.ListBox Items.Count:2022
System.Windows.Controls.ListBox Items.Count:2023
System.Windows.Controls.ListBox Items.Count:2024
System.Windows.Controls.ListBox Items.Count:2025

但是表单没有变化。这非常令人困惑!

Listbox上有任何数据绑定吗?在Page_Load事件中使用Items.Add("test")会起作用吗? - H H
没有数据绑定...我正在从代码的其他区域更新ListBox,没有遇到任何困难。我还尝试更新其他UI控件,以防它是由于Listbox问题...但没有成功。 - mrbencowell
还有一件奇怪的事情需要注意...如果我在控制台中输出listBox_Output,我可以看到项目正在被添加(它报告了项目计数),因此似乎我的项目正在被添加,但是UI根本没有更新。我尝试了listBox_Output.InvalidateVisual();但没有效果。 - mrbencowell
我看到的一个不同之处是我正在使用this.Dispatcher,其中“this”是包含列表框的表单/容器,而您正在使用Application.Current。我不确定这是否有所不同。 - Jon Raynor
非常奇怪,我按照您的代码创建了一个新的WPF应用程序。我在网格上放置了一个列表框和一个按钮。然后我从按钮单击事件中简单地生成了一个新线程,并从该线程调用了LogUpdate函数。它起作用了。还有其他事情发生了,我不确定是什么。 - Jon Raynor
3个回答

2

我也刚开始学习WPF,需要从旧的WinForms方式重新学习。在我的屏幕(表单)上,我一直在使用BeginInvoke()和这种语法...

public delegate void WorkCompleted();

        /// <summary>
        /// Marks the end of the progress
        /// </summary>
        private void ProgressComplete()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(new WorkCompleted(ProgressComplete), System.Windows.Threading.DispatcherPriority.Normal, null);
            }
            else
            {
                this.buttonClose.Visibility = Visibility.Visible;
                this.progressBarStatus.IsIndeterminate = false;
            }
        }

谢谢您的建议。我会尝试这种方法,看看是否有所进展。 - mrbencowell

1

我终于解决了这个问题。

我的问题在于,当我从另一个线程和另一个类中调用LogUpdate方法时,我需要将一个引用传递给包含列表框的主窗体,而不是在辅助类中创建主窗体的新实例。

因此,与其在我的辅助类中有这个声明:

public MainWindow MainForm = new MainWindow();

我需要将表单的引用传递到辅助类中的方法中:
    public void Plot(BackgroundWorker worker, MainWindow MainForm)

0
在WPF中,所有的UI控件都派生自DispatcherObject。这意味着您的ListBox控件本身就是一个具备调度能力的对象。尝试在该对象上调用Dispatcher而不是依赖于窗体本身。从委托中删除参数(因为您已经可以访问LogMessage变量),它看起来会像这样:
public void LogUpdate(string LogMessage)
{
    Console.WriteLine("Entering");
    if (listBox_Output.Dispatcher.CheckAccess())
    {
        listBox_Output.Dispatcher.Invoke(new LogUpdateCallBack(delegate
        {
            listBox_Output.Items.Add(LogMessage);
        }));
        Console.WriteLine(LogMessage);
    }
}

感谢您的建议,但问题在于我误解了在另一个类中声明MainForm实例的含义,我以为这会让我访问主窗体,但它只是创建了一个新的实例..所以当我更新控件时,我没有看到结果。 - mrbencowell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接