Thursday, March 31, 2011

Visual Studio Isolated Shell: ToolWindowPane close and its detection

Currently I'm using C# every day. And sometimes I'm coming across situation when solution isn't obvious or easy for me to find. Maybe I’ve just used wrong keywords when performed my search queries but I still hope that this can be useful for someone (at least for me to remember those things and recall them if necessary in the future). I'm going to post such situations here (maybe not only them in the future, but currently it is the main idea of this blog). And since I’ve been working with Visual Studio 2010 Isolated Shell recently, I’m going to start with it.

Here it is the first problem.

What do we have? We have package for VS (Visual Studio) 2010 Isolated Shell mode. It was created for custom project type in VS 2005 then it was migrated to VS 2008 and then changed to support VS 2010 Isolated Shell.

What should we do? One of the tasks was to create wizard based on Microsoft.VisualStudio.Shell.ToolWindowPane. This wizard should query user for some data and create appropriate files after it finishes.


Obviously we should have ability to
  1. Close window when user presses Finish or Cancel buttons
  2. Detect when user closes the wizard

Actually the first was solved quite easily just with one small surprise. I’ve used following code inside my wizard close method:


      IVsWindowFrame windowFrame = ( IVsWindowFrame )this.Frame;

      windowFrame.CloseFrame( 0 );

It used to work as a package inside VS 2008 without problems. But for some reason it doesn’t anymore in VS 2010 Isolated Shell. After short investigation and recalling that VS actually never closes ToolWindowPane’s window until VS gets closed (it just hides tool window) I changed CloseFrame method with Hide and it worked after that:

      IVsWindowFrame windowFrame = ( IVsWindowFrame )this.Frame;
      windowFrame.Hide();

The second one wasn’t that obvious and it took a while to find out how it can be performed.
The first attempt was to use protected virtual void OnClose(); derived from WindowPane class. Unfortunately, as I already mentioned, VS actually never closes tool windows if it was opened. And my overridden OnClose method was actually called when I was closing Visual Studio and my wizard didn’t perform steps which were supposed to be done during close operation.
After that I went to google. But I was a little bit disappointed. The most relevant posts were saying that it is bad design to allow ToolWindow to actually edit something. It is not its purpose. Well, probably I can agree with that, but I don’t need file for this thing too, some it is not an editor too. Plus I had that requirement and didn’t believe that it is impossible to archive (however I was close to it).

The answer is quite simple. I’ve used IVsWindowFrame whenever I wanted to do something with frame. But there is IVsWindowFrame2 interface which introduces three new methods: Advise, Unadvise and ActivateOwnerDockedWindow. The last one wasn’t interesting for me, but the rest of them were. Advise and Unadvise are like event objects in C# - one allows to subscribe for certain events the other unsubscribe. The only problem was that IVsWindowFrameNotify was handling docking, move, show and size events. And that’s all. How could they do this to me? Why? But MS went theirs usual road (at least for vs extensibility case) – they added IVsWindowFrameNotify2 interface and that interface has just one member – OnClose. So the only thing I needed to do was implement object that implements IVsWindowFrameNotify and IVsWindowFrameNotify2 interfaces (they are not derived from each other as I could expect) and put Advise call at appropriate position. And the code looked like this:

    public override void OnToolWindowCreated()
    {
      IVsWindowFrame2 frame = ( this.Frame as IVsWindowFrame2 );
      uint pdwCookie;
      WizardNotify notifyObject = new WizardNotify( this );
      frame.Advise( notifyObject, out pdwCookie );
    }

I have placed my calls inside OnToolWindowCreated method to ensure it is called when tool window gets created. I didn’t want to unsubscribe from those events, so I didn’t need to store that cookie output.
And my WizardNotify class looked like this:

  class WizardNotify : IVsWindowFrameNotify, IVsWindowFrameNotify2
  {
    #region Members
    /// <summary>
    /// Parent window tool window.
    /// </summary>
    private WizardToolWindow _window;
    #endregion

    #region Methods
    /// <summary>
    /// Initializes new instance of the tool window.
    /// </summary>
    /// <param name="window">Parent tool window.</param>
    public WizardNotify( WizardToolWindow window )
    {
      if( window == null )
        throw new ArgumentNullException( "window" );

      _window = window;
    }
    #endregion

    #region IVsWindowFrameNotify Members

    public int OnDockableChange( int fDockable )
    {
      return VSConstants.S_OK;
    }

    public int OnMove()
    {
      return VSConstants.S_OK;
    }

    public int OnShow( int fShow )
    {
      return VSConstants.S_OK;
    }

    public int OnSize()
    {
      return VSConstants.S_OK;
    }

    #endregion

    #region IVsWindowFrameNotify2 Members
    /// <summary>
    /// Notifies the VSPackage that a window frame is closing and tells
    /// the environment what action to take.
    /// </summary>
    /// <param name="pgrfSaveOptions">Specifies options for saving
    /// window content.</param>
    /// <returns></returns>
    public int OnClose( ref uint pgrfSaveOptions )
    {
      MessageBox.Show( "Close", "Notify" );
      return VSConstants.S_OK;
    }

    #endregion
  }

Note: don’t forget to return VSConstants.S_OK in all method instead of keeping throw new NotImplementedException() :)




1 comment: