在Visual Studio中开发插件时,经常需要考虑如何提高代码编辑的效率。本文将介绍如何开发一个插件,该插件使用鼠标拇指按钮在C++、Visual Basic和F#语言中进行代码导航。这个插件是Visual Studio 2008版本插件的改进版,它利用了Visual Studio 2010的新特性,提供了一种更简洁的方式来监听鼠标事件。
在Visual Studio2008的旧版本插件中,使用了鼠标钩子来响应鼠标事件。在开发Visual Studio 2010版本时,发现了一个更优雅的解决方案:使用NativeWindow类。这个类提供了对窗口过程的低级封装,是一个使用SetWindowLong()和GWL_WNDPROC进行WinAPI子类化的包装器。WndProc是从子类窗口过程调用的。由于插件运行在Visual Studio进程中,因此可以使用NativeWindow。
要使用这个插件,可以通过右键点击函数名,然后选择“Go to definition”来跳转到函数定义。使用鼠标的“后退按钮”可以返回。
插件的样板是通过Addin-wizard创建的,它创建了一个名为Connect的类。IDTExtensibility2接口提供了诸如OnConnect或OnDisconnection之类的插件事件。OnConnect提供了DTE2对象作为参数。主要的问题是如何获取鼠标事件。插件API只支持键盘和高级事件(以及其他一些事件,如焦点)。在论坛上,找到了一个干净的解决方案来拦截主IDE窗口的消息过程:NativeWindow类。
在VS2010中,文本编辑器区域不再是VS2008中的子窗口,而是直接在主IDE窗口内绘制。因此,需要子类的窗口是主IDE窗口。AssignHandle(HWND_IDE_WINDOW)执行了这一步。主窗口的HWND ID存储在DTE2.MainWindow.HWnd中。
SubclassedWindow类继承自NativeWindow,如下所示:
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是在OnConnection方法中创建的。WndProc方法拦截了主窗口的所有窗口消息。鼠标拇指按钮的值是WM_XBUTTONUP,表示按钮释放。ActiveDocument.Language包含了当前活动文档的语言作为字符串。如果活动文档包含C++、Visual Basic或F#代码,将使用ExecuteCommand执行View.NavigateBackward或View.NavigateForward。这将触发与VS菜单中的“导航前进/后退”按钮相同的功能。
以下是插件的完整源代码:
/*
* MouseThumbButtonsVS2010
*
* Copyright 2010, Jochen Baier, email@jochen-baier
* License: The Code Project Open License (CPOL) 1.02
*
* Addin for VS2010 providing support for forward/backward navigation with the
* mouse thumb buttons in C++, Visual Basic and F#.
*
*/
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
}
}