I have a custom output pane set up in a VsPackage
similar to the following:
///--------------------------------------------------------------------------------
/// <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;
}
}
And messages are sent to this pane using a method similar to:
///--------------------------------------------------------------------------------
/// <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);
}
}
I have an external process
that sends diagnostic results of the current solution to the console. It is set up similar to the following:
///--------------------------------------------------------------------------------
/// <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);
}
}
When I launch this external process
from the VSPackage
, I would like to stream these results (indirectly) to the custom output pane, showing messages as the diagnostics tool is reporting them. Is there a good way to do that?
Apparently you have to use the Process.BeginOutputReadLine()
method. An example can be found here: System.Diagnostics.Process.BeginOutputReadLine.
Updated RunDiagnostics, mostly utilizing J. Tihon's answer and some of the_drow's answer:
///--------------------------------------------------------------------------------
/// <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);
}
}
Although the OutPutDataReceived+BeginOutputReadLine looks like a more elegant and simple solution, I'll give an alternative. I solved the problem with a BackgroundWorker, and a ProcessOutPutHandler inspired from here. This approach also handles messages from stdout and stderr separately, and I can report progress to the BackgroundWorker depending on the output. Here I use the standard VS Output Window for the output, but should work with your OutputPane just as well:
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");
}
}
}
}
And usage:
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();
}
}
And this is called from the BackgroundWorker DoWork
method, the Worker passed as a reference.
You can use a listener and attach the process's stdout to it.
ConsoleTraceListener listener = new ConsoleTraceListener(process.StandardOutput);
Debug.Listeners.Add(listener);
Make sure you remove it after the process ends:
proc.Exited += () => Debug.Listeners.Remove(listener);
You'll need to use the process' OutputDataReceived event and than attach a listener:
ConsoleTraceListener listener = new ConsoleTraceListener();
Debug.Listeners.Add(listener);
proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) => Trace.WriteLine(outLine.Data);
Make sure you remove it after the process ends:
proc.Exited += (object sender, EventArgs e) => Debug.Listeners.Remove(listener);
来源:https://stackoverflow.com/questions/8345636/is-there-a-good-way-to-stream-the-results-from-an-external-process-into-a-visual