2011-11-25 33 views
5

Anh chàng nghiệp vụ thông minh ở đây với đủ C# dưới đai của tôi để trở nên nguy hiểm.Cập nhật chuỗi giao diện người dùng (hộp văn bản) qua C#

Tôi đã xây dựng ứng dụng winforms homebrew về cơ bản thực hiện một công cụ dòng lệnh trong vòng lặp để "thực hiện công cụ". Nói những thứ có thể hoàn thành trong vài giây hoặc vài phút. Thông thường, tôi sẽ cần phải thực thi công cụ một lần cho mỗi hàng ngồi trong một DataTable.

Tôi cần chuyển hướng đầu ra của công cụ dòng lệnh và hiển thị nó trong ứng dụng "của tôi". Tôi đang cố gắng làm như vậy thông qua một hộp văn bản. Tôi đang gặp sự cố khi cập nhật chuỗi giao diện người dùng mà tôi không thể tự mình thực hiện được.

Để thực hiện công cụ dòng lệnh của tôi, tôi đã mượn mã từ đây: How to parse command line output from c#?

Dưới đây là tương đương của tôi:

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

      } 

     } 

Tham khảo txtOuput.text khi tôi nhanh chóng StringBuilder tôi trong cmd_DataReceived() gọn gàng làm cho ứng dụng bị treo: tôi đoán một số loại vấn đề chéo.

Nếu tôi loại bỏ các tham chiếu đến txtOuput.text trong StringBuilder và tiếp tục gỡ lỗi, tôi nhận được một sự vi phạm chéo chủ đề ở đây:

txtOutput.Text = sb.ToString(); 

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

OK, không ngạc nhiên. Tôi giả sử cmd_DataReceived đang chạy trên một thread khác vì tôi đánh nó như là kết quả của việc thực hiện sau khi một Process.Start() ... và nếu tôi loại bỏ TẤT CẢ các tham chiếu đến txtOuput.Text trong cmd_DataReceived() và chỉ cần đổ văn bản dòng lệnh đầu ra cho giao diện điều khiển thông qua Console.Write(), mọi thứ hoạt động tốt.

Vì vậy, bên cạnh tôi sẽ thử kỹ thuật tiêu chuẩn cho việc cập nhật TextBox của tôi trên thread UI bằng cách sử dụng thông tin trong http://msdn.microsoft.com/en-us/library/ms171728.aspx

tôi thêm một đại biểu và thread để lớp học của tôi:

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; 

tôi thêm một thủ tục để cập nhật các hộp văn bản:

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

tôi thêm một proc mà gọi là thread-safe một:

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

    } 

... và cuối cùng tôi gọi mớ hỗn độn này trong vòng cmd_DataReceived như thế này:

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


    } 

} 

... Khi tôi chạy văn bản này, các textbox ngồi ở đó chết như một doornail, không nhận được cập nhật. Cửa sổ bảng điều khiển của tôi tiếp tục cập nhật. Như bạn có thể thấy, tôi đã thử đơn giản hóa mọi thứ một chút chỉ bằng cách nhận được hộp văn bản để hiển thị "foo" so với đầu ra thực sự từ công cụ - nhưng không có niềm vui. Giao diện người dùng của tôi đã chết.

Vì vậy, điều gì mang lại? Không thể hiểu được tôi đang làm gì sai. Tôi không kết hôn để hiển thị kết quả trong một hộp văn bản, btw - Tôi chỉ cần để có thể xem những gì đang xảy ra bên trong ứng dụng và tôi không muốn bật lên một cửa sổ để làm như vậy.

Rất cám ơn.

Trả lời

0

một trong những lý do khiến hộp văn bản không cập nhật là vì bạn không chuyển chuỗi đó vào phương thức SetText của mình.

Bạn không cần tạo chuỗi. Thực hiện của bạn sẽ SetText sẽ xử lý đi qua các cuộc gọi đến từ chủ đề công nhân (nơi cmd_DataReceived được gọi) đến thread UI.

Dưới đây là những gì tôi đề nghị bạn làm:

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


    } 

} 

Ngoài ra, bạn sẽ loại bỏ quyền thread UI như @Fischermaen đề cập khi bạn gọi WaitForExit, bạn không cần nó.

Tôi cũng xin đề nghị bạn nên chạy ImportWorkbooks trên một sợi công nhân như vậy: (nếu bạn làm điều này bạn có thể để lại lời kêu gọi 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

Nhưng hộp văn bản sẽ vẫn không được cập nhật, vì chuỗi giao diện người dùng bị chặn bởi dòng 'cmdProcess.WaitForExit();'. – Fischermaen

+0

Cảm ơn, tôi sẽ thử kết hợp gợi ý của bạn với Fisherman và KooKiz. –

3

Bạn đang gọi ImportWorkbooks từ chuỗi giao diện người dùng. Sau đó, trong phương thức này, bạn đang gọi "cmdProcess.WaitForExit()". Vì vậy, về cơ bản bạn đang chặn chuỗi giao diện người dùng cho đến khi quá trình thực hiện xong.Thực thi ImportWorkbooks từ một chủ đề và nó sẽ làm việc, hoặc loại bỏ WaitForExit và thay vào đó sử dụng sự kiện '' Exited ''.

+0

Cảm ơn, KooKiz! - Tôi có thể khởi chạy ImportWorkbooks() trên một luồng khác bằng cách sử dụng cùng một kỹ thuật cơ bản như trong cmd_DataReceived() không? Thread.Start(), về cơ bản? –

+0

@RussellChristopher Có, vì ImportWorkbooks không tương tác với giao diện người dùng, bạn có thể thực thi nó trong một luồng 'đơn giản', sử dụng Thread.Start. –

4

Tôi nghĩ vấn đề là ở dòng này:

cmdProcess.WaitForExit(); 

Đó là trong phương pháp ImportWorkbooks được gọi là từ phương pháp sự kiện Click của btnImport. Vì vậy, chuỗi giao diện người dùng của bạn bị chặn cho đến khi quá trình nền hoàn tất.

Các vấn đề liên quan