VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

时间:2023-03-09 20:17:50
VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

Moving Code Blocks Among Code Regions using VS 2010 Extensions

(翻译)使用VS 2010 扩展性将代码块移至Region区域中

[译注:以上两个链接需登录CodeProject]

VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

背景

为了通过将代码块放置在有逻辑的Region区域中,规范已经写好的代码,依照Visual Studio 目前现有的机制,你需要完成以下工作:

1、 生成 Regions

2、 对于每一个代码块,都要进行以下操作

  A、 选中代码块

  B、 执行剪切操作

  C、 导航到需要的Region,或者通过Region名查找它

  D、粘贴代码块到这个Region中

而自从我实现了 MoveToRegionVXS 这一扩展包之后,只需采用以下步骤:

1、 生成Regions

2、 右击代码编辑器选择 Move to Region 命令,将会弹出一个工具窗,展示一个包含所哟Regions的列表

3、 对于每一个代码块,执行以下步骤:

  A、 选中代码块

  B、 双击工具窗中的目标Region

项目前准备

扩展Visual Studio 既有趣又有挑战性,第一步是要选择正确的项目模板,为了开发这个工具,我决定使用Visual Studio Package机制,使用C#语言开发。接下了,我将一步步的解释如何开发这个工具。

1、 打开Visual Studio,选择新建项目…

2、 选择C#作为开发语言

3、 在“其他项目类型”中选择“扩展性”

4、 选择“Visual Studio Package”

5、 输入Package所在项目名称后点确定,前两个向导界面都点“Next”

6、 在company文本框中,我建议你输入你的名字,这个将作为你的Package的命名空间,并且你需要给你的Package起一个不易混淆的名字,在这里我用MoveToRegionVSX命名

7、 这一步将相当重要,在下图所示的界面中,勾选Menu Command 和 Tool Window。

Menu Command被用来在上下文菜单(即右键菜单)中调用菜单命令,Tool Window能够展示一个包含所有Region 名称列表的面板。

VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

1、 输入命令的名称和命令的ID为“Move To Region”

2、 输入窗口的名称和窗口的ID为“Select Region”

3、 向导的最后一步中,不要勾选“test projects”

信不信由你,你已经完成了Visual Studio 2010的扩展,按下F5将会打开Visual Studio的新实例,点击 工具->Move To Region 将会打开一个信息框(Message Box),这个就是你所做的Visual Studio 扩展。

现在,我们已经准备好实现它的功能了,但是,首先,请让我将这个大任务分解:

1、 将Move To Region命令由工具菜单(Tools Meun)移动到上下文菜单(Context Meun)中去

2、 建立工具窗口

3、 添加必要的程序集和命名空间

4、 加载 Regions

5、 移动选中的代码到选中的Region中

6、 处理菜单项点击事件

将命令(按钮)从工具菜单移到上下文菜单

VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

正因为规范代码的过程和代码编辑器息息相关,我认为放置命令按钮较为理想的地方是上下文菜单而不是工具菜单。但是正如你所看到的,默认的命令放置处是工具菜单,在解决方案资源管理器中,打开文件MoveToRegionVSX.vsct ,找到以下代码块:

 <!-- In this section you can define new menu groups. A menu group is a
container for other menus or buttons (commands); from a visual point of view
you can see the group as the part of a menu contained between two lines. The
parent of a group must be a menu. -->
<Groups>
<Group guid="guidMoveToRegionVSXCmdSet" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
</Groups>

从这个位置可以看出,我们的命令的父菜单是通过Parent id确定的。

IDM_VS_MEUN_TOOLS指工具菜单

IDM_VS_CTXT_CODEWIN指上下文菜单

因此只要简单地用IDM_VS_CTXT_CODEWIN取代IDM_VS_MEUN_TOOLS就可以了,事实证明也确实如此。

创建工具窗

在解决方案资源管理器(Solution explorer)中打开文件MyControl.xaml,你将会看到下图:

VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

删除按钮(button)和标签(label),添加一个列表容器控件(list box),命名为lstbxRegions,这个控件将会容纳Regions列表。(注意这是一个WPF控件,因此,这个控件没有ID,只有name属性)。

调整列表容器控件的大小,使其和用户控件一样大,即宽=200,高=300(width=200、height=300)

现在当我们点击“Move to Region”命令,让我们展示一下工具窗(Tool Window)。

为了实现这个目的,我们需要完成以下步骤:

1、 从解决方资源管理器中打开文件"MoveToRegionVSXPackage.cs"

2、 找到MenuItemCallback 事件句柄(event handler)[译注:事件处理程序,下面有函数代码]正如他名字所陈述的,当你点击”Move to Region”命令时正是这个事件句柄被调用

3、 将函数主体用简单地对于另一个函数ShowToolWindow 的调用替代

private void MenuItemCallback(object sender, EventArgs e)
{
//Show the tool window
ShowToolWindow(sender, e);
}

运行它,你就会发现工具窗出现了。

注意第一次运行的时候,你可能会发现工具窗比列表容器控件稍大,请你调整它的大小,只需调整这一次,它将会保存它的大小,下一次运行时就不会出现这一问题。

添加必要的程序集和命名空间

添加对下列命名空间的引用:

Microsoft.VisualStudio.CoreUtility

Microsoft.VisualStudio.Editor

Microsoft.VisualStudio.Text.Data

Microsoft.VisualStudio.Text.Logic

Microsoft.VisualStudio.Text.UI

Microsoft.VisualStudio.Text.UI.Wpf

在文件"MoveToRegionVSXPackage.cs"中,使用下列using语句代替已存在的using语句:

 using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;

加载Regions

我们的想法很简单,首先需要获取目前活动的代码视口(code viewer),然后遍历该视口内所有的代码,查找所有关键字#region,对于每一个region,读取它的name[译注:关键字后面的标示],添加name到一个string list中,然后用string list填充list box。

因此,首先,我们将在MoveToRegionVSXPackage类中申明以下成员:

 public sealed class MoveToRegionVSXPackage : Package
{
//<gouda>
//The current active editor's view info
private IVsTextView currentTextView;
private IVsUserData userData;
private IWpfTextViewHost viewHost;
private string allText;
private const string keyword = "#region ";
//</gouda>

现在,我们就来实现方法GetRegions():

 /// <summary>
/// Parse(解析) all the text in the active editor's view and get all regions
/// </summary>
/// <returns> list of strings containing the names of the existing regions </returns>
internal List<string> GetRegions()
{
List<string> regionsList = new List<string>();
userData = currentTextView as IVsUserData;
if (userData == null)// no text view
{
Console.WriteLine("No text view is currently open");
return regionsList;
}
// In the next 4 statements, I am trying to get access to the editor's view
object holder;
Guid guidViewHost = DefGuidList.guidIWpfTextViewHost;
userData.GetData(ref guidViewHost, out holder);
viewHost = (IWpfTextViewHost)holder;
//Now, I will load all the text of the editor to detect the key word "#region"
allText = viewHost.TextView.TextSnapshot.GetText();
string[] regionDelimitedCode = System.Text.RegularExpressions.Regex.Split(allText,
"\\s#region\\s", //'\s' means any white space character e.g. \t, space, \n, \r, etc
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
for (int index = /*skip first block*/; index < regionDelimitedCode.Length; index++)
{
regionsList.Add(regionDelimitedCode[index].Split('\r')[]);
}
return regionsList;
}

现在假设我们已经取得了regions列表,所以我们能组装list box了,为了这个目的,我在MyControl类中添加了一个名叫PopulateList 的简单方法。

 /// <summary>
/// Populates the list box with the list of regions found in the current active view
/// </summary>
/// <param name="regionsList"> list of strings holding the names of regions </param>
/// <param name="myPkg"> reference to my package (MoveToRegion Package)</param>
public void PopulateList(List<string> regionsList, MoveToRegionVSXPackage myPkg)
{
//Here is the best place to initialise the packageRef
//I need that reference to enable calling the method MoveToRegion later on double click
//Unless it is logically to do this initialization in the constructor,
//this cannot be done
//because we do not create instance of that class directly
if(packageRef == null)
packageRef = myPkg;
lstbxRegions.Items.Clear();
foreach (string s in regionsList)
lstbxRegions.Items.Add(s);
}

可以看到,我们使用MoveToReionVSXPackage类中的方法GetRegions能重新得到所有的regions,然后我们使用MyControl类中的PopulateList方法将这些regions组装到list box中,那么,如何传递由前一个方法所返回的regions list到后一个方法呢?

解决方案是如下所示的ShowToolWindow方法。为了更利于解释,我们来看看这个方法是如何实现的。

这个由向导生成的方法主体如下:

 /// <summary>
/// This function is called when the user clicks the menu item that shows the
/// tool window. See the Initialize method to see how the menu item is associated to
/// this function using the OleMenuCommandService service and the MenuCommand class.
/// </summary>
private void ShowToolWindow(object sender, EventArgs e)
{
// Get the instance number 0 of this tool window. This window is single
// instance so this instance is actually the only one.
// The last flag is set to true so that if the tool window does not exists
// it will be created.
ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), , true);
if ((null == window) || (null == window.Frame))
{
throw new NotSupportedException(Resources.CanNotCreateWindow);
}
IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
}

方法FindToolWindow MyToolWindow 类型作为第一个形参,通过调用这一方法,我们得到了window 对象,这一方法的调用同时也调用了MyToolWindow类的无参构造函数,如果你看一看这个类,你会发现它仅有这么一个无参构造函数,这个无参构造函数反过来也生成了MyControl类的一个新实例,这个实例包含一个Content属性.这就是链接list box和菜单命令的关键。[译注:这一段话我不太懂,可以参见文末的原文链接,希望博友能提供更好的翻译]

因此,在获取window之后,我将会把Content属性转换成MyControl类型并且调用PopulateList方法:

 

 ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), , true);
//<gouda>
List<string> regionsList = this.GetRegions();
//call the method PopulateList which is member in MyControl class
//So, cast the Content property of window to MyControl
((MyControl)window.Content).PopulateList(regionsList, this);
//</gouda>

移动选中的代码块到选中的Region中

这儿我的想法是检测被选中的代码块,一旦我们初始化viewHost,这个任务就会相当的简单(待会我将会说明什么时候在哪儿初始化viewHost)

初始化viewHost之后,我们能访问它的TextView属性,这个属性将会提供许多有用的的属性和方法。

请看下面代码:

 /// <summary>
/// Moves the selected text to the given regionName
/// If no selection, does nothing
/// </summary>
/// <param name="regionName"> The name of the destination region </param>
internal void MoveToRegion(string regionName)
{
if (viewHost.TextView.Selection.IsEmpty)
return;
//Get the selected text
string selectedText = viewHost.TextView.Selection.StreamSelectionSpan.GetText();
//get the selected span to delete its contents
Span deleteSpan = viewHost.TextView.Selection.SelectedSpans[];
//now, delete the span as its text is saved in the selectedText
viewHost.TextView.TextBuffer.Delete(deleteSpan);
//Now, I will load all the text of the editor again, because it is subject to change
allText = viewHost.TextView.TextSnapshot.GetText();
//get the position at which region exists
string fullRegionName = keyword + regionName;
int regPos = allText.IndexOf(fullRegionName) + fullRegionName.Length;
//insert the selected text at the specified position
viewHost.TextView.TextBuffer.Insert(regPos, "\r" + selectedText);
}

处理菜单项的点击

现在,我们初始化currentTextView,这样,我们才能初始化viewHost[译注:有关viewHost为什么被初始化请看上文中GetRegions()方法],然后显示工具窗:

 /// This function is the callback used to execute a command when the a menu item
/// is clicked.
/// See the Initialize method to see how the menu item is associated to this
/// function using
/// the OleMenuCommandService service and the MenuCommand class.
/// </summary>
private void MenuItemCallback(object sender, EventArgs e)
{
IVsTextManager txtMgr = (IVsTextManager)GetService(typeof(SVsTextManager));
int mustHaveFocus = ;//means true
//initialize the currentTextView
txtMgr.GetActiveView(mustHaveFocus, null, out currentTextView);
//Show the tool window
ShowToolWindow(sender, e);
}

引用

译注:本人近期做VS扩展的工作,无奈资料甚少,于是从分析代码入手,这是第一次翻译别人的代码讲解,才疏学浅,一定有不少疏漏和错误,下面给出原文链接

http://www.codeproject.com/Articles/69125/Moving-Code-Blocks-Among-Code-Regions-using-VS-201

欢迎大家留言指正