2011-11-25 68 views
5

商業智能的人在這裏有足夠的C#在我的腰帶下是危險的。更新UI線程(文本框)通過C#

我已經構建了一個自制的winforms應用程序,實際上是在一個循環中執行一個命令行工具來「執行」。說的東西可能在幾秒鐘或幾分鐘內完成。通常我需要爲坐在DataTable中的每一行執行一次該工具。

我需要重定向命令行工具的輸出並將其顯示在「我的」應用程序中。我試圖通過一個文本框來做到這一點。我遇到了關於更新UI線程的問題,我無法自行理清。

要執行我的命令行工具,我已經借了代碼從這裏:How to parse command line output from c#?

這裏是我的等價:

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

      } 

     } 

引用txtOuput.text當我實例化我的cmd_DataReceived的StringBuilder()整潔地導致應用程序掛起:我猜測某種跨線程問題。

如果我刪除的StringBuilder的參考txtOuput.text並繼續調試,我得到一個跨線程違反此:

txtOutput.Text = sb.ToString(); 

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

OK,並不感到意外。我認爲cmd_DataReceived在另一個線程上運行,因爲我在Process.Start()之後執行某些操作,並且如果我刪除了對cmd_DataReceived()中的txtOuput.Text的所有引用,並只是轉儲命令行文本通過Console.Write()輸出到控制檯,一切工作正常。

所以,下次我會試着在http://msdn.microsoft.com/en-us/library/ms171728.aspx

使用更新的信息的UI線程上我的文本框的標準技術,我添加了一個委託和線程上我的課:

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; 

我添加一個程序來更新文本框:

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

我再添PROC它調用線程安全的一個:

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

    } 

...最後我把這樣的內cmd_DataReceived這個爛攤子:

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


    } 

} 

...當我運行這個文本,該文本框坐在那裏確實死了,不會得到更新。我的控制檯窗口繼續更新。正如你所看到的,我試着簡化了一些東西,只是讓文本框顯示「foo」與工具的實際輸出結果 - 但沒有什麼樂趣。我的用戶界面已死亡。

那麼是什麼原因?無法弄清楚我做錯了什麼。我根本不會結婚在文本框中顯示結果,順便說一句 - 我只需要能夠看到應用程序內部發生了什麼,我不想彈出另一個窗口來這樣做。

非常感謝。

回答

0

你的文本框不更新的原因之一,是因爲你沒有把這個字符串傳給你的setText方法。

你並不需要創建一個線程。 SetText的實現將處理將來自工作線程(其中調用cmd_DataReceived)的調用傳遞給UI線程。

這裏是我建議你做:

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


    } 

} 

而且,你是阻塞UI線程作爲@Fischermaen提到的,當你調用WaitForExit,你不需要它。

我也建議您在工作線程運行ImportWorkbooks像這樣: (如果你這樣做,你可以離開調用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

但是文本框仍然不會被更新,因爲UI線程被'cmdProcess.WaitForExit();'行阻止。 – Fischermaen

+0

謝謝,我會嘗試將您的建議與Fisherman和KooKiz的建議結合起來。 –

3

您從UI線程調用ImportWorkbooks。然後,在這個方法中,你調用「cmdProcess.WaitForExit()」。所以基本上你會阻止UI線程,直到進程完成執行。從一個線程執行ImportWorkbooks,它應該工作,或者移除WaitForExit並使用進程的「Exited」事件。

+0

謝謝,KooKiz! - 我可以使用與cmd_DataReceived()中相同的基本技術在不同的線程上啓動ImportWorkbooks()嗎? Thread.Start(),本質上? –

+0

@RussellChristopher是的,由於ImportWorkbooks不與UI交互,因此可以使用Thread.Start在「簡單」線程中執行它。 –

4

我認爲這個問題是在這一行:

cmdProcess.WaitForExit(); 

它在方法ImportWorkbooks這是從btnImport Click事件方法調用。所以你的UI線程被阻塞,直到後臺進程完成。