Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Moving Code Blocks Among Code Regions using VS Extensions

0.00/5 (No votes)
17 Jan 2017 10  
Extend VS by invoking menu item to the context menu that enables moving code blocks among code regions

Introduction

Are you familiar with the keyword #region? If yes, you can skip this introduction.

Well, let's introduce what #region is used for and how.

If you worked in a class that is big enough, say 100s of lines and 10s of methods and properties. Imagine if these methods and properties are arranged in some sort of logical groups (e.g., read only properties, read/write properties, static methods, private methods, public methods, ...) and these groups are collapsible. This way will make it easier and faster to find a specific piece of code to maintain. And it will also be easier to other developers who use, maintain, or review your code.

The keyword #region is there for this purpose. And there are 3 ways to insert and use region in your code.

  1. Type #region followed by region name, and in the next line, type #endregion, then write or paste your code in between.
  2. An easier way is to right click, select Insert Snippet, then Visual C#/VB.NET then #region.
  3. An additional option is to select a block of code, right click, and select Surround with then #region.

Background

To format an existing code by grouping code blocks in logical regions using currently available Visual Studio features, you need to follow these steps:

  1. Create the regions
  2. For each block of code, do the following:
    1. Select that block
    2. Cut the selected code
    3. Navigate to the destination region, or search for it by name
    4. Paste your code inside it

As I frequently need to do this operation, and may be many developers I think, I created MoveToRegionVSX extension to Visual Studio to make it easier and faster. With this tool, the process of formatting an existing code will be as follows:

  1. Create the regions.
  2. Right click the editor and select Move to Region, this will bring a tool window displaying a list of all existing regions.
  3. For each block of code, do the following:
    1. Select that block.
    2. Double click the destination region from the tool window just opened.

Prerequisites

  • Visual Studio 2010 or higher
  • Visual Studio 2010 SDK or higher

Preparing the Project

Creating an extension to Visual Studio is some sort of fun and challenge as well. The first step is to select the correct project template. For this tool, I used Visual Studio Package with C#. In the next few lines, I will explain step by step how to recreate this tool yourself.

  • Open Visual Studio and select new project
  • Select your language (C# was my option)
  • Select Extensibility from the template categories
  • Select Visual Studio Package from templates
  • Click next in the first 2 screens in the wizard (the first is a welcome, and the second is to select the language which is predefined)
  • In the company name field, I recommend to enter your name as this will be used as the namespace name. Also, provide a descriptive name for the package (mine is MoveToRegionVSX)
  • The next step is very important. In the additional functionality screen, select both Menu Command and Tool Window. Menu Command is needed to invoke a menu command to the context menu, and the Tool Window is to enable displaying a panel that will contain the regions list.

    MoveToRegionVSX/02.png

  • Enter the command name "Move to Region", and its command id
  • Enter the window name "Select Region", and its command id
  • In the final screen of the wizard, just uncheck the test projects.

Believe it or not, you have created an extension to Visual Studio 2010. Press F5 to believe me. It will launch a new instance of Visual Studio. Click the Tools menu, you will find a new command named "Move To Region", when clicked, it will show a message box. This is the result of the extension you just created.

Now, we are ready for the real work. But first, let me break down the mission into smaller tasks.

  • Move the command from Tools menu to Context Menu
  • Create the Tool Window
  • Add required Assemblies and Namespaces
  • Load Regions
  • Move Selected Code to Selected Region
  • Handle Menu Item Click

Move the Command from Tools Menu to Context Menu

MoveToRegionVSX/08.png

As the formatting process is related to the editor, I think the logical place for our command is the context menu rather than the Tools menu. But as you saw, the default menu used is the Tools menu. In the solution explorer, open the file MoveToRegionVSX.vsct and search for the following block.

<!-- 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>

In this portion of that file, the parent menu for our command is defined from the Parent id.

  • IDM_VS_MENU_TOOLS refers to Tools menu
  • IDM_VS_CTXT_CODEWIN refers to Context menu

So, just replace the existing parent id with that one of the Context menu and it is done.

Create the Tool Window

From the solution explorer, open the file MyControl.xaml, you will find something like this image:

MoveToRegionVSX/ToolWin.png

  • Delete the button and label
  • Add a list box and name it lstbxRegions, this will hold the regions list. (Note that this is a WPF control, so, there is no ID, rather set the name property.)
  • Adjust the dimensions of the list box to be the same as the user control. Say width=200 and height=300.

Now, let's integrate what we have so far. In other words, let's show the tool window when we click on the "Move to Region" command.

To do so, follow these steps:

  • From the solution explorer, open the file "MoveToRegionVSXPackage.cs" and find the MenuItemCallback event handler. As the name states, this is the event handler that is called when you click o "Move To Region" command.
  • Replace the body of the event handler with a simple call to the method ShowToolWindow:
    private void MenuItemCallback(object sender, EventArgs e)
    {
       //Show the tool window
       ShowToolWindow(sender, e);
    }
  • Run it now to see the result.

Note that the first time you run it, you may find the tool window little bigger than the list box, adjust it once and it will reserve its dimensions for the next runs.

Add Required Assemblies and Namespaces

Add references to the following assemblies:

  • Microsoft.VisualStudio.CoreUtility
  • Microsoft.VisualStudio.Editor
  • Microsoft.VisualStudio.Text.Data
  • Microsoft.VisualStudio.Text.Logic
  • Microsoft.VisualStudio.Text.UI
  • Microsoft.VisualStudio.Text.UI.Wpf

In the file "MoveToRegionVSXPackage.cs", replace the existing using statements with the following ones:

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;

Load Regions

The idea is very simple. We need to get access to the current active code viewer, then parse its text searching for all the occurrences of the keyword #region. For each found region, we will read its name and add it to a string list, then populate the list box of the tool window with that string list.

So, first, we will declare the following members in the MoveToRegionVSXPackage class:

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>

Now, we can implement the method 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 = 1/*skip first block*/; index < regionDelimitedCode.Length; index++)
 {
  regionsList.Add(regionDelimitedCode[index].Split('\r')[0]);
 }
 return regionsList;
}

Now, assume we have the list of regions on hand, so we can populate the list box laid on the tool window. For this purpose, I added a simple method to MyControl class and named it 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="activeView"> 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);
}

You see, we can retrieve all regions using GetRegions which is a member of MoveToReionVSXPackage class, and we fill in the list box using PopulateList which is a member of MyControl class. So, how to pass the regions list returned from the former method as a parameter to the later method?

The place for this is the ShowToolWindow method. To justify, we need to understand how this method works.

The method body is generated by the wizard as follows:

/// <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), 0, true);
 if ((null == window) || (null == window.Frame))
 {
  throw new NotSupportedException(Resources.CanNotCreateWindow);
 }
 IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
 Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
}

The window object is the result of the call to the method FindToolWindow which takes the type of MyToolWindow as its first parameter. This call in its turn calls the parameter-less constructor of the class MyToolWindow at the first call only. If you paid a visit to that class, you will find nothing other than that constructor. And the constructor in turn assigns a new instance of MyControl class to the property Content. Here is the relation between the list box and the menu command.

So, after getting the window, I will cast its Content property to MyControl to invoke the method PopulateList.

ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, 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>

Move Selected Code to Selected Region

The idea here is to detect selection which is so easy once we initialize the viewHost (I will tell you later when and how to initialize it).

After initializing it, we can get access to its TextView property which provides a list of rich properties and methods.

Rather, I will let you see the code with few lines of comments.

/// <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[0];
 //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);
}

Handle Menu Item Click

Here, we initialize the currentTextView from which we can later initialize the viewHost. Then, we show the tool window.

/// <summary>
/// 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 = 1;//means true
 //initialize the currentTextView 
 txtMgr.GetActiveView(mustHaveFocus, null, out currentTextView);
 //Show the tool window
 ShowToolWindow(sender, e);
}

How to Download

You can download the plug in from the installer link at the top of this article.

Or you can find it on VisualStudio Gallery or VisualStudio Marketplace. 

Here is the direct link.

Supported Visual Studio Versions

Currently, I updated the code and plug in to support Visual Studio 2015.

This extension was originally developed using Visual Studio 2010.
If you need to install it on newer versions, just do the following:

  1. Open the source code using your own version
  2. Upgrade the project to that version
  3. Update the project references to use VS SDK of your target version

No need to change any piece of code.

It was tested on both Visual Studio 2012 and 2013.
Unfortunately, it failed on versions earlier than 2010. But as the code is available, feel free to trace and update it to be compatible with your own version if needed.

Special Thanks

Special thanks to Matt U and Ron Nicholson for their support through this useful discussion.

References

Unfortunately, the assemblies used for this purpose were not fully commented. So, I had to try many classes, interfaces, methods, and properties to get it done.

However, I learned more from these 2 links:

History

  • 30th March 2010: Initial version submitted as my involvement in Visual Studio 2010 Extension Contest
  • 1st April 2010: Added the "How to Download" section, besides some text changes
  • 28th April 2014: Added "Supported Visual Studio Version" and "Special Thanks" section
  • 13th Jan 2017: Updated source code and plug in to support Visual Studio 2015

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here