2012-12-31 13 views
5

Sto provando ad avere un thread separato in un'applicazione C# WinForms avviare un worker in background che controlli una barra di avanzamento (riquadro di selezione). Il problema è che quando provo a impostare la barra su visibile non fa nulla, e ho provato molte forme di Invoke ma non sembrano aiutare.Manipolazione di elementi dell'interfaccia utente da un'altra discussione

Il seguente metodo progressBarCycle viene chiamato da una filettatura separata.

BackgroundWorker backgroundWorker = new BackgroundWorker(); 

    public void progressBarCycle(int duration) 
    { 
     backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); 
     backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); 
     backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); 
     backgroundWorker.WorkerReportsProgress = true; 
     backgroundWorker.WorkerSupportsCancellation = true; 
     backgroundWorker.RunWorkerAsync(duration); 
    } 

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 

     worker.ReportProgress(0); 

     DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument); 
     while (DateTime.Now <= end) 
     { 
      System.Threading.Thread.Sleep(1000); 
     } 
    } 

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     if (!this.IsHandleCreated) 
      this.CreateHandle(); 
     statusStrip1.Invoke((MethodInvoker)delegate 
     { 
      progressBar1.Visible = false; 
     }); 
     // if (!this.IsHandleCreated) 
     // { 
     //  this.CreateHandle(); 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false)); 
     //  else progressBar1.Visible = false; 
     // } 
     // else 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false)); 
     //  else progressBar1.Visible = false; 
    } 

    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     if (!this.IsHandleCreated) 
      this.CreateHandle(); 
     statusStrip1.Invoke((MethodInvoker)delegate 
     { 
      progressBar1.Visible = true; 
     }); 
     // if (!this.IsHandleCreated) 
     // { 
     //  this.CreateHandle(); 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); 
     //  else progressBar1.Visible = true; 
     // } 
     // else 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); 
     //  else progressBar1.Visible = true; 
    } 

Mi manca qualcosa di ovvio qui? Le sezioni dei commenti sono altre cose che ho provato.

risposta

6

ProgressChanged è già generato nel thread dell'interfaccia utente (tramite il contesto di sincronizzazione); il tuo ProgressChanged non ha bisogno di farlo Invoke - può manipolare l'interfaccia utente direttamente (al contrario, DoWork può assolutamente non farlo). Forse il vero problema è che non si fa il worker.ReportProgress(...)all'interno del ciclo - quindi succede solo una volta all'inizio.

Ecco un esempio completo:

using System; 
using System.ComponentModel; 
using System.Threading; 
using System.Windows.Forms; 
static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     using (var worker = new BackgroundWorker { 
      WorkerReportsProgress = true }) 
     using (var progBar = new ProgressBar { 
      Visible = false, Step = 1, Maximum = 100, 
      Dock = DockStyle.Bottom }) 
     using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" }) 
     using (var form = new Form { Controls = { btn, progBar } }) 
     { 
      worker.ProgressChanged += (s,a) => { 
       progBar.Visible = true; 
       progBar.Value = a.ProgressPercentage; 
      }; 
      worker.RunWorkerCompleted += delegate 
      { 
       progBar.Visible = false; 
      }; 
      worker.DoWork += delegate 
      { 
       for (int i = 0; i < 100; i++) 
       { 
        worker.ReportProgress(i); 
        Thread.Sleep(100); 
       } 
      }; 
      btn.Click += delegate 
      { 
       worker.RunWorkerAsync(); 
      }; 
      Application.Run(form); 
     } 
    } 
} 
+0

Ho inserito il file ProgressChanged e RunWorkerCompleted puramente per vedere se avrebbe risolto il problema. Quando avevo tutto quel codice incluso in DoWork, invece, avrebbe fatto esattamente la stessa cosa.Lo stato di Visible non cambierebbe mai, con o senza invocazione. - Oh, e probabilmente vale la pena ricordare che questo è un Marquee, quindi non intendo usare rapporti sui progressi. – UncleDave

+0

@UncleDave quindi sembra che la barra di avanzamento si trovi all'interno di un altro controllo (pannello forse) che non è visibile. –

+0

La barra di avanzamento si trova nel modulo che esegue questo codice, che è anche il modulo di avvio dell'applicazione. – UncleDave

2
  1. Run progressBarCycle dal thread dell'interfaccia utente. RunWorkerAsync sarà creare il nuovo thread per te.
  2. In backgroundWorker_ProgressChanged è sufficiente chiamare progressBar1.Visible = true;. Non è necessario per Invoke.
  3. Meglio anche aggiungere uno progressBar1.Refresh();.
2

Un'altra possibilità da tenere presente è che la barra di avanzamento è in esecuzione sul thread dell'interfaccia utente. Affinché la barra di avanzamento sia visualizzata e ridisegnata per mostrare nuovi importi di avanzamento, il thread dell'interfaccia utente deve essere eseguito liberamente, elaborando i messaggi di Windows nel ciclo principale dell'applicazione.

Quindi, se si avvia il thread in background, ma il thread dell'interfaccia utente si trova in un ciclo di attesa occupato in attesa del completamento, oppure si interrompe e fa un sacco di altri lavori, non elaborerà i messaggi di Windows e barra di avanzamento sarà "non risponde". È necessario rilasciare il thread dell'interfaccia utente in modo che questo aggiornamento continui a verificarsi (ovvero, restituire dal gestore eventi e consentire all'interfaccia utente di continuare a funzionare normalmente).

Il pericolo è che se l'interfaccia utente è attiva, l'utente può comunque interagire con esso. Pertanto, è necessario scrivere l'interfaccia utente per essere consapevoli quando l'operatore in background è attivo e gestire correttamente la situazione (i problemi possono includere: Consentire all'utente di avviare nuovamente l'operatore in background mentre è già in esecuzione, l'interfaccia utente tenta di visualizzare le informazioni mentre l'operatore thread lo sta aggiornando attivamente, l'utente decide di caricare un nuovo documento o di uscire mentre l'operatore in background è occupato, ecc.). Le due soluzioni principali per questo sono avvolgere ogni bit di interfaccia utente in uno scudo protettivo che impedisce l'avvio di qualsiasi cosa pericolosa mentre il lavoro in background è in esecuzione (questo può essere molto lavoro se hai molti controlli da avvolgere in questo modo, e è facile commettere un errore che lascia passare un errore) o lasciare l'interfaccia utente "non protetta" ma aggiungere un IMessageFilter che interrompe tutte le interazioni "pericolose" dell'utente (clic e tasti) eliminando i messaggi Windows in arrivo (WM_KEYPRESS, ecc.) mentre il l'elaborazione in background è attiva.