2011-11-25 9 views
5

Tipo de inteligencia de negocios aquí con suficiente C# debajo de mi cinturón como para ser peligroso.Actualizando el hilo de la interfaz de usuario (cuadro de texto) a través de C#

He creado una aplicación homeform winforms que esencialmente ejecuta una herramienta de línea de comandos en un bucle para "hacer cosas". Dichas cosas pueden completarse en segundos o minutos. Normalmente tendré que ejecutar la herramienta una vez por cada fila sentada en una DataTable.

Necesito redirigir la salida de la herramienta de línea de comandos y mostrarla en "mi" aplicación. Estoy intentando hacerlo a través de un cuadro de texto. Estoy teniendo problemas para actualizar el hilo de la interfaz de usuario que no puedo resolver por mi cuenta.

Para ejecutar mi herramienta de línea de comandos, que ha pedido prestado código de aquí: How to parse command line output from c#?

Aquí es mi equivalentes:

 private void btnImport_Click(object sender, EventArgs e) 
     { 
      txtOutput.Clear(); 
      ImportWorkbooks(dtable); 

     } 


     public void ImportWorkbooks(DataTable dt) 
     { 

      ProcessStartInfo cmdStartInfo = new ProcessStartInfo(); 
      cmdStartInfo.FileName = @"C:\Windows\System32\cmd.exe"; 
      cmdStartInfo.RedirectStandardOutput = true; 
      cmdStartInfo.RedirectStandardError = true; 
      cmdStartInfo.RedirectStandardInput = true; 
      cmdStartInfo.UseShellExecute = false; 
      cmdStartInfo.CreateNoWindow = false; 

      Process cmdProcess = new Process(); 
      cmdProcess.StartInfo = cmdStartInfo; 
      cmdProcess.ErrorDataReceived += cmd_Error; 
      cmdProcess.OutputDataReceived += cmd_DataReceived; 
      cmdProcess.EnableRaisingEvents = true; 
      cmdProcess.Start(); 
      cmdProcess.BeginOutputReadLine(); 
      cmdProcess.BeginErrorReadLine(); 

      //Login 
      cmdProcess.StandardInput.WriteLine(BuildLoginString(txtTabCmd.Text, txtImportUserName.Text, txtImportPassword.Text, txtImportToServer.Text)); 


      foreach (DataRow dr in dt.Rows) 
      { 
        cmdProcess.StandardInput.WriteLine(CreateServerProjectsString(dr["Project"].ToString(), txtTabCmd.Text)); 

       //Import Workbook 

       cmdProcess.StandardInput.WriteLine(BuildPublishString(txtTabCmd.Text, dr["Name"].ToString(), dr["UID"].ToString(),dr["Password"].ToString(), dr["Project"].ToString())); 
      } 
      cmdProcess.StandardInput.WriteLine("exit"); //Execute exit. 
      cmdProcess.EnableRaisingEvents = false; 
      cmdProcess.WaitForExit(); 
     } 


private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
     { 
      //MessageBox.Show("Output from other process"); 
      try 
      { 
     // I want to update my textbox here, and then position the cursor 
     // at the bottom ala: 

       StringBuilder sb = new StringBuilder(txtOutput.Text); 
       sb.AppendLine(e.Data.ToString()); 
       txtOutput.Text = sb.ToString(); 
       this.txtOutput.SelectionStart = txtOutput.Text.Length; 
       this.txtOutput.ScrollToCaret(); 


      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("{0} Exception caught.", ex); 

      } 

     } 

referencia a txtOuput.text cuando instanciar mi StringBuilder en cmd_DataReceived() ordenadamente hace que la aplicación se cuelgue: estoy adivinando algún tipo de problema de cruce de hilos.

Si quito la referencia a txtOuput.text en StringBuilder y continuar con la depuración, me sale una violación entre subprocesos aquí:

txtOutput.Text = sb.ToString(); 

Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on. 

bien, no sorprende. Supuse que cmd_DataReceived se está ejecutando en otro hilo ya que lo estoy golpeando como resultado de hacer cosas después de un Process.Start() ... y si elimino TODAS las referencias a txtOuput.Text en cmd_DataReceived() y simplemente vuelvo a colocar el texto de la línea de comandos salida a la consola a través de Console.Write(), todo funciona bien.

Así, la próxima voy a probar técnicas estándar para la actualización de mi cuadro de texto en el subproceso de interfaz de usuario con la información de http://msdn.microsoft.com/en-us/library/ms171728.aspx

agrego un delegado y el hilo de mi clase:

delegate void SetTextCallback(string text); 
// This thread is used to demonstrate both thread-safe and 
// unsafe ways to call a Windows Forms control. 
private Thread demoThread = null; 

I añadir un procedimiento para actualizar el cuadro de texto:

private void SetText(string text) 
    { 
     // InvokeRequired required compares the thread ID of the 
     // calling thread to the thread ID of the creating thread. 
     // If these threads are different, it returns true. 
     if (this.txtOutput.InvokeRequired) 
     { 
      SetTextCallback d = new SetTextCallback(SetText); 
      this.Invoke(d, new object[] { text }); 
     } 
     else 
     { 
      this.txtOutput.Text = text; 
     } 
    } 

añado otra proc que llama al seguro para subprocesos uno:

private void ThreadProcSafe() 
    { 
     // this.SetText(sb3.ToString()); 
     this.SetText("foo"); 

    } 

... y finalmente me llamo este lío dentro cmd_DataReceived así:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
{ 
    //MessageBox.Show("Output from other process"); 
    try 
    { 

     sb3.AppendLine(e.Data.ToString()); 

     //this.backgroundWorker2.RunWorkerAsync(); 
     this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); 
     this.demoThread.Start(); 
     Console.WriteLine(e.Data.ToString()); 

    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("{0} Exception caught.", ex); 


    } 

} 

... Cuando ejecuto este texto, el cuadro de texto no se sienta bien muerto, no tener actualizada. La ventana de mi consola continúa actualizándose. Como puede ver, traté de simplificar un poco las cosas con solo hacer que el cuadro de texto muestre "foo" frente a la salida real de la herramienta, pero no hay alegría. Mi UI está muerta.

¿Qué ofrece? No puedo entender lo que estoy haciendo mal. No estoy para nada casado con mostrar los resultados en un cuadro de texto, por cierto, solo necesito poder ver lo que sucede dentro de la aplicación y preferiría no abrir otra ventana para hacerlo.

Muchas gracias.

Respuesta

0

uno de los motivos por los que su cuadro de texto no se actualiza se debe a que no pasa la cadena a su método SetText.

No es necesario crear un hilo. Su implementación de SetText se encargará de pasar la llamada desde el hilo del trabajador (donde se llama a cmd_DataReceived) al hilo de la interfaz de usuario.

Esto es lo que te sugiero que hagas:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
{ 
    //MessageBox.Show("Output from other process"); 
    try 
    { 


     string str = e.Data.ToString(); 
     sb3.AppendLine(str); 
     SetText(str); //or use sb3.ToString if you need the entire thing 

     Console.WriteLine(str); 

    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("{0} Exception caught.", ex); 


    } 

} 

Además, están bloqueando el hilo de interfaz de usuario como se mencionó @Fischermaen cuando llame WaitForExit, que no lo necesita.

También sugeriría que ejecute ImportWorkbooks en un subproceso de trabajo, así: (si lo hace, puede dejar la llamada a WaitForExit)

private void btnImport_Click(object sender, EventArgs e) 
{ 
    txtOutput.Clear(); 
    ThreadPool.QueueUserWorkItem(ImportBooksHelper, dtTable); 
} 

private ImportBooksHelper(object obj) 
{ 
    DataTable dt = (DataTable)obj; 
    ImportWorkbooks(dtable); 
} 
+0

Pero el cuadro de texto todavía no se actualizará, porque el hilo de la interfaz de usuario está bloqueado por la línea 'cmdProcess.WaitForExit();'. – Fischermaen

+0

Gracias, intentaré combinar su sugerencia con la de Fisherman y KooKiz. –

3

Está llamando a ImportWorkbooks desde el hilo de la interfaz de usuario. Luego, en este método, está llamando "cmdProcess.WaitForExit()". Entonces, básicamente, estás bloqueando el hilo de UI hasta que el proceso haya terminado de ejecutarse.Ejecute ImportWorkbooks desde un subproceso y debería funcionar, o elimine WaitForExit y use el evento 'Exited' del proceso en su lugar.

+0

Gracias, KooKiz! - ¿Puedo lanzar ImportWorkbooks() en un hilo diferente usando la misma técnica básica que en cmd_DataReceived()? Thread.Start(), esencialmente? –

+0

@RussellChristopher Sí, dado que ImportWorkbooks no interactúa con la interfaz de usuario, puede ejecutarlo en un hilo 'simple', utilizando Thread.Start. –

4

Creo que el problema es en esta línea:

cmdProcess.WaitForExit(); 

Está en el método ImportWorkbooks que se llama el método de evento Click de btnImport. Así que su subproceso de interfaz de usuario está bloqueado hasta que se complete el proceso en segundo plano.

Cuestiones relacionadas