2014-06-24 23 views
6

Il nome della domanda è: "Aggiornamento della GUI dall'operatore in background", ma il nome corretto è: "Aggiornamento della GUI dall'operatore in background OPPURE segnalazione di più variabili (diversa da un numero intero) dal lavoratore in background "Aggiornamento della GUI dall'operatore in background

Per favore lasciatemi spiegare la mia situazione. In un programma ho un assistente in background che analizza le informazioni. Come risultato di questa analisi, gli elementi della GUI del modulo devono essere compilati con i dati necessari. In GUI vorrei aggiornare

  • 2 DataGridViews
  • 1 casella di riepilogo
  • 5 etichette

A quanto ho capito - Posso solo nativamente rapporto 1 int valore via ReportProgress() metodo lavoratore in background.

Quindi la domanda è - come posso passare un List<> (+ alcune altre variabili: string, int) tramite ReportProgress()? Fondamentalmente - voglio aggiornare la GUI con le informazioni ma "1 intero" non lo farò. Quindi o dovrebbe essere possibile passare più variabili tramite un ReportProgress() OPPURE posso usare un Invoke all'interno dello stesso BackgroundWorker per aggiornare il GUI .. Personalmente non mi piace l'approccio Invoke ... Qual è la tua opinione?

Ecco il mio codice (vedi i commenti):

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

Cosa non ti piace di 'Invoke'? –

+3

ReportProgress ha anche un oggetto [UserState] (http://msdn.microsoft.com/en-us/library/a3zbdb1t (v = vs.110) .aspx) che è possibile trasmettere. – LarsTech

+1

@Savanna non è che lo odio .. Sto cercando un'alternativa/migliore punto di vista per la mia soluzione attuale;) Forse spostando da BW a una "nuova discussione" sarà una soluzione .. Personalmente non lo faccio come invocando perché devo fare il controllo "if InvokeRequired, then ..." ma ancora una volta - se non ci sono alternative allora dovrò attenermi a questo – Alex

risposta

6

C'è un parametro UserState quando si chiama ReportProgress.

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

new backgroundWorker1.ReportProgress(0, list_result); 

Il tipo di parametro è un object quindi dovrete lanciare di nuovo al tipo è necessario:

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

La questione spinosa con questo è, come si fa a stabilire se sei restituire un List o un elenco di elenchi o una stringa singola, un numero, ecc. È necessario verificare ogni possibilità nell'evento 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 
} 

In alternativa, è possibile creare una classe che contiene tutti i valori necessari (o utilizzare Tuple), tutto eseguito in background per popolare quella classe, quindi passare che per l'evento RunWorkerCompleted, e aggiornare l'interfaccia utente tutto subito da lì.

+2

Il tuo primo snippet crea una variabile chiamata 'list_result', ma tu passi qualcosa di indefinito chiamato 'myList' al metodo' ReportProgress' del lavoratore in background. Intendevi passare 'list_result' o hai lasciato fuori una linea? –

+0

@TonyVitabile Grazie, era solo un errore di copia/incolla. L'ho digitato in un modo, quindi ho cambiato il nome delle variabili in modo che corrispondessero a quelle di Alex. –

+0

oppure ... possiamo usare "e.ProgressPercentage" per rilevare un tipo di oggetto. Se (e.ProgressPercentage = 1) => oggetto è una stringa. "2" -> Obj è un Int, quindi fai altro, "3" -> ... – Alex

0

Il modello di base per l'aggiornamento dell'interfaccia utente da un altro thread è:

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

Questo potrebbe aggiornare l'elenco dei controlli senza che sia necessario passare nulla attraverso ReportProgress. Se desideri aggiornare il tuo controllo all'interno della discussione, non credo che sia necessario controllare InvokeRequired, perché sarà sempre richiesto. Tuttavia, le migliori pratiche potrebbero essere quella di esporre l'impostazione di un controllo tramite una proprietà e quindi di effettuare il controllo completo in modo da poterla chiamare da qualsiasi luogo.

4

Ho scritto due metodi molto semplici che consentono di richiamare il codice (solo se necessario) e basta scrivere il codice una sola volta. Penso che questo rende Invoke molto più amichevole da utilizzare:

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) Richiamare

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(); 
} 

Può essere chiamato in questo modo:

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

In alternativa si potrebbe solo

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

(che modifica solo il comportamento in modalità debug btw) e controlla se si verificano problemi.
Molto spesso non lo fai davvero. Mi ci è voluto un po 'di tempo per trovare casi in cui Molto Invoke è davvero necessario per non incasinare le cose.

+2

[È sicuro impostare CheckForIllegalCrossThreadCalls su false?] (Http://stackoverflow.com/a/13345622/719186) – LarsTech