KnowDotNet NetRefactor

Collapsing and UnCollapsing All Regions in an Add-in

by Les Smith
Print this Article Discuss in Forums

How can I programatically Collapse or UnCollapse all Regions in a Code Window with an Add-in or Macro?

I have seen the question on Microsoft and Other Newsgroups asking, "How can I collapse all Regions Programatically in a Visual Studio .NET Add-In? Too often, some have answered that the IDE does not provide access to the the Regions in the Extensibility Model.  Strictly speaking, that statement is true.  However, the answer to the original question lies in an addage that I believe to be true; Add-ins, for the most part, are limited only by the creativity and ingenuinity of the the Add-in developer.  In this case, what seems to be a great mystery, because the IDE does not expose a command that you would think that would do this, is actually a piece of cake!

First, let me state the case for doing this.  One may point out that the IDE has, under its Outlining Menu, commands for collapsing and uncollapsing code.  However, these commands behave differently based on the cursor position at the time the command is invoked.  For example, ToggleOutliningExpansion will collapse a method if the cursor is placed in an open method, or it will collapse a Class if the cursor is placed in the Class, but not in a method.  So, the key is to place the cursor in the line that you want to toggle.  Additionally, if you want to collapse all regions at once, you might think to use CollapseToDefinitions.  The problem with that is that it will collapse all methods within the regions also.  Now, when you go to open a region, you find that all methods have be collapsed also, and that probably was not what you wanted.

If you place the cursor anywhere in the #Region (VB.NET) or #region in C# (CSharp), and select the Edit, Outlining, ToggleOutliningExpansion, the region will collapse! Now the problem is how can I get the cursor to move from where the user has it, somewhere in the region, to position in the #Region line? I am going to provide two methods, one for closing and one for opening all regions.  They will call a third method that actually does the work of closing or opening all regions, based on the parameter passed to it.

You can test this code by coping into the Macro IDE. In the Visual Studio .NET IDE, double-click on the respective Sub in the Macro Explorer.

A little explanation of the Macro/Add-In code.   There is a subtle nuance that is being employed here.  You will note that the ToggleAllRegions method is using a TextSelection object to find the #region or #end region.  Normally, I would use an EditPoint object, because it works behind  the scenes, in the Edit Buffer, and does not cause "flash for the audience."  However, in this case, I must be able to tell if a region is already open or closed.  If it is collapsed already, and I try to collapse it, it will uncollapse.  Toggle means Toogle!  So, I have to look, from the bottom of the window up, checking to see if I find an #end region (VB.NET) or #endregion (C#) so that I can determine if the region is closed.  If I use the EditPoint object, I will see the #end region regardless of whether the region is collapsed or not.  The EditPoint object sees all of the code, a line at a time.  But, if I find a #region line, that is collapsed, using the TextSelection object, the line retrieved will actually have the whole collapsed #region in it, but it starts with #region and I will not see the #end region.  So much for the ingenuity; now to the code.

First, I create a TextSelection object of the current Visual Studio .NET IDE CodeWindow. Next, I create an EditPoint object set to the ActivePoint of the TextSelection object. This causes the EditPoint Line Property to be set to the line in which the cursor resides in the current CodeWindow. I need the EditPoint object so that I can get out of the loop when I reach the top of the code window. I then search backwards through the CodeWindow until I find the desired text. For C#, the "#region" is lower case, while in VB.NET "#Region" is capitalized, so I set the line ToLower and Trim it to ensure that I will make a find, regardless of the type language being processed.

    Public Sub CloseAllRegions()
End Sub
    Public Sub OpenAllRegions()
End Sub
    Public Sub ToggleAllRegions(ByVal Switch As String)
' in walking up through the code, if we do not see
        ' #endregion/end region before we get to the
        ' #region, then the region was closed, otherwise
        ' it was already open, and each time a #region is
        ' found, set open = false as we are only looking
        ' for closed regions and if we toggle an open region
        ' it will close...
        Dim cmd As String = "Edit.ToggleOutliningExpansion"

            Dim open As Boolean = False
            Dim ts As TextSelection = DTE.ActiveDocument.Selection
Dim ep As EditPoint = ts.ActivePoint.CreateEditPoint
Dim line As String

            Do While Not ep.AtStartOfDocument
                line = ts.Text.ToLower.Trim
If line.StartsWith("#end region") Or _
                    open = True
                ElseIf line.StartsWith("#region ") Then
                    If Switch = "C" Then
                        If open Then
End If
                        If Not open Then
End If
                    End If
                    open = False
                    End If
                    ep = ts.ActivePoint.CreateEditPoint
        End Try
    End Sub

To use this code in an add-in, simply replace DTE with the name of the applicationObject in the add-in. Visual Studio .NET Add-Ins are not hard to write, once you figure out which object, method, property, etc., in the vast Extensibility Model to use.

Actually, you could collapse the current Class, NameSpace, or even the method, in which the cursor currently resides, by using a modification of the search string in the example code shown above.

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing