2011-12-01 29 views
4

我有一個VsPackage類似設置如下的自定義輸出窗格:是否有將外部流程的結果流式傳輸到Visual Studio輸出窗格的好方法?

///-------------------------------------------------------------------------------- 
    /// <summary>This property gets the custom output pane.</summary> 
    ///-------------------------------------------------------------------------------- 
    private Guid _customPaneGuid = Guid.Empty; 
    private IVsOutputWindowPane _customPane = null; 
    private IVsOutputWindowPane customPane 
    { 
     get 
     { 
      if (_customPane == null) 
      { 
       IVsOutputWindow outputWindow = GetService(typeof(SVsOutputWindow)) as IVsOutputWindow; 
       if (outputWindow != null) 
       { 
        // look for existing solution updater pane 
        if (_customPaneGuid == Guid.Empty || ErrorHandler.Failed(outputWindow.GetPane(ref _customPaneGuid, out _customPane)) || _customPane == null) 
        { 
         // create a new solution updater pane 
         outputWindow.CreatePane(ref _customPaneGuid, "My Output", 1, 1); 
         if (ErrorHandler.Failed(outputWindow.GetPane(ref _customPaneGuid, out _customPane)) || _customPane == null) 
         { 
          // pane could not be created and retrieved, throw exception 
          throw new Exception("Custom pane could not be created and/or retrieved"); 
         } 
        } 
       } 
      } 
      if (_customPane != null) 
      { 
       _customPane.Activate(); 
      } 
      return _customPane; 
     } 
    } 

和消息使用了一種類似於方法發送到該窗格:

///-------------------------------------------------------------------------------- 
    /// <summary>This method displays a message in the output area.</summary> 
    /// 
    /// <param name="outputTitle">The title for the message.</param> 
    /// <param name="outputMessage">The message to show.</param> 
    /// <param name="appendMessage">Flag indicating whether message should be appended to existing message.</param> 
    ///-------------------------------------------------------------------------------- 
    public void ShowOutput(string outputTitle, string outputMessage, bool appendMessage, bool isException) 
    { 
     if (appendMessage == false) 
     { 
      // clear output pane 
      CustomPane.Clear(); 
     } 

     if (outputTitle != string.Empty) 
     { 
      // put output title to output pane 
      CustomPane.OutputString("\r\n" + outputTitle); 
     } 

     // put output message to output pane 
     CustomPane.OutputString("\r\n" + outputMessage); 

     if (isException == true) 
     { 
      // show message box 
      MessageBox.Show(outputTitle + "\r\n" + outputMessage, outputTitle); 
     } 
    } 

我有一個external process那將當前解決方案的診斷結果發送到控制檯。它是建立一個類似以下內容:

///-------------------------------------------------------------------------------- 
/// <summary>This method handles clicking on the Run Diagnostics submenu.</summary> 
/// 
/// <param term='inputCommandBarControl'>The control that is source of the click.</param> 
/// <param term='handled'>Handled flag.</param> 
/// <param term='cancelDefault'>Cancel default flag.</param> 
///-------------------------------------------------------------------------------- 
protected void RunDiagnostics_Click(object inputCommandBarControl, ref bool handled, ref bool cancelDefault) 
{ 
    try 
    { 
     // set up and execute diagnostics thread 
     RunDiagnosticsDelegate RunDiagnosticsDelegate = RunDiagnostics; 
     RunDiagnosticsDelegate.BeginInvoke(RunDiagnosticsCompleted, RunDiagnosticsDelegate); 
    } 
    catch (Exception ex) 
    { 
     // put exception message in output pane 
     CustomPane.OutputString(ex.Message); 
    } 
} 

protected delegate void RunDiagnosticsDelegate(); 

///-------------------------------------------------------------------------------- 
/// <summary>This method launches the diagnostics to review the solution.</summary> 
///-------------------------------------------------------------------------------- 
protected void RunDiagnostics() 
{ 
    try 
    { 
     // set up diagnostics process 
     string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName); 
     System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(@"MyDiagnostics.exe", solutionDir); 
     procStartInfo.RedirectStandardOutput = true; 
     procStartInfo.UseShellExecute = false; 
     procStartInfo.CreateNoWindow = true; 
     System.Diagnostics.Process proc = new System.Diagnostics.Process(); 
     proc.StartInfo = procStartInfo; 

     // execute the diagnostics 
     proc.Start(); 

     // put diagnostics output to output pane 
     CustomPane.OutputString(proc.StandardOutput.ReadToEnd()); 
     CustomPane.OutputString("Diagnostics run complete."); 
    } 
    catch (Exception ex) 
    { 
     // put exception message in output pane 
     CustomPane.OutputString(ex.Message); 
    } 
} 

///-------------------------------------------------------------------------------- 
/// <summary>This method handles completing the run diagnostics thread.</summary> 
/// 
/// <param name="ar">IAsyncResult.</param> 
///-------------------------------------------------------------------------------- 
protected void RunDiagnosticsCompleted(IAsyncResult ar) 
{ 
    try 
    { 
     if (ar == null) throw new ArgumentNullException("ar"); 

     RunDiagnosticsDelegate RunDiagnosticsDelegate = ar.AsyncState as RunDiagnosticsDelegate; 
     Trace.Assert(RunDiagnosticsDelegate != null, "Invalid object type"); 

     RunDiagnosticsDelegate.EndInvoke(ar); 
    } 
    catch (Exception ex) 
    { 
     // put exception message in output pane 
     CustomPane.OutputString(ex.Message); 
    } 
} 

當我從VSPackage推出這個external process,我想流這些結果(間接)自定義輸出窗口,顯示出作爲診斷工具的消息被報告給他們。 。有沒有一個好的方法來做到這一點?

+0

我假設你的榜樣工作,但沒有「流」的輸出,但寫這一切在一個大的批次?因爲根據文檔,'CustomPane.OutputString(proc.StandardOutput.ReadToEnd())'會阻塞該線程,直到過程結束。所以你正在尋找一種方法讓輸出「推」給你,而不是「拉」它,對吧? –

+0

@ J.Tihon:看到我的回答:) –

+0

@J。Tihon,沒錯,我不想將標準輸出直接流到輸出窗格(阻止進程),但想要在輸出窗格中寫入消息。 –

回答

1

您可以使用listener並附加進程的標準輸出它。

ConsoleTraceListener listener = new ConsoleTraceListener(process.StandardOutput); 
Debug.Listeners.Add(listener); 

確保您的過程結束之後將其刪除:

proc.Exited +=() => Debug.Listeners.Remove(listener); 

你需要在使用過程中OutputDataReceived事件,比附上listener

ConsoleTraceListener listener = new ConsoleTraceListener(); 
Debug.Listeners.Add(listener); 

proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) => Trace.WriteLine(outLine.Data); 

確保你第後刪除e過程結束:

proc.Exited += (object sender, EventArgs e) => Debug.Listeners.Remove(listener); 
+1

ConsoleTraceListener沒有這樣的構造函數重載。 ('System.Diagnostics.Process.StandardOutput'類型爲'StreamReader') –

+0

對於偵聽器方法而言,但由於某些原因未收到消息。 proc.Exited + =(object sender,EventArgs e)=> Debug.Listeners.Remove(listener); –

+0

@DaveClemmer:奇怪,並感謝代碼修復。你可以編輯它。 –

3

更新RunDiagnostics,大多是利用J.吉洪的答案和一些the_drow的回答:

///-------------------------------------------------------------------------------- 
/// <summary>This method launches the diagnostics to review the solution.</summary> 
///-------------------------------------------------------------------------------- 
protected void RunDiagnostics() 
{ 
    try 
    { 
     // set up diagnostics process 
     string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName); 
     System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(@"MyDiagnostics.exe", solutionDir); 
     procStartInfo.RedirectStandardOutput = true; 
     procStartInfo.UseShellExecute = false; 
     procStartInfo.CreateNoWindow = true; 
     System.Diagnostics.Process proc = new System.Diagnostics.Process(); 
     proc.StartInfo = procStartInfo; 
     proc.StartInfo.RedirectStandardOutput = true; 
     proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) 
      => ShowOutput(String.Empty, outLine.Data, true, false); 

     // execute the diagnostics 
     proc.Start(); 
     proc.BeginOutputReadLine(); 
    } 
    catch (Exception ex) 
    { 
     // put exception message in output pane 
     CustomPane.OutputString(ex.Message); 
    } 
} 
1

雖然OutPutDataReceived + BeginOutputReadLine看起來更優雅和簡單的解決方案,我給的替代品。我用一個BackgroundWorker解決了這個問題,並從here中啓發了一個ProcessOutPutHandler。這種方法還分別處理來自stdout和stderr的消息,並根據輸出結果向BackgroundWorker報告進度。這裏我使用VS輸出窗口的輸出標準,但應與你的OutputPane很好的工作:

public class ProcessOutputHandler 
{ 
    public Process proc { get; set; } 
    public string StdOut { get; set; } 
    public string StdErr { get; set; } 
    private IVsOutputWindowPane _pane; 
    private BackgroundWorker _worker; 

    /// <summary> 
    /// The constructor requires a reference to the process that will be read. 
    /// The process should have .RedirectStandardOutput and .RedirectStandardError set to true. 
    /// </summary> 
    /// <param name="process">The process that will have its output read by this class.</param> 
    public ProcessOutputHandler(Process process, BackgroundWorker worker) 
    { 
     _worker = worker; 
     proc = process; 
     IVsOutputWindow outputWindow = 
     Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; 

     Guid guidGeneral = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid; 
     int hr = outputWindow.CreatePane(guidGeneral, "Phone Visualizer", 1, 0); 
     hr = outputWindow.GetPane(guidGeneral, out _pane); 
     _pane.Activate(); 
     _pane.OutputString("Starting Ui State workers.."); 

     StdErr = ""; 
     StdOut = ""; 
     Debug.Assert(proc.StartInfo.RedirectStandardError, "RedirectStandardError must be true to use ProcessOutputHandler."); 
     Debug.Assert(proc.StartInfo.RedirectStandardOutput, "RedirectStandardOut must be true to use ProcessOutputHandler."); 
    } 

    /// <summary> 
    /// This method starts reading the standard error stream from Process. 
    /// </summary> 
    public void ReadStdErr() 
    { 
     string line; 
     while ((!proc.HasExited) && ((line = proc.StandardError.ReadLine()) != null)) 
     { 
      StdErr += line; 
      _pane.OutputString(line + "\n"); 
      // Here I could do something special if errors occur 
     } 
    } 
    /// <summary> 
    /// This method starts reading the standard output sream from Process. 
    /// </summary> 
    public void ReadStdOut() 
    { 
     string line; 
     while ((!proc.HasExited) && ((line = proc.StandardOutput.ReadLine()) != null)) 
     { 
      StdOut += line; 
      _pane.OutputString(line + "\n"); 
      if (_worker != null && line.Contains("Something I'm looking for")) 
      {        
       _worker.ReportProgress(20, "Something worth mentioning happened"); 
      } 
     } 
    } 

} 

與用法:

void RunProcess(string fileName, string arguments, BackgroundWorker worker) 
{ 
    // prep process 
    ProcessStartInfo psi = new ProcessStartInfo(fileName, arguments); 
    psi.UseShellExecute = false; 
    psi.RedirectStandardOutput = true; 
    psi.RedirectStandardError = true; 
    // start process 
    using (Process process = new Process()) 
    { 
    // pass process data 
    process.StartInfo = psi; 
    // prep for multithreaded logging 
    ProcessOutputHandler outputHandler = new ProcessOutputHandler(process,worker); 
    Thread stdOutReader = new Thread(new ThreadStart(outputHandler.ReadStdOut)); 
    Thread stdErrReader = new Thread(new ThreadStart(outputHandler.ReadStdErr)); 
    // start process and stream readers 
    process.Start(); 
    stdOutReader.Start(); 
    stdErrReader.Start(); 
    // wait for process to complete 
    process.WaitForExit(); 
    } 
} 

,這是從BackgroundWorker的DoWork方法調用,工作人員作爲參考傳遞。