2011-11-25 13 views
5

で更新しています。UIスレッド(テキストボックス)をC#

私は基本的にコマンドラインツールを "やる"ループで実行する自作のwinformsアプリケーションを構築しました。上記のものは数秒または数分で完了することがあります。通常、私はDataTableに座っている行ごとにツールを一度実行する必要があります。

私は、コマンドラインツールの出力をリダイレクトし、 "my"アプリケーションに表示する必要があります。私はテキストボックスを介してそうしようとしています。私は自分で修正することができないUIスレッドの更新に関する問題に取り組んでいます。

私のコマンドラインツールを実行するには、私はここからコードを借りてきました。ここで

How to parse command line output from c#?は私と同等です:私はcmd_DataReceivedの私のStringBuilderをインスタンス化するときtxtOuput.textを参照

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

      } 

     } 

()きちんとアプリがハングアップする:私はある種のクロススレッドの問題を推測している。

txtOutput.Text = sb.ToString(); 

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

OK、驚いていない:私はStringBuilderの中txtOuput.textへの参照を削除し、デバッグを続行した場合

は、私はここで、クロススレッド違反を取得します。私は、Process.Start()の後に処理を実行した結果、別のスレッドでcmd_DataReceivedが実行されていると仮定しました。cmd_DataReceived()のtxtOuput.Textへの参照をすべて削除し、単にコマンドラインのテキストをダンプするとConsole.Write()経由でコンソールに出力すると、すべて正常に動作します。

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:

だから、次の私は、私は私のクラスにデリゲートを追加して、スレッド

http://msdn.microsoft.com/en-us/library/ms171728.aspx内の情報を使用してUIスレッドで私のテキストボックスを更新するための標準的な技術を試してみるつもりですテキストボックスを更新するための手順を追加します。

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以内にこの混乱を呼ぶ:私はこのテキストを実行すると、テキストボックスが更新され得ていない、doornailとしてそこに死んで座っている...

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


    } 

} 

。私のコンソールウィンドウは更新を続けます。ご覧のとおり、テキストボックスにツールからの実際の出力を表示するように設定すると、ちょっと単純化されましたが、喜びはありません。私のUIは死んでいる。

だから何ですか?私が間違っていることを理解できません。私はテキストボックスに結果を表示することに結婚していません - 私はアプリケーション内で何が起こっているのかを見ることができ、別のウィンドウをポップアップしないようにしたいと思うだけです。

多くのありがとうございます。

答えて

0

テキストボックスが更新されない理由の1つは、文字列をSetTextメソッドに渡さないためです。

スレッドを作成する必要はありません。 SetTextの実装は、ワーカースレッド(cmd_DataReceivedが呼び出されている場所)からUIスレッドへの呼び出しの受け渡しを処理します。ここで

は、私はあなたがお勧めです:@FischermaenはあなたがWaitForExitを呼び出すときに、あなたがそれを必要としないに述べたように

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スレッドをブロックしています。

私はまた、あなたがそうのようなワーカースレッド上で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を削除し、代わりにプロセスの「終了」イベントを使用する必要があります。

+0

おかげで、KooKizを! cmd_DataReceived()と同じ基本的なテクニックを使用して、別のスレッドでImportWorkbooks()を起動できますか? Thread.Start()、本質的に? –

+0

@RussellChristopherはい、ImportWorkbooksはUIとやり取りしないので、Thread.Startを使用して '単純な'スレッドで実行できます。 –

4

私は、問題は、この行であると思う:

cmdProcess.WaitForExit(); 

それはbtnImportのClickイベントメソッドから呼び出されるメソッドImportWorkbooksにあります。したがって、UIスレッドは、バックグラウンドプロセスが完了するまでブロックされます。

関連する問題