Introduction
Visual Studio 2010 lacks support to navigate in the source code using the mouse thumb buttons in the languages C++, Visual Basic and F#. The addin from this article will provide support for navigation in the source code using the mouse thumb buttons and it will show how to get mouse events inside an addin.
Background
This addin is an improved version of the addin for Visual Studio 2008: Forward/Backward navigation with the mouse thumb buttons for Visual Studio 2008 (C++). In the old addin, I used a mouse hook to react to mouse events. During the development for Visual Studio 2010, I found a much cleaner solution to listen to mouse events: NativeWindow class.
Usage
- Jump to a function definition (right click on the function name, "Go to definition").
- Jump back using the "backward button" of your mouse.
Implementation
The addin boilerplate is created with the Addin-wizard. It creates a class named Connect
. The IDTExtensibility2
interface provides addin events like OnConnect
or OnDisconnec
t
. OnConnect
provides the DTE2 object as parameter.
The main problem was to solve how to get mouse events. The addin API only support keyboard and high level events (and some other like focus). In this forum*, I found a clean solution to intercept the main IDE window message procedure: the NativeWindow class.
It provides low-level encapsulation of the window procedure. It is a wrapper to WinAPI subclassing using SetWindowLong()
with GWL_WNDPROC. WndProc
is called from the subclass window procedure. All windows within the same process could be subclassed. Since the addin is in the process of Visual Studio, NativeWindow
could be used here. The implementation source code of NativeWindow
can be found here.
In VS2010, the text editor area is not a child window anymore as in VS2008. It is directly paint inside the main IDE window. So the window which must be subclassed is the main IDE window. AssignHandle(HWND_IDE_WINDOW)
does this step. The HWND ID from the main window is stored in DTE2.MainWindow.HWnd
.
*: The other solution discussed in the forum thread IVsBroadcastMessageEvents::OnBroadcastMessage
only provides the following messages: WM_WININICHANGE
, WM_DISPLAYCHANGE
, WM_SYSCOLORCHANGE
, WM_PALETTECHANGED
, WM_PALETTEISCHANGING
and WM_ACTIVATEAPP
.
The SubclassedWindow
class which derives from NativeWindow
looks like:
public class SubclassedWindow : NativeWindow
{
private DTE2 dte;
private const int WM_XBUTTONUP = 0x020C;
private const int XBUTTON1 = 1;
private const int XBUTTON2 = 2;
public SubclassedWindow(int hWnd, DTE2 dte)
{
this.dte = dte;
AssignHandle((IntPtr)hWnd);
}
static int HiWord(IntPtr Number)
{
return (ushort)((Number.ToInt32() >> 16) & 0xffff);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_XBUTTONUP)
{
if (this.dte.ActiveDocument != null &&
(this.dte.ActiveDocument.Language == "C/C++" ||
this.dte.ActiveDocument.Language == "Basic" ||
this.dte.ActiveDocument.Language == "F#"))
{
switch (HiWord(m.WParam))
{
case XBUTTON1:
{
try { this.dte.ExecuteCommand("View.NavigateBackward", ""); }
catch { }
} break;
case XBUTTON2:
{
try { this.dte.ExecuteCommand("View.NavigateForward", ""); }
catch { }
}
break;
}
}
}
base.WndProc(ref m);
}
}
SubclassedWindow
is created inside the OnConnection
method. The WndProc
method intercepts all window messages from the main window. The mouse thumb buttons have the value WM_XBUTTONUP
for button release. ActiveDocument.Language
contains the language of the currently active document as string
. If the active document contains C++, Visual Basic or F# code View.NavigateBackward
or View.NavigateForward
will be executed using ExecuteCommand.
This will trigger the same functionality as the Navigate Forward/Backward buttons from the VS menu.
Whole Addin Source Code
using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using System.Diagnostics;
using System.Windows.Forms;
namespace MouseThumbButtonsVS2010
{
public class SubclassedWindow : NativeWindow
{
private DTE2 dte;
private const int WM_XBUTTONUP = 0x020C;
private const int XBUTTON1 = 1;
private const int XBUTTON2 = 2;
public SubclassedWindow(int hWnd, DTE2 dte)
{
this.dte = dte;
AssignHandle((IntPtr)hWnd);
}
static int HiWord(IntPtr Number)
{
return (ushort)((Number.ToInt32() >> 16) & 0xffff);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_XBUTTONUP)
{
if (this.dte.ActiveDocument != null &&
(this.dte.ActiveDocument.Language == "C/C++" ||
this.dte.ActiveDocument.Language == "Basic" ||
this.dte.ActiveDocument.Language == "F#"))
{
switch (HiWord(m.WParam))
{
case XBUTTON1:
{
try { this.dte.ExecuteCommand("View.NavigateBackward", ""); }
catch { }
} break;
case XBUTTON2:
{
try { this.dte.ExecuteCommand("View.NavigateForward", ""); }
catch { }
}
break;
}
}
}
base.WndProc(ref m);
}
}
public class Connect : IDTExtensibility2
{
private DTE2 _applicationObject;
private AddIn _addInInstance;
private SubclassedWindow subclassedMainWindow;
public void OnConnection(object application,
ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
subclassedMainWindow = new SubclassedWindow
(_applicationObject.MainWindow.HWnd, _applicationObject);
}
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
if (subclassedMainWindow != null)
{
subclassedMainWindow.ReleaseHandle();
subclassedMainWindow = null;
}
}
#region not_used
#endregion
}
}
Limitations
The addin will only work if the text editor 'window' is docked.
History
- Initial release based on the addin for VS 2008