Go Back 插件 for VS.NET 2003






4.74/5 (14投票s)
2005 年 3 月 20 日
8分钟阅读

76473

664
创建一个 Visual Studio 2003 插件,以返回到之前的位置。
- 下载演示项目 - 235 Kb (已编译并包含安装包)
- 下载源代码 - 25.3 Kb (仅源代码)
目录
引言
在处理大型项目时,我经常需要在许多文件中跳转到不同的位置(我一直在使用“转到定义”上下文菜单命令)。一旦我沿着特定路径导航到某处,我就想回到我最初的起点。虽然我可以在离开之前使用书签来标记我的位置,但我倾向于将书签用于其他目的(而且书签只在包含源文件的上下文中工作——你无法在源文件之间使用“编辑.下一个书签”)。有人告诉我 Visual Studio 7 具有“向后导航”命令,该命令可以做到这一点,但是,我发现它有一些我不喜欢的怪癖。
此插件提供了一种更方便的“向后导航”机制。它会监视当前文档位置的导航,并提供一种返回到前一个位置或导航链中任何位置的方法。我将文档位置定义为文档(即源文件)文件名,以及该文档中文本插入光标的行号和列号。
背景
Visual Studio 通过“转到定义”和“转到引用”上下文菜单命令提供了一种快速的代码跳转机制。还有许多其他命令可以更改源文件中的当前活动编辑位置(例如,“编辑.文档结尾”、“编辑.下一个书签”、“编辑.转到下一个位置”等)。在 Visual Basic 6 中,有一个“前一个位置”上下文菜单命令可以选择,该命令可以将您直接带回到您之前的位置。不幸的是,Visual Studio 2002 和 2003(以及 2005 Beta 版)的编辑上下文菜单不包含此支持。“编辑.转到前一个位置”命令已定义,但遗憾的是,该命令用于移动到之前标记的位置(例如,来自“在文件中查找”文本搜索)。
“向后导航”命令是 VB6 中“前一个位置”命令的替代品。它有所改进,但也有几个(我不喜欢的)怪癖。具体来说:
- 它在编辑上下文菜单中不可用(因此,要么将鼠标指针移到 VS 窗口顶部附近的标准工具栏,要么用手离开鼠标输入 'Ctrl+-'),这很不方便。
- 每个文本插入点都会被记录下来,因此当您在源文件中单击时,导航历史记录会变得相当长(我并不觉得这很有帮助——我想要的是主要的移动,而不是所有细微的移动)。
- 当代码导航打开一个源文件,然后您进行“向后导航”时,您需要选择该命令两次才能回到您真正的位置。这是记录每次文本插入更改的结果(当新文件打开时,文件开头会被记录为一个导航位置,即使您在视觉上看到的是其他位置)。
- 由于代码导航而打开的文件在您从中返回时(“向后导航”)仍保持打开状态。
使用代码
加载插件后,代码窗口的上下文菜单会得到修改,添加了两个额外的命令 - 前一个位置
和 选择位置...
。这些菜单项根据您的代码导航操作启用。我的偏好是拥有一致的菜单,因此菜单项是启用/禁用的,而不是根据命令可用性出现/消失。注意:如果您未设置在启动时加载插件的选项,菜单状态将显示为已启用。当您选择其中一个命令时,插件将被加载,Visual Studio 会对每个命令执行 QueryStatus
检查,然后它们将正确显示为禁用。
![]() |
![]() |
![]() |
不存在导航历史记录。 |
存在一个导航位置。 |
存在两个或更多导航位置。返回到最近的位置或从列表中选择。 |
如果 前一个位置
已启用然后被选中,则当前编辑位置将恢复到记住的位置。如果该文档是因代码导航而打开的(并且随后未被修改),则将被关闭。我的偏好是只保留我当前正在处理的文档,并且我认为自动关闭仅为“参考”检查而打开的文档是一种便利。
如果 选择位置...
已启用然后被选中,则会显示 PositionDialog
窗体(参见下图)。它显示了因导航命令而离开的文档位置历史记录。最近的位置显示在最前面。单击任何一行将导致选中并返回到该位置。
PositionDialog
还允许您清除记录的位置历史记录。“关闭跳过的文档”复选框决定了在返回时是否自动关闭沿途打开的任何文档。
关于代码
插件的骨架由 Visual Studio 插件项目向导创建(特别是 Connect
类)。有很多关于如何为 Visual Studio 编写插件的文章,所以我在这里就不赘述基础知识了。
起初,我以为我可以通过处理文本编辑器 LineChanged
事件来简单地完成。事件处理程序会收到一个指示更改性质的值(参见 MSDN 的值表)。vsTextChangedCaretMoved
值(插入点已移动)表明这会很好地工作。然而,LineChanged
事件仅在行的文本发生更改且您移开该行时触发。这可能是我没有按照希望的方式工作的原因——它会触发大量事件,并产生与“向后导航”命令相同的导航历史记录。
阅读新闻组帖子时,我发现有人也在尝试解决类似的问题,并通过为某些命令设置 BeforeExecute
和 AfterExecute
处理程序来解决。这就是我采用的方法,并且在大多数情况下它效果相当不错。在 OnConnection
方法中,我遍历一个编辑器命令列表,我希望在这些命令执行时收到通知。对于每个命令,我获取其相应的 EnvDTE.CommandEvents
并注册 BeforeExecute
和 AfterExecute
处理程序。
for( int i = 0; i < _interceptCommandNames.Length; i++ )
{
EnvDTE.Command cmd = _vsNet.Commands.Item( _interceptCommandNames[i], -1 );
if( null != cmd )
{
_commandEvents[i] = _vsNet.Events.get_CommandEvents( cmd.Guid, cmd.ID );
_commandEvents[i].BeforeExecute +=
new _dispCommandEvents_BeforeExecuteEventHandler( BeforeExecute );
_commandEvents[i].AfterExecute +=
new _dispCommandEvents_AfterExecuteEventHandler( AfterExecute );
}
}
最初,我只使用了 BeforeExecute
处理程序,它只是简单地记录了当前的文档位置。然而,在某些情况下,会请求代码导航,但实际并未发生导航。因此,AfterExecute
处理程序用于在缓存的位置被记录之前,验证当前文档位置是否已更改。PositionManager
类(下面代码中的 _pm
实例变量)负责所有文档位置的处理,这些位置内部使用 System.Collections.Stack
存储。它反过来依赖于 DocumentPosition
数据类,该类封装了位置信息。
private void BeforeExecute( string Guid,
int ID,
object CustomIn,
object CustomOut,
ref bool CancelDefault)
{
// Don't indicate that we've handled this command event.
CancelDefault = false;
try
{
_pm.CacheCurrentPosition();
}
catch( Exception ex )
{
DisplayInOutputWindow( ex.ToString() );
}
}
private void AfterExecute( string Guid,
int ID,
object CustomIn,
object CustomOut )
{
try
{
if( _pm.PositionHasChanged )
{
_pm.RecordCachedPosition();
}
else
{
_pm.ClearCachedPosition();
}
}
catch( Exception ex )
{
DisplayInOutputWindow( ex.ToString() );
}
}
PositionManager.RecordCachedPosition
方法执行检查,以确定当前文档位置是否需要打开文档。如果需要,它会设置 OpenedDocumentName
属性,以便在返回时关闭该文档。
public void CacheCurrentPosition()
{
_position = GetCurrentPosition();
_openedDocumentCount = _vsNet.Documents.Count;
}
public void RecordCachedPosition()
{
//Since this method should be called after some code navigation
// event, any change in the document count means that a new
// document was opened as a result of the navigation. That means
// its not a working document and can be closed (if its not later
// modified) when returning back to the original location.
if( _openedDocumentCount != _vsNet.Documents.Count )
{
DocumentPosition newPosition = GetCurrentPosition();
_position.OpenedDocumentName = newPosition.DocumentName;
}
_positionStack.Push( _position );
}
选择 前一个位置
或 选择位置...
命令之一会导致调用 Connect.Exec
方法,该方法必须决定实际正在处理哪个命令,然后采取相应行动。下面显示了该方法的相关代码。
bool closeOnReturn = true;
DocumentPosition position = null;
if( COMMAND_PREVIOUS_LOCATION == commandName )
{
position = _pm.PreviousPosition;
}
if( COMMAND_CHOOSE_LOCATION == commandName )
{
PositionDialog pd = new PositionDialog();
pd.InitializeForm( _pm );
if( DialogResult.OK == pd.ShowDialog() )
{
position = pd.SelectedPosition;
closeOnReturn = pd.CloseSkippedDocuments;
}
}
if( null != position )
{
MoveToPosition( position, closeOnReturn );
}
handled = true;
Connect.MoveToPosition
方法非常直接。它负责在返回时关闭文档。唯一需要注意的是调用 MoveToDisplayColumn
并传入行号和列号。这会将光标放置在正确的位置(否则,列号将被视为从行开头开始的字符数)。
private void MoveToPosition( DocumentPosition position, bool closeDocument )
{
if( closeDocument && ( 0 < position.OpenedDocumentName.Length ) )
{
Document leavingDoc = _vsNet.Documents.Item( position.OpenedDocumentName );
if( ( null != leavingDoc ) && leavingDoc.Saved )
{
leavingDoc.Close( EnvDTE.vsSaveChanges.vsSaveChangesPrompt );
}
}
//Move to the specified position. First activate the document, and
// then move to the original line number and column number.
Document doc = _vsNet.Documents.Item( position.DocumentName );
if( null != doc )
{
doc.Activate();
TextSelection ts = (TextSelection)_vsNet.ActiveDocument.Selection;
ts.MoveToDisplayColumn( position.LineNumber,
position.ColumnNumber,
false );
}
}
限制
Visual Studio 中存在一些代码导航机制,它们的关联事件目前未被捕获。如果您使用其中一种机制导航离开了当前的编辑位置,则没有拦截事件机制可以在移动到所需位置之前捕获当前位置(因此此插件将无法将您带回到起始位置)。这可能导致一种误导情况,即此插件的菜单项已被之前的代码导航事件启用,而选择 前一个位置
会将您带回到一个更早的文档位置,而不是您以为要回去的位置。
具体来说,目前未捕获的导航事件是:
- 单击“查找”对话框的“查找下一个”按钮(尽管
Edit.FindNext
命令事件已被捕获,但该命令似乎并未通过按钮单击来执行)。 - 在成员定义组合框中选择一个名称。
- 单击查找窗口结果或任务列表项。
学到的教训
我发现开发 VS.NET 插件用户界面非常不方便(特别是工具栏命令——对话框窗体与常规 WinForm 应用程序没有区别)。插件的用户界面控件仅创建一次(在插件设置期间),我无法为其设置断点。创建后,就没有方便的机制来删除这些工具栏按钮。我最终不得不这样做:
- 使用插件管理器卸载插件并退出 Visual Studio。
- 删除插件 DLL 文件。
- 重新启动 Visual Studio,然后在插件安装的(每个)命令按钮上单击。Visual Studio 会抱怨插件无法正常工作,并询问您是否要删除该插件(单击“确定”)。
- 重新创建插件的注册表项和值。(此插件的注册表项为 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AddIns\GoBackAddin.Connect),然后重新运行 Visual Studio 创建的 ReCreateCommands.reg 文件。
- 重新启动 Visual Studio 并通过插件管理器加载插件。
- 当以上方法不起作用时,请在命令窗口中使用命令 devenv /setup。
修订历史
- 2005-03-20:
已根据 mav.northwind 的反馈修改了介绍性文本。
- 2005-03-19:
初始版本。