2014-06-24 20 views
6

Nazwa pytania brzmi: "Aktualizacja GUI od pracownika w tle", ale poprawna nazwa tego świata brzmi: "Aktualizacja GUI z tła roboczego LUB raportowanie wielu zmiennych (innych niż liczba całkowita) od pracownika tła "Aktualizacja GUI z działającego w tle pracownika

Proszę pozwolić mi wyjaśnić moją sytuację. W programie mam pracownika działającego w tle, który analizuje informacje. W wyniku tej analizy - elementy GUI formularza powinny być wypełnione niezbędnymi danymi. W GUI chciałbym zaktualizować

  • 2 datagridviews
  • 1 listbox
  • 5 etykiet

Jak rozumiem - mogę tylko natywnie raport 1 int wartość poprzez ReportProgress() metody pracownik w tle.

Więc pytanie - w jaki sposób mogę przekazać List<> (+ kilka innych zmiennych: string, int) Via ReportProgress()? Zasadniczo - chcę zaktualizować GUI z informacją, ale "1 liczba całkowita" po prostu nie zrobi. Tak więc powinno być możliwe przekazanie wielu zmiennych przez ReportProgress() LUB mogę użyć Invoke z wewnątrz samego BackgroundWorker, aby zaktualizować GUI .. Osobiście nie podoba mi się podejście Invoke ... Jaka jest Twoja opinia?

Oto mój kod (patrz komentarze):

private void button9_Click(object sender, EventArgs e) // start BW 
    { 
     bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); 
     bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted); 
     bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged); 

     bw.WorkerReportsProgress = true; 
     bw.WorkerSupportsCancellation = true; 

     bw.RunWorkerAsync(10); 
    } 

    private void button10_Click(object sender, EventArgs e) // cancel BW 
    { 
     bw.CancelAsync(); 
    } 

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     int count = (int)e.Argument; 
     for (int i = 1; i <= count; i++) 
     { 
      if (bw.CancellationPending) 
      { 
       e.Cancel = true; 
       break; 
      } 

      List<List<string>> list_result = new List<List<string>>(); 
      list_result = Proccess(); 

      bw.ReportProgress(list_result.Count()); // right now I can only return a single INT 

      /////////// UPDATE GUI ////////////// 
      // change datagridview 1 based on "list_result" values 
      // change datagridview 2 
      // change listbox 
      // change label 1 
      // change label ..   

      Thread.Sleep(20000); 
     } 

     MessageBox.Show("Complete!"); 
     e.Result = sum; 
    } 

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     prog_count++; 
     listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + "."); 
    } 
+0

Co nie podoba ci się w "Wywołuj"? –

+3

ReportProgress ma również obiekt [UserState] (http://msdn.microsoft.com/en-us/library/a3zbdb1t (v = vs.110) .aspx), który można przekazać. – LarsTech

+1

@Savanna to nie to, że go nienawidzę .. Szukam alternatywnego/lepszego punktu widzenia do mojego obecnego rozwiązania;) Może przejście od BW do "nowej Nici" będzie rozwiązaniem .. Osobiście nie lubię wywoływać, ponieważ muszę zrobić sprawdzanie "jeśli InvokeRequired, to ...", ale znowu - jeśli nie ma alternatywy, będę musiał trzymać się tego jednego – Alex

Odpowiedz

6

Jest to parametr UserState Dzwoniąc ReportProgress.

var list_result = new List<List<string>>(); 

new backgroundWorker1.ReportProgress(0, list_result); 

Parametr typu jest object więc będziesz musiał oddać go z powrotem do typu trzeba:

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    var userState = (List<List<string>>)e.UserState; 
} 

Tricky problem z tym jest, w jaki sposób określić, czy jesteś odsyłając List, lub listę list, lub pojedynczy ciąg, liczbę itp. Musisz przetestować każdą możliwość w wydarzeniu ProgressChanged.

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    var myList = e.UserState as List<List<string>>; 
    if (myList != null) 
    { 
     // use list 
     return; 
    } 

    int myNumber; 
    if (Int32.TryParse(e.UserState.ToString(), out myNumber)) 
    { 
     // use number 
     return; 
    } 

    var myString = e.UserState.ToString(); 
    // use string 
} 

Alternatywnie, można utworzyć klasę, która posiada wszystkie wartości potrzebne (lub użyć Tuple), uruchomić wszystko w tle, aby wypełnić tę klasę, a następnie przekazać, że do zdarzenia RunWorkerCompleted i zaktualizuj UI wszystko od razu.

+2

Twój pierwszy fragment tworzy zmienną o nazwie 'list_result', ale zdasz coś niezdefiniowanego o nazwie 'myList' dla metody" ReportProgress "działającej w tle. Czy chciałeś przekazać 'list_result' lub czy odszedłeś od linii? –

+0

@TonyVitabile Dzięki, był to tylko błąd kopiowania/wklejania. Napisałem to w jeden sposób, a następnie zmieniono nazwy zmiennych, aby pasowały do ​​Alexa. –

+0

lub ... możemy użyć "e.ProgressPercentage" do wykrycia typu obiektu. Jeśli (e.ProgressPercentage = 1) => object jest ciągiem znaków. "2" -> Obj jest int, więc rób inne rzeczy, "3" -> ... – Alex

0

Podstawowy wzór do aktualizacji UI z innego wątku jest:

If controlItem.InvokeRequired Then 
    controlItem.Invoke(Sub() controlItem.Text = textUpdateValue) 
Else 
    controlItem.Text = textUpdateValue 
End If 

Można zaktualizować listę kontroli bez konieczności przekazać coś poprzez ReportProgress. Jeśli chcesz zaktualizować kontrolę z poziomu wątku, nie uważam, że musisz sprawdzić InvokeRequired, ponieważ będzie on zawsze wymagany. Najlepszą praktyką może być jednak wyeksponowanie ustawienia kontroli za pomocą właściwości, a następnie wykonanie pełnego sprawdzenia, aby można było wywołać ją z dowolnego miejsca.

4

Napisałem dwie bardzo proste metody, które umożliwiają wywoływanie kodu (tylko w razie potrzeby) i wystarczy napisać kod tylko raz. Myślę, że to sprawia, Invoke znacznie bardziej przyjazne w obsłudze:

1) BeginInvoke

public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action) 
{ 
    if (control.InvokeRequired) 
     control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); })); 
    else 
     action(); 
} 

2) Wywołanie

public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action) 
{ 
    if (control.InvokeRequired) 
     control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); })); 
    else 
     action(); 
} 

może być wywołana tak:

SafeInvoke(textbox,() => { textbox.Text = "text got changed"; }); 

Alternatywnie cię mógł po prostu

System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false; 

(które zmienia tylko zachowanie w trybie debugowania btw) i sprawdź, czy napotkasz problemy.
Najczęściej nie robisz tego. Zajęło mi trochę czasu, aby znaleźć przypadki bardzo Invoke jest naprawdę wymagane, aby rzeczy nie zostały popsute.

+2

[Czy bezpieczne jest ustawienie CheckForIllegalCrossThreadCalls na false?] (Http://stackoverflow.com/a/13345622/719186) – LarsTech