Принудительное обновление графического интерфейса из потока - PullRequest
69 голосов
/ 01 сентября 2009

В WinForms, как принудительно обновить пользовательский интерфейс из потока пользовательского интерфейса?

То, что я делаю, примерно так:

label.Text = "Please Wait..."
try 
{
    SomewhatLongRunningOperation(); 
}
catch(Exception e)
{
    label.Text = "Error: " + e.Message;
    return;
}
label.Text = "Success!";

Текст метки не устанавливается на «Пожалуйста, подождите ...» перед операцией.

Я решил эту проблему, используя другой поток для операции, но он становится проблематичным, и я хотел бы упростить код.

Ответы [ 11 ]

92 голосов
/ 02 апреля 2010

Сначала я удивлялся, почему ОП еще не пометил один из ответов как ответ, но после того, как я попробовал его сам, но все еще не получилось, я покопался немного глубже и обнаружил, что в этом вопросе гораздо больше сначала предположил.

Лучшее понимание можно получить, прочитав аналогичный вопрос: Почему не контролируется процесс обновления / обновления в середине

Наконец, для записи я смог обновить свой лейбл, выполнив следующие действия:

private void SetStatus(string status) 
{
    lblStatus.Text = status;
    lblStatus.Invalidate();
    lblStatus.Update();
    lblStatus.Refresh();
    Application.DoEvents();
}

Хотя, насколько я понимаю, это далеко не элегантный и правильный подход к этому. Это взлом, который может или не может работать в зависимости от того, насколько занят поток.

14 голосов
/ 01 сентября 2009

Вызовите label.Invalidate, а затем label.Update() - обычно обновление происходит только после выхода из текущей функции, но вызов Update заставляет его обновляться в определенном месте кода. От MSDN :

Метод Invalidate определяет, что будет окрашено или перекрашено. Метод Update определяет, когда происходит рисование или перекрашивание. Если вы используете методы Invalidate и Update вместе, а не вызываете Refresh, то, что перерисовывается, зависит от того, какую перегрузку Invalidate вы используете. Метод Update просто заставляет немедленно нарисовать элемент управления, но метод Invalidate управляет тем, что рисуется при вызове метода Update.

13 голосов
/ 01 сентября 2009

Позвоните Application.DoEvents() после установки метки, но вместо этого вы должны выполнять всю работу в отдельном потоке, чтобы пользователь мог закрыть окно.

4 голосов
/ 07 августа 2013

Я только что наткнулся на ту же проблему и нашел некоторую интересную информацию, и я хотел вставить свои два цента и добавить ее сюда.

Прежде всего, как уже упоминали другие, длительные операции должны выполняться потоком, который может быть фоновым работником, явным потоком, потоком из пула потоков или (начиная с .Net 4.0) задачей: Stackoverflow 570537: обновлять метки при обработке в окнах , чтобы пользовательский интерфейс оставался отзывчивым.

Но для коротких задач нет необходимости в многопоточности, хотя это, конечно, не повредит.

Я создал winform с одной кнопкой и одной меткой для анализа этой проблемы:

System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
{
  label1->Text = "Start 1";
  label1->Update();
  System::Threading::Thread::Sleep(5000); // do other work
}

Мой анализ перешагнул через код (используя F10) и увидел, что произошло. И после прочтения этой статьи Многопоточность в WinForms я обнаружил кое-что интересное. В статье в нижней части первой страницы говорится, что поток пользовательского интерфейса не может перерисовать пользовательский интерфейс до тех пор, пока текущая выполняемая функция не завершится и Windows не помечает окно как «не отвечающее» через некоторое время. Я также заметил, что в моем тестовом приложении сверху, хотя шагая через него, но только в определенных случаях.

(Для следующего теста важно не устанавливать полноэкранный режим Visual Studio, вы должны одновременно видеть рядом с ним свое маленькое окно приложения. Вам не нужно переключаться между окном Visual Studio для отладка и окно вашего приложения, чтобы увидеть, что происходит. Запустите приложение, установите точку останова на label1->Text ..., поместите окно приложения рядом с окном VS и поместите курсор мыши на окно VS.)

  1. Когда я нажимаю один раз на VS после запуска приложения (чтобы поместить фокусы туда и включить пошаговое управление) и шагаю по нему БЕЗ перемещения мыши, устанавливается новый текст и метка обновляется в обновлении () function. Это означает, что пользовательский интерфейс перекрашивается явно.

  2. Когда я перехожу через первую строку, затем многократно двигаю мышь и щелкаю куда-то, затем шагаю дальше, вероятно, устанавливается новый текст и вызывается функция update (), но пользовательский интерфейс не обновляется / перекрашивается и старый текст остается там до завершения функции button1_click (). Вместо перекраски окно помечается как "не отвечающее"! Также не помогает добавление this->Update(); для обновления всей формы.

  3. Добавление Application::DoEvents(); дает интерфейсу возможность обновлять / перекрашивать. В любом случае вы должны позаботиться о том, чтобы пользователь не мог нажимать кнопки или выполнять другие операции с пользовательским интерфейсом, которые не разрешены! Поэтому: Старайтесь избегать DoEvents ()! , лучше используйте многопоточность (что, я думаю, довольно просто в .Net).
    Но ( @ Jagd, 2 апреля 2010 года в 19: 25 ) вы можете опустить .refresh() и .invalidate().

Мои объяснения следующие: AFAIK winform все еще использует функцию WINAPI. Также Статья MSDN о методе System.Windows.Forms Control.Update относится к функции WINAPI WM_PAINT. В статье MSDN о WM_PAINT в первом предложении говорится, что команда WM_PAINT отправляется системой только тогда, когда очередь сообщений пуста. Но поскольку очередь сообщений уже заполнена во втором случае, она не отправляется, и поэтому метка и форма заявки не перекрашиваются.

<> joke> Вывод: так что вам просто нужно запретить пользователю использовать мышь ;-) <> / joke>

3 голосов
/ 04 декабря 2009

вы можете попробовать это

using System.Windows.Forms; // u need this to include.

MethodInvoker updateIt = delegate
                {
                    this.label1.Text = "Started...";
                };
this.label1.BeginInvoke(updateIt);

Посмотрите, работает ли оно.

2 голосов
/ 16 апреля 2013

После обновления пользовательского интерфейса запустите задачу для выполнения с длительной операцией:

label.Text = "Please Wait...";

Task<string> task = Task<string>.Factory.StartNew(() =>
{
    try
    {
        SomewhatLongRunningOperation();
        return "Success!";
    }
    catch (Exception e)
    {
        return "Error: " + e.Message;
    }
});
Task UITask = task.ContinueWith((ret) =>
{
    label.Text = ret.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());

Это работает в .NET 3.5 и более поздних версиях.

1 голос
/ 28 ноября 2018

Если вам нужно только обновить пару элементов управления, достаточно .update ().

btnMyButton.BackColor=Color.Green; // it eventually turned green, after a delay
btnMyButton.Update(); // after I added this, it turned green quickly
1 голос
/ 04 октября 2016

Думаю, у меня есть ответ, извлеченный из вышесказанного и небольшой эксперимент.

progressBar.Value = progressBar.Maximum - 1;
progressBar.Maximum = progressBar.Value;

Я попытался уменьшить значение, и экран обновился даже в режиме отладки, но это не сработало бы для установки progressBar.Value на progressBar.Maximum, потому что вы не можете установить значение индикатора выполнения выше максимального, поэтому я сначала установил * От 1006 * до progressBar.Maximum - 1, затем установите progressBar.Maxiumum равным progressBar.Valu e. Говорят, есть несколько способов убить кошку. Иногда я хотел бы убить Билла Гейтса или кого бы то ни было сейчас: о).

С этим результатом мне даже не потребовалось Invalidate(), Refresh(), Update() или что-либо делать с индикатором выполнения, его контейнером Panel или родительской формой.

1 голос
/ 01 сентября 2009
1 голос
/ 01 сентября 2009

Очень заманчиво хотеть «исправить» это и заставить обновление пользовательского интерфейса, но лучшее решение - это сделать это в фоновом потоке, а не связывать поток пользовательского интерфейса, чтобы он все еще мог реагировать на события.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...