Tuesday, May 17, 2011

Writing into Visual Studio Output Window. Part 2


In Part 1 we set up OutputWindowWriter class which performs writing to the Visual Studio output window. What else can we add here?
It would be great if everything worked fine from the first attempt, but this not always happens, especially when dialing with someone’s else code. In our situation we have problem - when output window is initially hidden (by auto hide option at the top right corner of the window next to the cross) and we are writing something there we may get something like this
image

Oh, where is my text which I have written into the output window in part 1, did it stop working? The answer is – no, it is still written there, just take a look at the scrollbar appeared at the right side. If you scroll – you will find all the text that was written. Gosh, the code is saved :). But can I make that text visible? (By the way, sometimes when compiling project I get the same result in Build output pane, so this isn’t bug of our code, it is (for some reason) designed behavior).
I went through many of the interfaces/properties which I could get from OutputWindow object and its pane. But everything which sets cursor or scroll position resulting in no effect (result code was either not implemented or some other values which aren’t important here).
The solution is quite simple – show output window before writing data in it. As for me, it is rather weird fact that the way window will be displayed later depends on whether it was visible, but the fact still remains – if you want to see last lines of written data without scrolling somewhere, you should display output window first (by the way, remember there is option called “Show Output window when build starts” (Tools \ Options \ Projects and Solutions \ General) – I suppose at least part of the reason it exists is this problem with output text (or vice versa – VS development team didn’t notice this behavior because they have that option turned on).
Now task became simpler – we simply need to show the window. Let’s do it:
/// <summary> /// Makes output window visible. /// </summary> public void Show() { DTE2 dte = ( _package as IServiceProvider ).GetService( typeof( DTE ) ) as DTE2; OutputWindow outputWindow = dte.ToolWindows.OutputWindow; otputWindow.Parent.Activate(); }
And now you have an option – call it before first write call, call it during each write operation, or never call it. I prefer the first one.

After calling it inside our About box, we are getting something like this in the output:

image

This time lines are visible. And the problem is solved.

3 comments:

  1. Late, I know - but Thank You!

    I assembled it into a class, if anybody is interested:

    ----- snippet starts here ----
    private class OutputWindowWriter : TextWriter
    {
    private IVsOutputWindow outputWindow;
    private IVsOutputWindowPane outputWindowPane;

    public OutputWindowWriter(DTE dte, Guid? outputPaneGuid = null, string outputPaneName = null)
    {
    this.DTE = dte;
    if (outputPaneGuid != null && outputPaneGuid.HasValue == true)
    {
    this.OutputPaneGuid = outputPaneGuid.Value;
    this.OutputPaneName = string.IsNullOrEmpty(outputPaneName) ? this.OutputPaneGuid.ToString() : outputPaneName;
    }
    else
    {
    this.OutputPaneGuid = VSConstants.GUID_OutWindowGeneralPane;
    this.OutputPaneName = string.Empty;
    }
    }

    public Guid OutputPaneGuid
    {
    get;
    private set;
    }

    public string OutputPaneName
    {
    get;
    private set;
    }

    private DTE Dte
    {
    get;
    set;
    }

    private IVsOutputWindow OutputWindow
    {
    get
    {
    if (outputWindow == null)
    {
    if (this.Dte == null)
    {
    return null;
    }

    Microsoft.VisualStudio.OLE.Interop.IServiceProvider provider = this.Dte as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    IServiceProvider services = new ServiceProvider(provider);

    IVsOutputWindow output = services.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;

    outputWindow = services.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
    }

    return outputWindow;
    }
    }

    private IVsOutputWindowPane OutputWindowPane
    {
    get
    {
    if (outputWindowPane == null)
    {
    IVsOutputWindowPane pane;
    Guid generalPaneGuid = this.OutputPaneGuid;
    this.OutputWindow.GetPane(ref generalPaneGuid, out pane);

    if (pane == null)
    {
    this.OutputWindow.CreatePane(ref generalPaneGuid, this.OutputPaneName, 1, 1);
    this.OutputWindow.GetPane(ref generalPaneGuid, out pane);
    }

    outputWindowPane = pane;
    }

    return outputWindowPane;
    }
    }

    public override Encoding Encoding
    {
    get { return Encoding.UTF8; }
    }

    public override void Close()
    {
    if (outputWindow != null)
    {
    if (outputWindowPane != null)
    {
    outputWindow.DeletePane(this.OutputPaneGuid);
    outputWindowPane = null;
    }

    outputWindow = null;
    }

    base.Close();
    }

    ---- will be continued in next comment ---

    ReplyDelete
    Replies
    1. Sorry, the void Close() override should look like this:

      public override void Close()
      {
      if (outputWindow != null)
      {
      if (outputWindowPane != null)
      {
      if(this.OutputPaneGuid != VSConstants.GUID_OutWindowGeneralPane)
      {
      outputWindow.DeletePane(this.OutputPaneGuid);
      }
      outputWindowPane = null;
      }

      Delete
  2. ---- continued from previous comment ----

    public override void Write(string message)
    {
    string errorTemplate = "WARNING: Could not log '" + message + "' to the '"
    + (string.IsNullOrEmpty(this.OutputPaneName) ? "General" : this.OutputPaneName)
    + "' output window: {0}";

    if (this.Dte == null)
    {
    Trace.WriteLine(string.Format(errorTemplate, "Given DTE object is null."));
    return;
    }

    if (this.OutputWindow == null)
    {
    Trace.WriteLine(string.Format(errorTemplate, "Could not get output window."));
    return;
    }

    if (this.OutputWindowPane == null)
    {
    // ideally, throw an exception and/or trace/output...
    Trace.WriteLine(string.Format(errorTemplate, "Could not get output window pane."));
    return;
    }

    // wrap attempts to write in an error handler:
    if (ErrorHandler.Failed(this.OutputWindowPane.OutputString(message)) == true)
    {
    // ideally, throw an exception and/or trace/output...
    Trace.WriteLine(string.Format(errorTemplate, "Could not write to the output window pane."));
    }
    }

    public override void Write(char message)
    {
    this.Write(message.ToString());
    }

    public void ShowOutputWindowPane()
    {
    string errorTemplate = "WARNING: Could not show the '"
    + (string.IsNullOrEmpty(this.OutputPaneName) ? "General" : this.OutputPaneName)
    + "' output window: {0}";

    if (this.Dte == null)
    {
    Trace.WriteLine(string.Format(errorTemplate, "Given DTE object is null."));
    return;
    }

    if (this.OutputWindow == null)
    {
    Trace.WriteLine(string.Format(errorTemplate, "Could not get output window."));
    return;
    }

    if (this.OutputWindowPane == null)
    {
    // ideally, throw an exception and/or trace/output...
    Trace.WriteLine(string.Format(errorTemplate, "Could not get output window pane."));
    return;
    }

    // wrap attempts to write in an error handler:
    if (ErrorHandler.Failed(this.OutputWindowPane.Activate()) == true)
    {
    // ideally, throw an exception and/or trace/output...
    Trace.WriteLine(string.Format(errorTemplate, "Could not activate the output window pane."));
    }
    }
    }

    ----- snippet ends ----

    ReplyDelete