从另一个线程中操作UI元素

本文关键字:UI 元素 操作 另一个 线程 | 更新日期: 2023-09-27 17:50:27

我正在尝试在WinForms c#应用程序中启动一个单独的线程,该线程控制进度条(选框)。问题是,当我试图设置酒吧可见它只是什么也不做,我已经尝试了许多形式的调用,但他们似乎没有帮助。

下面的方法progressBarCycle是从一个单独的线程调用的。

    BackgroundWorker backgroundWorker = new BackgroundWorker();
    public void progressBarCycle(int duration)
    {
        backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.RunWorkerAsync(duration);
    }
    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        worker.ReportProgress(0);
        DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument);
        while (DateTime.Now <= end)
        {
            System.Threading.Thread.Sleep(1000);
        }
    }
    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = false;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
    }
    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = true;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
    }

我错过了什么明显的东西吗?注释区是我尝试过的其他东西

从另一个线程中操作UI元素

ProgressChanged已经在UI线程引发(通过sync-context);你的ProgressChanged不需要做Invoke -它可以直接操纵UI(相比之下,DoWork绝对可以而不是做到这一点)。也许真正的问题是你没有在循环中执行任何worker.ReportProgress(...) ,所以它只在开始时发生一次。

下面是一个完整的例子:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using (var worker = new BackgroundWorker {
            WorkerReportsProgress = true })
        using (var progBar = new ProgressBar {
            Visible = false, Step = 1, Maximum = 100,
            Dock = DockStyle.Bottom })
        using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" })
        using (var form = new Form { Controls = { btn, progBar } })
        {
            worker.ProgressChanged += (s,a) => {
                progBar.Visible = true;
                progBar.Value = a.ProgressPercentage;
            };
            worker.RunWorkerCompleted += delegate
            {
                progBar.Visible = false;
            };
            worker.DoWork += delegate
            {
                for (int i = 0; i < 100; i++)
                {
                    worker.ReportProgress(i);
                    Thread.Sleep(100);
                }
            };
            btn.Click += delegate
            {
                worker.RunWorkerAsync();
            };
            Application.Run(form);
        }
    }
}
  1. 从UI线程运行progressBarCycleRunWorkerAsync将创建新线程
  2. backgroundWorker_ProgressChanged中只需调用progressBar1.Visible = true;。不需要Invoke .
  3. 最好添加progressBar1.Refresh();

需要注意的另一种可能性是进度条正在UI线程上运行。为了显示进度条并重新绘制自己以显示新的进度量,UI线程必须自由运行,在主应用程序循环中处理windows消息。

所以如果你启动你的后台工作线程,但你的UI线程坐在一个繁忙的等待循环等待它完成,或者去做其他工作负载,那么它不会处理窗口消息,你的进度条将是"无响应"。你需要释放UI线程,这样更新仍然会发生(即从事件处理程序返回并允许UI继续正常运行)。

这样做的危险在于,如果UI处于活动状态,那么用户仍然可以与它交互。因此,你必须编写UI来了解后台工作线程何时处于活动状态,并正确处理这种情况(问题可能包括:允许用户在后台工作线程已经运行时再次启动后台工作线程,UI试图在工作线程忙于更新信息时显示信息,用户决定在后台工作线程繁忙时加载新文档或退出,等等)。对此的两个主要解决方案是将每个UI包裹在一个保护罩中,以防止在后台工作运行时启动任何危险的东西(如果你有很多控件要以这种方式包裹,这可能是很多工作;这很容易犯一个错误,让一个错误滑过)或离开UI"不受保护",但添加一个IMessageFilter,停止所有"危险"的用户交互(点击和按键)通过抑制他们的传入窗口消息(WM_KEYPRESS等),而后台处理是活跃的。