Moving Code Blocks Among Code Regions using VS 2010 Extensions
(翻译)使用VS 2010 扩展性将代码块移至Region区域中
[译注:以上两个链接需登录CodeProject]
背景
为了通过将代码块放置在有逻辑的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 名称列表的面板。
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、 处理菜单项点击事件
将命令(按钮)从工具菜单移到上下文菜单
正因为规范代码的过程和代码编辑器息息相关,我认为放置命令按钮较为理想的地方是上下文菜单而不是工具菜单。但是正如你所看到的,默认的命令放置处是工具菜单,在解决方案资源管理器中,打开文件MoveToRegionVSX.vsct ,找到以下代码块:
1 <!-- In this section you can define new menu groups. A menu group is a 2 container for other menus or buttons (commands); from a visual point of view 3 you can see the group as the part of a menu contained between two lines. The 4 parent of a group must be a menu. --> 5 <Groups> 6 <Group guid="guidMoveToRegionVSXCmdSet" id="MyMenuGroup" priority="0x0600"> 7 <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/> 8 </Group> 9 </Groups>
从这个位置可以看出,我们的命令的父菜单是通过Parent id确定的。
IDM_VS_MEUN_TOOLS指工具菜单
IDM_VS_CTXT_CODEWIN指上下文菜单
因此只要简单地用IDM_VS_CTXT_CODEWIN取代IDM_VS_MEUN_TOOLS就可以了,事实证明也确实如此。
创建工具窗
在解决方案资源管理器(Solution explorer)中打开文件MyControl.xaml,你将会看到下图:
删除按钮(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语句:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.Design; 4 using System.Diagnostics; 5 using System.Globalization; 6 using System.Runtime.InteropServices; 7 using Microsoft.VisualStudio.Editor; 8 using Microsoft.VisualStudio.Shell; 9 using Microsoft.VisualStudio.Shell.Interop; 10 using Microsoft.VisualStudio.Text; 11 using Microsoft.VisualStudio.Text.Editor; 12 using Microsoft.VisualStudio.TextManager.Interop;
加载Regions
我们的想法很简单,首先需要获取目前活动的代码视口(code viewer),然后遍历该视口内所有的代码,查找所有关键字#region,对于每一个region,读取它的name[译注:关键字后面的标示],添加name到一个string list中,然后用string list填充list box。
因此,首先,我们将在MoveToRegionVSXPackage
类中
申明以下成员:
1 public sealed class MoveToRegionVSXPackage : Package 2 { 3 //<gouda> 4 //The current active editor's view info 5 private IVsTextView currentTextView; 6 private IVsUserData userData; 7 private IWpfTextViewHost viewHost; 8 private string allText; 9 private const string keyword = "#region "; 10 //</gouda>
现在,我们就来实现方法GetRegions():
1 /// <summary> 2 /// Parse(解析) all the text in the active editor's view and get all regions 3 /// </summary> 4 /// <returns> list of strings containing the names of the existing regions </returns> 5 internal List<string> GetRegions() 6 { 7 List<string> regionsList = new List<string>(); 8 userData = currentTextView as IVsUserData; 9 if (userData == null)// no text view 10 { 11 Console.WriteLine("No text view is currently open"); 12 return regionsList; 13 } 14 // In the next 4 statements, I am trying to get access to the editor's view 15 object holder; 16 Guid guidViewHost = DefGuidList.guidIWpfTextViewHost; 17 userData.GetData(ref guidViewHost, out holder); 18 viewHost = (IWpfTextViewHost)holder; 19 //Now, I will load all the text of the editor to detect the key word "#region" 20 allText = viewHost.TextView.TextSnapshot.GetText(); 21 string[] regionDelimitedCode = System.Text.RegularExpressions.Regex.Split(allText, 22 "\s#region\s", //'s' means any white space character e.g. , space, , , etc 23 System.Text.RegularExpressions.RegexOptions.IgnoreCase); 24 for (int index = 1/*skip first block*/; index < regionDelimitedCode.Length; index++) 25 { 26 regionsList.Add(regionDelimitedCode[index].Split(' ')[0]); 27 } 28 return regionsList; 29 }
现在假设我们已经取得了regions列表,所以我们能组装list box了,为了这个目的,我在MyControl
类中
添加了一个名叫PopulateList
的简单方法。
1 /// <summary> 2 /// Populates the list box with the list of regions found in the current active view 3 /// </summary> 4 /// <param name="regionsList"> list of strings holding the names of regions </param> 5 /// <param name="myPkg"> reference to my package (MoveToRegion Package)</param> 6 public void PopulateList(List<string> regionsList, MoveToRegionVSXPackage myPkg) 7 { 8 //Here is the best place to initialise the packageRef 9 //I need that reference to enable calling the method MoveToRegion later on double click 10 //Unless it is logically to do this initialization in the constructor, 11 //this cannot be done 12 //because we do not create instance of that class directly 13 if(packageRef == null) 14 packageRef = myPkg; 15 lstbxRegions.Items.Clear(); 16 foreach (string s in regionsList) 17 lstbxRegions.Items.Add(s); 18 }
可以看到,我们使用MoveToReionVSXPackage
类中的方法
GetRegions
能重新得到所有的regions,然后我们使用MyControl类中的PopulateList方法将这些regions组装到list box中,那么,如何传递由前一个方法所返回的regions list到后一个方法呢?
解决方案是如下所示的ShowToolWindow方法。为了更利于解释,我们来看看这个方法是如何实现的。
这个由向导生成的方法主体如下:
1 /// <summary> 2 /// This function is called when the user clicks the menu item that shows the 3 /// tool window. See the Initialize method to see how the menu item is associated to 4 /// this function using the OleMenuCommandService service and the MenuCommand class. 5 /// </summary> 6 private void ShowToolWindow(object sender, EventArgs e) 7 { 8 // Get the instance number 0 of this tool window. This window is single 9 // instance so this instance is actually the only one. 10 // The last flag is set to true so that if the tool window does not exists 11 // it will be created. 12 ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true); 13 if ((null == window) || (null == window.Frame)) 14 { 15 throw new NotSupportedException(Resources.CanNotCreateWindow); 16 } 17 IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; 18 Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); 19 }
方法FindToolWindow
将
MyToolWindow
类型
作为第一个形参,通过调用这一方法,我们得到了
window
对象,
这一方法的调用同时也调用了
MyToolWindow
类的无参构造函数,如果你看一看这个类,你会发现它仅有这么一个无参构造函数,这个无参构造函数反过来也生成了
MyControl
类的一个新实例
,
这个实例包含一个
Content
属性
.
这就是链接
list box
和菜单命令的关键。[译注:这一段话我不太懂,可以参见文末的原文链接,希望博友能提供更好的翻译]
因此,在获取window
之后,我将会把
Content
属性转换成
MyControl
类型并且调用
PopulateList
方法:
1 ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true); 2 //<gouda> 3 List<string> regionsList = this.GetRegions(); 4 //call the method PopulateList which is member in MyControl class 5 //So, cast the Content property of window to MyControl 6 ((MyControl)window.Content).PopulateList(regionsList, this); 7 //</gouda>
移动选中的代码块到选中的Region中
这儿我的想法是检测被选中的代码块,一旦我们初始化viewHost,这个任务就会相当的简单(待会我将会说明什么时候在哪儿初始化viewHost)
初始化viewHost之后,我们能访问它的TextView属性,这个属性将会提供许多有用的的属性和方法。
请看下面代码:
1 /// <summary> 2 /// Moves the selected text to the given regionName 3 /// If no selection, does nothing 4 /// </summary> 5 /// <param name="regionName"> The name of the destination region </param> 6 internal void MoveToRegion(string regionName) 7 { 8 if (viewHost.TextView.Selection.IsEmpty) 9 return; 10 //Get the selected text 11 string selectedText = viewHost.TextView.Selection.StreamSelectionSpan.GetText(); 12 //get the selected span to delete its contents 13 Span deleteSpan = viewHost.TextView.Selection.SelectedSpans[0]; 14 //now, delete the span as its text is saved in the selectedText 15 viewHost.TextView.TextBuffer.Delete(deleteSpan); 16 //Now, I will load all the text of the editor again, because it is subject to change 17 allText = viewHost.TextView.TextSnapshot.GetText(); 18 //get the position at which region exists 19 string fullRegionName = keyword + regionName; 20 int regPos = allText.IndexOf(fullRegionName) + fullRegionName.Length; 21 //insert the selected text at the specified position 22 viewHost.TextView.TextBuffer.Insert(regPos, " " + selectedText); 23 }
处理菜单项的点击
现在,我们初始化currentTextView,这样,我们才能初始化viewHost[译注:有关viewHost为什么被初始化请看上文中GetRegions()方法],然后显示工具窗:
1 /// This function is the callback used to execute a command when the a menu item 2 /// is clicked. 3 /// See the Initialize method to see how the menu item is associated to this 4 /// function using 5 /// the OleMenuCommandService service and the MenuCommand class. 6 /// </summary> 7 private void MenuItemCallback(object sender, EventArgs e) 8 { 9 IVsTextManager txtMgr = (IVsTextManager)GetService(typeof(SVsTextManager)); 10 int mustHaveFocus = 1;//means true 11 //initialize the currentTextView 12 txtMgr.GetActiveView(mustHaveFocus, null, out currentTextView); 13 //Show the tool window 14 ShowToolWindow(sender, e); 15 }
引用
- 以下两个链接让我学到了不少东西:
http://msdn.microsoft.com/en-us/library/dd884850(VS.100).aspx - http://www.devx.com/VS_2010/Article/44073[译注:该链接好像已经失效]
译注:本人近期做VS扩展的工作,无奈资料甚少,于是从分析代码入手,这是第一次翻译别人的代码讲解,才疏学浅,一定有不少疏漏和错误,下面给出原文链接
http://www.codeproject.com/Articles/69125/Moving-Code-Blocks-Among-Code-Regions-using-VS-201
欢迎大家留言指正