Управление элементами пользовательского интерфейса из другого потока

Я пытаюсь создать отдельный поток в приложении WinForms С# для запуска фонового рабочего, который управляет ProgressBar (шатер). Проблема в том, что когда я пытаюсь сделать панель видимой, она просто ничего не делает, и я пробовал много форм Invoke, но они, похоже, не помогают.

Следующий метод 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;
    }

Я пропустил что-то очевидное здесь? Разделы комментариев - это другие вещи, которые я пробовал.


person UncleDave    schedule 31.12.2012    source источник


Ответы (3)


ProgressChanged уже вызывается в потоке пользовательского интерфейса (через контекст синхронизации); вашему ProgressChanged не нужно делать это Invoke — он может напрямую манипулировать пользовательским интерфейсом (в отличие от этого, 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);
        }
    }
}
person Marc Gravell    schedule 31.12.2012
comment
Я вставил ProgressChanged и RunWorkerCompleted исключительно для того, чтобы посмотреть, решит ли это мою проблему. Когда весь этот код был включен в DoWork, он делал то же самое. Состояние Visible просто никогда не изменится, с вызовами или без них. - О, и, вероятно, стоит упомянуть, что это Бегущая строка, поэтому я не собираюсь использовать отчеты о проделанной работе. - person UncleDave; 31.12.2012
comment
@UncleDave, тогда это звучит так, будто индикатор выполнения находится внутри другого элемента управления (возможно, панели), который не виден. - person Marc Gravell; 31.12.2012
comment
Индикатор выполнения находится в форме, в которой выполняется этот код, которая также является стартовой формой приложения. - person UncleDave; 31.12.2012
comment
Вы не только не вызываете ReportProgress внутри цикла DoWork, в событии ProgressChanged нет приращения значения индикатора выполнения. - person Steve; 31.12.2012
comment
ReportProgress вызывается в DoWork один раз, и это все, что ему нужно — полоса представляет собой бегущую строку, поэтому ее не нужно обновлять в зависимости от хода выполнения. Проблема в том, что я не могу изменить свойство visible. - person UncleDave; 31.12.2012
comment
@UncleDave, тебе нужно проверить панели до самого верха. Проверяйте .Visible каждого .Parent, пока не дойдете до самой формы. Установка его видимым работает нормально: см. пример в редактировании - person Marc Gravell; 31.12.2012
comment
Виден каждый родитель, может ли это быть связано с вызовом progressBarCycle из другого потока? И затем этот поток (не основной поток) вызывает фонового рабочего. - person UncleDave; 31.12.2012

  1. Запустите progressBarCycle из потока пользовательского интерфейса. RunWorkerAsync создаст новую тему для вас.
  2. В backgroundWorker_ProgressChanged просто вызовите progressBar1.Visible = true;. Нет необходимости в Invoke.
  3. Лучше также добавить progressBar1.Refresh(); .
person ispiro    schedule 31.12.2012

Еще одна возможность, о которой следует знать, заключается в том, что индикатор выполнения работает в вашем потоке пользовательского интерфейса. Чтобы индикатор выполнения отображался и перерисовывался, чтобы показывать новые значения прогресса, поток пользовательского интерфейса должен работать свободно, обрабатывая сообщения Windows в основном цикле приложения.

Поэтому, если вы запускаете свой фоновый рабочий поток, но затем ваш поток пользовательского интерфейса находится в занятом цикле ожидания, ожидая его завершения, или отключается и выполняет множество другой работы, тогда он не будет обрабатывать сообщения Windows, и ваш индикатор выполнения будет "неотзывчивый". Вам нужно освободить поток пользовательского интерфейса, чтобы это обновление все еще происходило (т. е. возвращалось из вашего обработчика событий и позволяло пользовательскому интерфейсу продолжать работать в обычном режиме).

Опасность этого заключается в том, что если пользовательский интерфейс активен, то пользователь все равно может взаимодействовать с ним. Поэтому вам нужно написать пользовательский интерфейс, чтобы знать, когда фоновый рабочий процесс активен, и правильно обрабатывать ситуацию (проблемы могут включать: разрешение пользователю снова запускать фоновый рабочий процесс, когда он уже запущен, пользовательский интерфейс, пытающийся отобразить информацию во время рабочего процесса). поток усердно обновляет его, пользователь решает загрузить новый документ или выйти, пока фоновый рабочий процесс занят, и т. д.). Два основных решения этой проблемы заключаются в том, чтобы обернуть каждый бит пользовательского интерфейса защитным экраном, который предотвращает запуск чего-либо опасного во время фоновой работы (это может быть много работы, если у вас есть много элементов управления, которые можно обернуть таким образом, и легко сделать ошибку, которая позволит пропустить ошибку) или оставить пользовательский интерфейс «незащищенным», но добавить IMessageFilter, который останавливает все «опасные» взаимодействия с пользователем (щелчки и нажатия клавиш), подавляя их входящие сообщения Windows (WM_KEYPRESS и т. д.), в то время как фоновая обработка активна.

person Jason Williams    schedule 31.12.2012