Как мне обновить графический интерфейс из другого потока? - PullRequest
1279 голосов
/ 19 марта 2009

Какой самый простой способ обновить Label из другого потока?

У меня Form на thread1, и с этого я запускаю другой поток (thread2). Пока thread2 обрабатывает некоторые файлы, я бы хотел обновить Label на Form с текущим статусом thread2.

Как я могу это сделать?

Ответы [ 47 ]

28 голосов
/ 19 марта 2009

Вам нужно будет вызвать метод в потоке GUI. Вы можете сделать это, вызвав Control.Invoke.

Например:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}
26 голосов
/ 31 мая 2013

Ничего из вышеперечисленного в предыдущих ответах не требуется.

Вам нужно взглянуть на WindowsFormsSynchronizationContext:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}
26 голосов
/ 24 августа 2010

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

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

Этот подход позволяет избежать операции маршалинга, необходимой при использовании методов ISynchronizeInvoke.Invoke и ISynchronizeInvoke.BeginInvoke. Нет ничего плохого в использовании техники маршалинга, но есть несколько предостережений, о которых вам необходимо знать.

  • Убедитесь, что вы не звоните BeginInvoke слишком часто, иначе это может привести к переполнению модуля сообщений.
  • Вызов Invoke в рабочем потоке является блокирующим вызовом. Он временно приостановит работу, выполняемую в этой теме.

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

  • Пользовательский интерфейс и рабочие потоки остаются слабо связанными, в отличие от подхода Control.Invoke или Control.BeginInvoke, который тесно связывает их.
  • Поток пользовательского интерфейса не будет препятствовать продвижению рабочего потока.
  • Рабочий поток не может доминировать во время, которое поток пользовательского интерфейса тратит на обновление.
  • Интервалы, с которыми пользовательский интерфейс и рабочие потоки выполняют операции, могут оставаться независимыми.
  • Рабочий поток не может переполнить насос сообщений потока пользовательского интерфейса.
  • Поток пользовательского интерфейса определяет, когда и как часто пользовательский интерфейс обновляется.
22 голосов
/ 17 декабря 2011

Salvete! Поискав этот вопрос, я обнаружил, что ответы FrankG и Oregon Ghost являются самыми простыми и полезными для меня. Теперь я кодирую в Visual Basic и запускаю этот фрагмент через конвертер; так что я не совсем понимаю, как это получается.

У меня есть диалоговая форма с именем form_Diagnostics,, которая имеет поле richtext, называемое updateDiagWindow,, которое я использую для отображения журналов. Мне нужно было иметь возможность обновить его текст из всех тем. Дополнительные строки позволяют окну автоматически прокручиваться до самых новых строк.

Итак, теперь я могу обновить отображение одной строкой из любой точки всей программы так, как вы думаете, она будет работать без каких-либо потоков:

  form_Diagnostics.updateDiagWindow(whatmessage);

Основной код (поместите его внутри кода класса вашей формы):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion
22 голосов
/ 02 марта 2011

Это похоже на решение выше с использованием .NET Framework 3.0, но оно решило проблему поддержки безопасности во время компиляции .

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

Для использования:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

Компилятор потерпит неудачу, если пользователь передаст неверный тип данных.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
20 голосов
/ 16 сентября 2011

Это в моем C # 3.0 варианте решения Яна Кемпа:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

Вы называете это так:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. Добавляет нулевую проверку к результату "as MemberExpression".
  2. Улучшает статическую безопасность типов.

В противном случае оригинал - очень хорошее решение.

20 голосов
/ 24 августа 2010

Для многих целей это так просто:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()" - это метод уровня графического интерфейса в форме (this), который может изменять столько элементов управления, сколько вы хотите. Вызовите updateGUI () из другого потока. Параметры могут быть добавлены для передачи значений или (возможно, быстрее) использовать переменные области видимости класса с блокировками для них по мере необходимости, если есть какая-либо вероятность столкновения между потоками, обращающимися к ним, что может вызвать нестабильность. Используйте BeginInvoke вместо Invoke, если поток без графического интерфейса критичен ко времени (помня о предупреждении Брайана Гидеона).

19 голосов
/ 28 мая 2012
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

Обратите внимание, что BeginInvoke() предпочтительнее, чем Invoke(), потому что это менее вероятно, приведет к взаимоблокировкам (однако, это не проблема здесь, просто назначая текст метке):

При использовании Invoke() вы ожидаете возврата метода. Теперь может случиться так, что вы делаете что-то в вызываемом коде, который должен будет ожидать поток, что может быть неочевидно, если он скрыт в некоторых вызываемых вами функциях, что само по себе может происходить косвенно через обработчики событий. Таким образом, вы будете ждать нити, нить будет ждать вас, и вы зашли в тупик.

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

19 голосов
/ 24 октября 2012

Когда я столкнулся с той же проблемой, я обратился за помощью к Google, но вместо того, чтобы дать мне простое решение, он больше смутил меня, приведя примеры MethodInvoker и бла-бла-бла. Поэтому я решил решить это самостоятельно. Вот мое решение:

Создайте делегата следующим образом:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

Вы можете вызвать эту функцию в новом потоке, как это

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Не путайте с Thread(() => .....). Я использую анонимную функцию или лямбда-выражение, когда я работаю над потоком. Чтобы уменьшить количество строк кода, вы также можете использовать метод ThreadStart(..), который я не должен здесь объяснять.

16 голосов
/ 11 января 2016

Просто используйте что-то вроде этого:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...