2010-12-13 2 views
9

У меня есть кнопка в моей форме Windows, которая вызывает метод RunWorkerAsync(), это, в свою очередь, выполняет действие, которое затем обновляет ListBox в той же форме.Доступ к управлению пользовательским интерфейсом из BackgroundWorker Thread

После того, как событие DoWork закончил Поручаю результат для события (Какой список), я обрабатываю событие RunWorkerCompleted(), а затем выполнить следующий код, чтобы обновить мой Listbox

alt text

который называет это:

alt text

(Извиняюсь, форматирование кода не будет работать)

Теперь, когда я запустить приложение и нажать на кнопку обновления появится следующее исключение:

alt text

Как бы я обойти это?

Edit:

Исключение брошено на folowing заявление в это происходит в методе DoWork где я очистить содержимое, чтобы сохранить список в актуальном состоянии;

listBoxServers.Items.Clear();

+0

Является ли это WPF или Windows Forms? И на какой строке вы получаете это исключение? – decyclone

+0

@decyclone Я обновил свой вопрос с дополнительной информацией –

+0

Я рекомендую ответ Криса. – decyclone

ответ

10

Вот отрывок, который я нахожу очень удобно:

public static void ThreadSafe(Action action) 
{ 
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, 
     new MethodInvoker(action)); 
} 

Вы можете передать его любой делегат Action типа или просто лямбда, как это:

ThreadSafe(() => 
{ 
    [your code here] 
}); 

или

ThreadSafe(listBoxServers.Items.Clear); 
+1

Разве это не WPF? Поддерживает ли WinForms диспетчер? –

+0

'Dispatcher' - это класс WPF, но этот код будет работать с WinForms. – Nobody

+0

По какой-то причине диспетчер не распознается IntelliSense, у меня включен оператор System.Threading. –

1

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

Создайте метод, который будет вызывать UpdateServerDetails в главном потоке, как это:

private void DispatchServerDetails(List<ServerDetails> details) 
{ 
    Action<List<ServerDetails>> action = UpdateServerDetails; 
    Dispatcher.Invoke(action) 
} 

, а затем вызвать DispatchServerDetails вместо UpdateServerDetails.

Некоторые предостережений:
-Этот лучше работает в приложениях WPF для WinForms, вам придется прыгать через несколько обручей, или вы можете использовать InvokeRequired обновление
-The UI еще синхронные, так что если UpdateServerDetails делает много работы, он заблокирует поток пользовательского интерфейса (не ваш случай, просто чтобы быть в безопасности).

+0

Можете ли вы привести пример? –

+0

@Jamie http://stackoverflow.com/questions/975087/wpf-dispatcher-and-running-it-in-background – jan

+1

Разве это не класс WPF? – CodesInChaos

9

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

listBoxServers.BeginInvoke(
    (Action) 
    (() => listBoxServers.Items.Clear())); 
+0

Типичная проблема Лиспа, кажется, отсутствует. –

+0

Спасибо, Ханс, я добавил пропавший парен. –

13

Вы можете позвонить по телефону Invoke в поле списка, но на форме.Для WinForms приложений я использую что-то вроде:

... 
this.Invoke((MethodInvoker)delegate() 
{ 
    // Do stuff on ANY control on the form. 
}); 
... 

В зависимости от версии .NET, возможно, придется объявить делегат MethodInvoker себя

public delegate void MethodInvoker(); 

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

1

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

Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?

Он обрабатывает случаи, когда не требуется Invoke, вызывается из разных потоков, ручка или не создается, etcetcetc. Его можно легко изменить, чтобы быть SafeInvoke() и SafeBeginInvoke(), если вы не являетесь поклонником параметра bool.

(Включено здесь для вашего удобства:

/// Usage: 
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false); 

// or 
string taskName = string.Empty; 
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true); 


/// <summary> 
/// Execute a method on the control's owning thread. 
/// </summary> 
/// <param name="uiElement">The control that is being updated.</param> 
/// <param name="updater">The method that updates uiElement.</param> 
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater. False to allow asynchronous execution if the call is marshalled 
/// from a non-GUI thread. If the method is called on the GUI thread, 
/// execution is always synchronous.</param> 
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) 
{ 
    if (uiElement == null) 
    { 
     throw new ArgumentNullException("uiElement"); 
    } 

    if (uiElement.InvokeRequired) 
    { 
     if (forceSynchronous) 
     { 
      uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); 
     } 
     else 
     { 
      uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); 
     } 
    } 
    else 
    { 
     if (!uiElement.IsHandleCreated) 
     { 
      // Do nothing if the handle isn't created already. The user's responsible 
      // for ensuring that the handle they give us exists. 
      return; 
     } 

     if (uiElement.IsDisposed) 
     { 
      throw new ObjectDisposedException("Control is already disposed."); 
     } 

     updater(); 
    } 
} 
0

Я просто выяснял более простой способ без использования Invoke:

int fakepercentage = -1; 
//some loop here......if no loop exists, just change the value to something else 
if (fakepercentage == -1) 
{ 
    fakepercentage = -2; 
} 
else 
{ 
    fakepercentage = -1; 
} 
backgroundworker1.ReportProgress(fakepercentage); 

Затем в backgroundworker1_ProgressChanged (объект отправителя, ProgressChangedEventArgs е):

if (e.ProgressPercentage < 0) 
{ 
    //access your ui control safely here 
} 
Смежные вопросы