从另一个线程中操作UI元素(Manipulating UI elements from within

2019-07-17 17:08发布

我想有一个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;
    }

我失去了一些东西明显在这里吗? 注释部分是其他的事情我已经试过。

Answer 1:

ProgressChanged 已经提出的UI线程(通过同步上下文)上; 您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);
        }
    }
}


Answer 2:

  1. 运行progressBarCycle从UI线程。 RunWorkerAsync将创建新的线程为您服务。
  2. backgroundWorker_ProgressChanged只需拨打progressBar1.Visible = true; 。 有没有必要Invoke
  3. 更好还添加了progressBar1.Refresh();


Answer 3:

需要注意的另一种可能性是,进度条上的UI线程上运行。 为了使进度条来显示和重绘自己展现新的进展金额,UI线程必须运转自如,在主应用程序循环处理窗口消息。

所以,如果你开始你的背景工作者线程,但那么你的UI线程坐在忙等待循环,等待它完成,或熄灭,做其他工作负载,那么就不会被处理窗口的消息,你的进度条会“反应迟钝”。 你需要(从您的事件处理程序,即回报,并允许用户界面继续运行正常)释放UI线程使这一更新仍然发生。

这样做的危险是,如果UI是活动的,那么用户仍然可以与它进行交互。 因此,你必须编写UI要知道,当后台工作是有效的,并妥善处理的情况(问题包括:允许用户再次启动后台工作,而它已经在运行,UI试图显示,而作业人员信息线程正忙于更新它,用户决定加载一个新的文档或退出,而后台工作忙,等)。 这两个主要的解决方案包UI的每一个位在停止后台工作运行时,任何危险正在发起一个保护罩(这可能是一个大量的工作,如果你有很多的控制以这种方式来包装,并很容易犯了一个错误,让错误漏网之鱼)或离开UI“无保护”,但补充说,通过抑制其传入窗口消息将停止所有的“危险”的用户交互(点击和按键)的IMessageFilter(WM_KEYPRESS等),而背景处理是有效的。



文章来源: Manipulating UI elements from within another thread