Delen via


Overzicht: Een weergaveversiering, opdrachten en instellingen (kolomhulplijnen) maken

U kunt de Tekst-/code-editor van Visual Studio uitbreiden met opdrachten en effecten weergeven. In dit artikel leest u hoe u aan de slag gaat met een populaire extensiefunctie, kolomhandleidingen. Kolomhulplijnen zijn visueel lichte lijnen die zijn getekend in de weergave van de teksteditor om u te helpen bij het beheren van uw code voor specifieke kolombreedten. Met name opgemaakte code kan belangrijk zijn voor voorbeelden die u opneemt in documenten, blogberichten of foutrapporten.

In deze procedure gaat u het volgende doen:

  • Een VSIX-project maken

  • Een versiering van de editorweergave toevoegen

  • Ondersteuning toevoegen voor het opslaan en verkrijgen van instellingen (waar kolomhulplijnen en de bijbehorende kleur moeten worden getekend)

  • Opdrachten toevoegen (kolomhulplijnen toevoegen/verwijderen, de kleur ervan wijzigen)

  • Plaats de opdrachten in het menu Bewerken en contextmenu's voor tekstdocument

  • Ondersteuning toevoegen voor het aanroepen van de opdrachten vanuit het Visual Studio-opdrachtvenster

    U kunt een versie van de functie voor kolomhulplijnen uitproberen met deze Visual Studio Gallery extensie.

    Notitie

    In dit scenario plakt u een grote hoeveelheid code in een aantal bestanden die zijn gegenereerd door Visual Studio-extensiesjablonen. Maar binnenkort zal deze handleiding verwijzen naar een voltooide oplossing op GitHub met andere extensievoorbeelden. De voltooide code is enigszins anders omdat deze echte opdrachtpictogrammen bevat in plaats van generictemplate-pictogrammen te gebruiken.

De oplossing instellen

Eerst maakt u een VSIX-project, voegt u een versiering voor de editorweergave toe, en voegt u vervolgens een opdracht toe (waarmee een VSPackage wordt toegevoegd om de opdracht te bevatten). De basisarchitectuur is als volgt:

  • U hebt een listener voor het aanmaken van een tekstweergave die per weergave een ColumnGuideAdornment object aanmaakt. Dit object luistert naar gebeurtenissen over het wijzigen van de weergave of het wijzigen van instellingen, het bijwerken of opnieuw tekenen van kolomhulplijnen, indien nodig.

  • Er is een GuidesSettingsManager die het lezen en schrijven van de Visual Studio-instellingenopslag afhandelt. De instellingenbeheerder heeft ook bewerkingen voor het bijwerken van de instellingen die ondersteuning bieden voor de gebruikersopdrachten (kolom toevoegen, kolom verwijderen, kleur wijzigen).

  • Er is een VSIP-pakket dat nodig is als u gebruikersopdrachten hebt, maar het is gewoon standaardcode waarmee het implementatieobject voor opdrachten wordt geïnitialiseerd.

  • Er is een ColumnGuideCommands-object dat de gebruikersopdrachten uitvoert en de opdrachthandlers koppelt voor opdrachten die zijn gedeclareerd in het bestand .vsct.

    VSIX. -bestand gebruiken | Nieuwe opdracht ... om een project te maken. Kies het knooppunt Uitbreidbaarheid onder C# in het linkernavigatiedeelvenster en kies VSIX Project in het rechterdeelvenster. Voer de naam ColumnGuides in en kies OK om het project te maken.

    weergeven. Druk op de rechterknop op het projectknooppunt in Solution Explorer. Kies de Toevoegen | Nieuw item ... opdracht om een nieuw weergave sieritem toe te voegen. Kies Uitbreidbaarheid | Editor in het linkernavigatiedeelvenster en kies editorweergave in het rechterdeelvenster. Voer de naam ColumnGuideAdornment in als itemnaam en kies Toevoegen om deze toe te voegen.

    U ziet dat deze itemsjabloon twee bestanden heeft toegevoegd aan het project (evenals verwijzingen, enzovoort): ColumnGuideAdornment.cs en ColumnGuideAdornmentTextViewCreationListener.cs. De sjablonen tekenen een paarse rechthoek in de weergave. In de volgende sectie wijzigt u een aantal regels in de listener voor het maken van de weergave en vervangt u de inhoud van ColumnGuideAdornment.cs.

    opdrachten. Druk in Solution Explorerop de rechterknop op het projectknooppunt. Kies de Toevoegen | Nieuw item ... opdracht om een nieuw weergave sieritem toe te voegen. Kies Uitbreidbaarheid | VSPackage in het linkernavigatiedeelvenster en kies Aangepaste opdracht in het rechterdeelvenster. Voer de naam in ColumnGuideCommands als de naam van het item en kies Toevoegen. Naast verschillende verwijzingen zijn ook de opdrachten en het pakket, namelijk (ColumnGuideCommands.cs), (ColumnGuideCommandsPackage.cs) en (ColumnGuideCommandsPackage.vsct), toegevoegd. In de volgende sectie vervangt u de inhoud van de eerste en laatste bestanden om de opdrachten te definiëren en te implementeren.

De listener voor het maken van de tekstweergave instellen

Open ColumnGuideAdornmentTextViewCreationListener.cs in de editor. Met deze code wordt een handler geïmplementeerd voor wanneer Visual Studio tekstweergaven maakt. Er zijn kenmerken die bepalen wanneer de handler wordt aangeroepen, afhankelijk van de kenmerken van de weergave.

De code moet ook een sierlaag declareren. Wanneer de editor weergaven bijwerkt, worden de sierlagen en vervolgens de sierelementen voor de weergave opgehaald. U kunt de volgorde van uw laag ten opzichte van anderen declareren met kenmerken. Vervang de volgende regel:

[Order(After = PredefinedAdornmentLayers.Caret)]

met deze twee regels:

[Order(Before = PredefinedAdornmentLayers.Text)]
[TextViewRole(PredefinedTextViewRoles.Document)]

De regel die u hebt vervangen, bevindt zich in een groep kenmerken die een sierlaag declareren. De eerste regel die u hebt gewijzigd, verandert alleen de positie van de kolomhulplijnen. Als u de regels 'vóór' de tekst in de weergave tekent, worden ze achter of onder de tekst weergegeven. Met de tweede regel wordt aangegeven dat kolomhulplijnversieringen van toepassing zijn op tekstentiteiten die passen binnen uw idee van een document. U zou bijvoorbeeld kunnen instellen dat de versiering uitsluitend werkt voor bewerkbare tekst. Meer informatie vindt u in Language-service en editor-extensiepunten

Het instellingenbeheer implementeren

Vervang de inhoud van de GuidesSettingsManager.cs door de volgende code (zie hieronder):

using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;

namespace ColumnGuides
{
    internal static class GuidesSettingsManager
    {
        // Because my code is always called from the UI thred, this succeeds.
        internal static SettingsManager VsManagedSettingsManager =
            new ShellSettingsManager(ServiceProvider.GlobalProvider);

        private const int _maxGuides = 5;
        private const string _collectionSettingsName = "Text Editor";
        private const string _settingName = "Guides";
        // 1000 seems reasonable since primary scenario is long lines of code
        private const int _maxColumn = 1000;

        static internal bool AddGuideline(int column)
        {
            if (! IsValidColumn(column))
                throw new ArgumentOutOfRangeException(
                    "column",
                    "The parameter must be between 1 and " + _maxGuides.ToString());
            var offsets = GuidesSettingsManager.GetColumnOffsets();
            if (offsets.Count() >= _maxGuides)
                return false;
            // Check for duplicates
            if (offsets.Contains(column))
                return false;
            offsets.Add(column);
            WriteSettings(GuidesSettingsManager.GuidelinesColor, offsets);
            return true;
        }

        static internal bool RemoveGuideline(int column)
        {
            if (!IsValidColumn(column))
                throw new ArgumentOutOfRangeException(
                    "column", "The parameter must be between 1 and 10,000");
            var columns = GuidesSettingsManager.GetColumnOffsets();
            if (! columns.Remove(column))
            {
                // Not present.  Allow user to remove the last column
                // even if they're not on the right column.
                if (columns.Count != 1)
                    return false;

                columns.Clear();
            }
            WriteSettings(GuidesSettingsManager.GuidelinesColor, columns);
            return true;
        }

        static internal bool CanAddGuideline(int column)
        {
            if (!IsValidColumn(column))
                return false;
            var offsets = GetColumnOffsets();
            if (offsets.Count >= _maxGuides)
                return false;
            return ! offsets.Contains(column);
        }

        static internal bool CanRemoveGuideline(int column)
        {
            if (! IsValidColumn(column))
                return false;
            // Allow user to remove the last guideline regardless of the column.
            // Okay to call count, we limit the number of guides.
            var offsets = GuidesSettingsManager.GetColumnOffsets();
            return offsets.Contains(column) || offsets.Count() == 1;
        }

        static internal void RemoveAllGuidelines()
        {
            WriteSettings(GuidesSettingsManager.GuidelinesColor, new int[0]);
        }

        private static bool IsValidColumn(int column)
        {
            // zero is allowed (per user request)
            return 0 <= column && column <= _maxColumn;
        }

        // This has format "RGB(<int>, <int>, <int>) <int> <int>...".
        // There can be any number of ints following the RGB part,
        // and each int is a column (char offset into line) where to draw.
        static private string _guidelinesConfiguration;
        static private string GuidelinesConfiguration
        {
            get
            {
                if (_guidelinesConfiguration == null)
                {
                    _guidelinesConfiguration =
                        GetUserSettingsString(
                            GuidesSettingsManager._collectionSettingsName,
                            GuidesSettingsManager._settingName)
                        .Trim();
                }
                return _guidelinesConfiguration;
            }

            set
            {
                if (value != _guidelinesConfiguration)
                {
                    _guidelinesConfiguration = value;
                    WriteUserSettingsString(
                        GuidesSettingsManager._collectionSettingsName,
                        GuidesSettingsManager._settingName, value);
                    // Notify ColumnGuideAdornments to update adornments in views.
                    var handler = GuidesSettingsManager.SettingsChanged;
                    if (handler != null)
                        handler();
                }
            }
        }

        internal static string GetUserSettingsString(string collection, string setting)
        {
            var store = GuidesSettingsManager
                            .VsManagedSettingsManager
                            .GetReadOnlySettingsStore(SettingsScope.UserSettings);
            return store.GetString(collection, setting, "RGB(255,0,0) 80");
        }

        internal static void WriteUserSettingsString(string key, string propertyName,
                                                     string value)
        {
            var store = GuidesSettingsManager
                            .VsManagedSettingsManager
                            .GetWritableSettingsStore(SettingsScope.UserSettings);
            store.CreateCollection(key);
            store.SetString(key, propertyName, value);
        }

        // Persists settings and sets property with side effect of signaling
        // ColumnGuideAdornments to update.
        static private void WriteSettings(Color color, IEnumerable<int> columns)
        {
            string value = ComposeSettingsString(color, columns);
            GuidelinesConfiguration = value;
        }

        private static string ComposeSettingsString(Color color,
                                                    IEnumerable<int> columns)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("RGB({0},{1},{2})", color.R, color.G, color.B);
            IEnumerator<int> columnsEnumerator = columns.GetEnumerator();
            if (columnsEnumerator.MoveNext())
            {
                sb.AppendFormat(" {0}", columnsEnumerator.Current);
                while (columnsEnumerator.MoveNext())
                {
                    sb.AppendFormat(", {0}", columnsEnumerator.Current);
                }
            }
            return sb.ToString();
        }

        // Parse a color out of a string that begins like "RGB(255,0,0)"
        static internal Color GuidelinesColor
        {
            get
            {
                string config = GuidelinesConfiguration;
                if (!String.IsNullOrEmpty(config) && config.StartsWith("RGB("))
                {
                    int lastParen = config.IndexOf(')');
                    if (lastParen > 4)
                    {
                        string[] rgbs = config.Substring(4, lastParen - 4).Split(',');

                        if (rgbs.Length >= 3)
                        {
                            byte r, g, b;
                            if (byte.TryParse(rgbs[0], out r) &&
                                byte.TryParse(rgbs[1], out g) &&
                                byte.TryParse(rgbs[2], out b))
                            {
                                return Color.FromRgb(r, g, b);
                            }
                        }
                    }
                }
                return Colors.DarkRed;
            }

            set
            {
                WriteSettings(value, GetColumnOffsets());
            }
        }

        // Parse a list of integer values out of a string that looks like
        // "RGB(255,0,0) 1, 5, 10, 80"
        static internal List<int> GetColumnOffsets()
        {
            var result = new List<int>();
            string settings = GuidesSettingsManager.GuidelinesConfiguration;
            if (String.IsNullOrEmpty(settings))
                return new List<int>();

            if (!settings.StartsWith("RGB("))
                return new List<int>();

            int lastParen = settings.IndexOf(')');
            if (lastParen <= 4)
                return new List<int>();

            string[] columns = settings.Substring(lastParen + 1).Split(',');

            int columnCount = 0;
            foreach (string columnText in columns)
            {
                int column = -1;
                // VS 2008 gallery extension didn't allow zero, so per user request ...
                if (int.TryParse(columnText, out column) && column >= 0)
                {
                    columnCount++;
                    result.Add(column);
                    if (columnCount >= _maxGuides)
                        break;
                }
            }
            return result;
        }

        // Delegate and Event to fire when settings change so that ColumnGuideAdornments
        // can update.  We need nothing special in this event since the settings manager
        // is statically available.
        //
        internal delegate void SettingsChangedHandler();
        static internal event SettingsChangedHandler SettingsChanged;

    }
}

De meeste van deze code maakt en parseert de instellingenindeling: "RGB(<int>,<int>,<int>) <int>, <int>, ...". De gehele getallen aan het einde zijn de kolommen op basis van één kolom waarin u kolomhulplijnen wilt gebruiken. De extensie kolomhulplijnen legt alle instellingen vast in één instellingswaardetekenreeks.

Er zijn enkele onderdelen van de code die u moet markeren. De volgende coderegel haalt de beheerde Visual Studio-wrapper op voor de instellingenopslag. Voor het grootste deel abstracteert dit over het Windows-register, maar deze API is onafhankelijk van het opslagmechanisme.

internal static SettingsManager VsManagedSettingsManager =
    new ShellSettingsManager(ServiceProvider.GlobalProvider);

De opslag van Visual Studio-instellingen maakt gebruik van een categorie-id en een instellings-id om alle instellingen uniek te identificeren:

private const string _collectionSettingsName = "Text Editor";
private const string _settingName = "Guides";

U hoeft "Text Editor" niet te gebruiken als categorienaam. U kunt alles kiezen wat u wilt.

De eerste paar functies zijn de toegangspunten om instellingen te wijzigen. Ze controleren beperkingen op hoog niveau, zoals het maximum aantal toegestane gidsen. Vervolgens roepen ze WriteSettingsaan, die een instellingentekenreeks opstelt en de eigenschap GuideLinesConfigurationinstelt. Als u deze eigenschap instelt, wordt de waarde van de instellingen opgeslagen in het Visual Studio-instellingenarchief en wordt de SettingsChanged gebeurtenis geactiveerd om alle ColumnGuideAdornment objecten bij te werken, die elk zijn gekoppeld aan een tekstweergave.

Er zijn een aantal invoerpuntfuncties, zoals CanAddGuideline, die worden gebruikt om opdrachten te implementeren die instellingen wijzigen. Wanneer In Visual Studio menu's worden weergegeven, worden opdracht-implementaties opgevraagd om te zien of de opdracht momenteel is ingeschakeld, wat de naam ervan is, enzovoort. Hieronder ziet u hoe u deze toegangspunten koppelt voor de opdracht-implementaties. Voor meer informatie over opdrachten, zie Menu's en opdrachten uitbreiden.

De klasse ColumnGuideAdornment implementeren

De ColumnGuideAdornment-klasse wordt geïnstantieerd voor elke tekstweergave die versieringen kan hebben. Deze klasse luistert naar gebeurtenissen over het wijzigen van de weergave of instellingen, en het bijwerken of opnieuw tekenen van kolomhulplijnen indien nodig.

Vervang de inhoud van de ColumnGuideAdornment.cs door de volgende code (zie hieronder):

using System;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Editor;
using System.Collections.Generic;
using System.Windows.Shapes;
using Microsoft.VisualStudio.Text.Formatting;
using System.Windows;

namespace ColumnGuides
{
    /// <summary>
    /// Adornment class, one instance per text view that draws a guides on the viewport
    /// </summary>
    internal sealed class ColumnGuideAdornment
    {
        private const double _lineThickness = 1.0;
        private IList<Line> _guidelines;
        private IWpfTextView _view;
        private double _baseIndentation;
        private double _columnWidth;

        /// <summary>
        /// Creates editor column guidelines
        /// </summary>
        /// <param name="view">The <see cref="IWpfTextView"/> upon
        /// which the adornment will be drawn</param>
        public ColumnGuideAdornment(IWpfTextView view)
        {
            _view = view;
            _guidelines = CreateGuidelines();
            GuidesSettingsManager.SettingsChanged +=
                new GuidesSettingsManager.SettingsChangedHandler(SettingsChanged);
            view.LayoutChanged +=
                new EventHandler<TextViewLayoutChangedEventArgs>(OnViewLayoutChanged);
            _view.Closed += new EventHandler(OnViewClosed);
        }

        void SettingsChanged()
        {
            _guidelines = CreateGuidelines();
            UpdatePositions();
            AddGuidelinesToAdornmentLayer();
        }

        void OnViewClosed(object sender, EventArgs e)
        {
            _view.LayoutChanged -= OnViewLayoutChanged;
            _view.Closed -= OnViewClosed;
            GuidesSettingsManager.SettingsChanged -= SettingsChanged;
        }

        private bool _firstLayoutDone;

        void OnViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
        {
            bool fUpdatePositions = false;

            IFormattedLineSource lineSource = _view.FormattedLineSource;
            if (lineSource == null)
            {
                return;
            }
            if (_columnWidth != lineSource.ColumnWidth)
            {
                _columnWidth = lineSource.ColumnWidth;
                fUpdatePositions = true;
            }
            if (_baseIndentation != lineSource.BaseIndentation)
            {
                _baseIndentation = lineSource.BaseIndentation;
                fUpdatePositions = true;
            }
            if (fUpdatePositions ||
                e.VerticalTranslation ||
                e.NewViewState.ViewportTop != e.OldViewState.ViewportTop ||
                e.NewViewState.ViewportBottom != e.OldViewState.ViewportBottom)
            {
                UpdatePositions();
            }
            if (!_firstLayoutDone)
            {
                AddGuidelinesToAdornmentLayer();
                _firstLayoutDone = true;
            }
        }

        private static IList<Line> CreateGuidelines()
        {
            Brush lineBrush = new SolidColorBrush(GuidesSettingsManager.GuidelinesColor);
            DoubleCollection dashArray = new DoubleCollection(new double[] { 1.0, 3.0 });
            IList<Line> result = new List<Line>();
            foreach (int column in GuidesSettingsManager.GetColumnOffsets())
            {
                Line line = new Line()
                {
                    // Use the DataContext slot as a cookie to hold the column
                    DataContext = column,
                    Stroke = lineBrush,
                    StrokeThickness = _lineThickness,
                    StrokeDashArray = dashArray
                };
                result.Add(line);
            }
            return result;
        }

        void UpdatePositions()
        {
            foreach (Line line in _guidelines)
            {
                int column = (int)line.DataContext;
                line.X2 = _baseIndentation + 0.5 + column * _columnWidth;
                line.X1 = line.X2;
                line.Y1 = _view.ViewportTop;
                line.Y2 = _view.ViewportBottom;
            }
        }

        void AddGuidelinesToAdornmentLayer()
        {
            // Grab a reference to the adornment layer that this adornment
            // should be added to
            // Must match exported name in ColumnGuideAdornmentTextViewCreationListener
            IAdornmentLayer adornmentLayer =
                _view.GetAdornmentLayer("ColumnGuideAdornment");
            if (adornmentLayer == null)
                return;
            adornmentLayer.RemoveAllAdornments();
            // Add the guidelines to the adornment layer and make them relative
            // to the viewport
            foreach (UIElement element in _guidelines)
                adornmentLayer.AddAdornment(AdornmentPositioningBehavior.OwnerControlled,
                                            null, null, element, null);
        }
    }

}

Exemplaren van deze klasse houden de bijbehorende IWpfTextView en een lijst met Line-objecten bij die op de weergave zijn getekend.

De constructor (aangeroepen vanuit ColumnGuideAdornmentTextViewCreationListener wanneer Visual Studio nieuwe weergaven maakt) creëert de kolomgidsobjecten Line. De constructor voegt ook handlers toe voor de SettingsChanged gebeurtenis (gedefinieerd in GuidesSettingsManager) en de weergave-gebeurtenissen LayoutChanged en Closed.

De LayoutChanged gebeurtenis wordt geactiveerd door verschillende wijzigingen in de weergave, zoals wanneer Visual Studio de weergave creëert. De OnViewLayoutChanged handler roept AddGuidelinesToAdornmentLayer aan om uit te voeren. De code in OnViewLayoutChanged bepaalt of de regelposities moeten worden bijgewerkt op basis van wijzigingen zoals wijzigingen in de tekengrootte, weergavewanden, horizontaal schuiven, enzovoort. De code in UpdatePositions zorgt ervoor dat hulplijnen worden getekend tussen tekens of net na de kolom met tekst die zich in de opgegeven karakteroffset in de tekstregel bevindt.

Wanneer instellingen de SettingsChanged functie wijzigen, worden alle Line objecten opnieuw gemaakt met wat de nieuwe instellingen zijn. Na het instellen van de lijnposities verwijdert de code alle vorige Line objecten uit de ColumnGuideAdornment sierlaag en worden de nieuwe objecten toegevoegd.

De opdrachten, menu's en menuplaatsingen definiëren

Er komt veel kijken bij het opstellen van opdrachten en menu's, het plaatsen van groepen opdrachten of menu's in andere menu's, en het koppelen van opdrachthandlers. In dit overzicht ziet u hoe opdrachten in deze extensie werken, maar zie Menu's en opdrachten uitbreidenvoor meer informatie.

Inleiding tot de code

De extensie Kolomhulplijnen toont het declareren van een groep opdrachten die bij elkaar horen (kolom toevoegen, kolom verwijderen, lijnkleur wijzigen) en die groep vervolgens in een submenu van het contextmenu van de editor plaatsen. De extensie Kolomhulplijnen voegt de opdrachten ook toe aan het hoofdmenu Bewerken, maar houdt ze onzichtbaar, zoals hieronder als een veelvoorkomend patroon besproken.

Er zijn drie onderdelen voor de implementatie van opdrachten: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct en ColumnGuideCommands.cs. De code die door de sjablonen wordt gegenereerd, plaatst een opdracht in het menu Extra waarin een dialoogvenster wordt weergegeven als de implementatie. U kunt bekijken hoe dat wordt geïmplementeerd in de .vsct-- en ColumnGuideCommands.cs-bestanden, omdat dit eenvoudig is. U vervangt de code in deze bestanden hieronder.

De pakketcode bevat standaarddeclaraties die vereist zijn voor Visual Studio om te ontdekken dat de extensie opdrachten biedt en waar u de opdrachten kunt plaatsen. Wanneer het pakket wordt geïnitialiseerd, wordt de implementatieklasse van de opdrachten geïnstitueert. Voor meer informatie over pakketten met betrekking tot opdrachten, zie Menu's en opdrachten uitbreiden.

Een algemeen patroon voor opdrachten

De opdrachten in de extensie Kolomhulplijnen zijn een voorbeeld van een veelvoorkomend patroon in Visual Studio. U plaatst gerelateerde opdrachten in een groep en u plaatst die groep in een hoofdmenu, vaak met '<CommandFlag>CommandWellOnly</CommandFlag>' ingesteld om de opdracht onzichtbaar te maken. Het plaatsen van opdrachten in de hoofdmenu's (zoals Bewerken) geeft ze mooie namen (zoals Edit.AddColumnGuide), die handig zijn voor het zoeken van opdrachten bij het opnieuw toewijzen van sleutelbindingen in Extra Opties. Het is ook handig om bij het aanroepen van opdrachten uit het opdrachtvenstervoltooiing te krijgen.

Vervolgens voegt u de groep opdrachten toe aan contextmenu's of submenu's waarvan u verwacht dat de gebruiker de opdrachten gebruikt. Visual Studio behandelt CommandWellOnly alleen als een onzichtbaarheidsvlag voor hoofdmenu's. Wanneer u dezelfde groep opdrachten in een contextmenu of submenu plaatst, zijn de opdrachten zichtbaar.

Als onderdeel van het gebruikelijke patroon maakt de extensie Kolomhulplijnen een tweede groep die één submenu bevat. Het submenu bevat vervolgens de eerste groep met de vierkolomgidscommando's. De tweede groep met het submenu is de herbruikbare asset die u in verschillende contextmenu's plaatst, waardoor een submenu in deze contextmenu's wordt geplaatst.

Het VSCT-bestand

Het bestand .vsct declareert de opdrachten en waar ze heengaan, samen met pictogrammen enzovoort. Vervang de inhoud van het bestand .vsct door de volgende code (zie hieronder):

<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <!--  This is the file that defines the actual layout and type of the commands.
        It is divided in different sections (e.g. command definition, command
        placement, ...), with each defining a specific set of properties.
        See the comment before each section for more details about how to
        use it. -->

  <!--  The VSCT compiler (the tool that translates this file into the binary
        format that VisualStudio will consume) has the ability to run a preprocessor
        on the vsct file; this preprocessor is (usually) the C++ preprocessor, so
        it is possible to define includes and macros with the same syntax used
        in C++ files. Using this ability of the compiler here, we include some files
        defining some of the constants that we will use inside the file. -->

  <!--This is the file that defines the IDs for all the commands exposed by
      VisualStudio. -->
  <Extern href="stdidcmd.h"/>

  <!--This header contains the command ids for the menus provided by the shell. -->
  <Extern href="vsshlids.h"/>

  <!--The Commands section is where commands, menus, and menu groups are defined.
      This section uses a Guid to identify the package that provides the command
      defined inside it. -->
  <Commands package="guidColumnGuideCommandsPkg">
    <!-- Inside this section we have different sub-sections: one for the menus, another
    for the menu groups, one for the buttons (the actual commands), one for the combos
    and the last one for the bitmaps used. Each element is identified by a command id
    that is a unique pair of guid and numeric identifier; the guid part of the identifier
    is usually called "command set" and is used to group different command inside a
    logically related group; your package should define its own command set in order to
    avoid collisions with command ids defined by other packages. -->

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

      <!-- The main group is parented to the edit menu. All the buttons within the group
           have the "CommandWellOnly" flag, so they're actually invisible, but it means
           they get canonical names that begin with "Edit". Using placements, the group
           is also placed in the GuidesSubMenu group. -->
      <!-- The priority 0xB801 is chosen so it goes just after
           IDG_VS_EDIT_COMMANDWELL -->
      <Group guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
             priority="0xB801">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
      </Group>

      <!-- Group for holding the "Guidelines" sub-menu anchor (the item on the menu that
           drops the sub menu). The group is parented to
           the context menu for code windows. That takes care of most editors, but it's
           also placed in a couple of other windows using Placements -->
      <Group guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
             priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN" />
      </Group>

    </Groups>

    <Menus>
      <Menu guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" priority="0x1000"
            type="Menu">
        <Parent guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup" />
        <Strings>
          <ButtonText>&Column Guides</ButtonText>
        </Strings>
      </Menu>
    </Menus>

    <!--Buttons section. -->
    <!--This section defines the elements the user can interact with, like a menu command or a button
        or combo box in a toolbar. -->
    <Buttons>
      <!--To define a menu group you have to specify its ID, the parent menu and its
          display priority.
          The command is visible and enabled by default. If you need to change the
          visibility, status, etc, you can use the CommandFlag node.
          You can add more than one CommandFlag node e.g.:
              <CommandFlag>DefaultInvisible</CommandFlag>
              <CommandFlag>DynamicVisibility</CommandFlag>
          If you do not want an image next to your command, remove the Icon node or
          set it to <Icon guid="guidOfficeIcon" id="msotcidNoIcon" /> -->

      <Button guid="guidColumnGuidesCommandSet" id="cmdidAddColumnGuide"
              priority="0x0100" type="Button">
        <Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
        <Icon guid="guidImages" id="bmpPicAddGuide" />
        <CommandFlag>CommandWellOnly</CommandFlag>
        <CommandFlag>AllowParams</CommandFlag>
        <Strings>
          <ButtonText>&Add Column Guide</ButtonText>
        </Strings>
      </Button>

      <Button guid="guidColumnGuidesCommandSet" id="cmdidRemoveColumnGuide"
              priority="0x0101" type="Button">
        <Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
        <Icon guid="guidImages" id="bmpPicRemoveGuide" />
        <CommandFlag>CommandWellOnly</CommandFlag>
        <CommandFlag>AllowParams</CommandFlag>
        <Strings>
          <ButtonText>&Remove Column Guide</ButtonText>
        </Strings>
      </Button>

      <Button guid="guidColumnGuidesCommandSet" id="cmdidChooseGuideColor"
              priority="0x0103" type="Button">
        <Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
        <Icon guid="guidImages" id="bmpPicChooseColor" />
        <CommandFlag>CommandWellOnly</CommandFlag>
        <Strings>
          <ButtonText>Column Guide &Color...</ButtonText>
        </Strings>
      </Button>

      <Button guid="guidColumnGuidesCommandSet" id="cmdidRemoveAllColumnGuides"
              priority="0x0102" type="Button">
        <Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
        <CommandFlag>CommandWellOnly</CommandFlag>
        <Strings>
          <ButtonText>Remove A&ll Columns</ButtonText>
        </Strings>
      </Button>
    </Buttons>

    <!--The bitmaps section is used to define the bitmaps that are used for the
        commands.-->
    <Bitmaps>
      <!--  The bitmap id is defined in a way that is a little bit different from the
            others:
            the declaration starts with a guid for the bitmap strip, then there is the
            resource id of the bitmap strip containing the bitmaps and then there are
            the numeric ids of the elements used inside a button definition. An important
            aspect of this declaration is that the element id
            must be the actual index (1-based) of the bitmap inside the bitmap strip. -->
      <Bitmap guid="guidImages" href="Resources\ColumnGuideCommands.png"
              usedList="bmpPicAddGuide, bmpPicRemoveGuide, bmpPicChooseColor" />
    </Bitmaps>

  </Commands>

  <CommandPlacements>

    <!-- Define secondary placements for our groups -->

    <!-- Place the group containing the three commands in the sub-menu -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
                      priority="0x0100">
      <Parent guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" />
    </CommandPlacement>

    <!-- The HTML editor context menu, for some reason, redefines its own groups
         so we need to place a copy of our context menu there too. -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
                      priority="0x1001">
      <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_HTML" />
    </CommandPlacement>

    <!-- The HTML context menu in Dev12 changed. -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
                      priority="0x1001">
      <Parent guid="CMDSETID_HtmEdGrp_Dev12" id="IDMX_HTM_SOURCE_HTML_Dev12" />
    </CommandPlacement>

    <!-- Similarly for Script -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
                      priority="0x1001">
      <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_SCRIPT" />
    </CommandPlacement>

    <!-- Similarly for ASPX  -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
                      priority="0x1001">
      <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_ASPX" />
    </CommandPlacement>

    <!-- Similarly for the XAML editor context menu -->
    <CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
                      priority="0x0600">
      <Parent guid="guidXamlUiCmds" id="IDM_XAML_EDITOR" />
    </CommandPlacement>

  </CommandPlacements>

  <!-- This defines the identifiers and their values used above to index resources
       and specify commands. -->
  <Symbols>
    <!-- This is the package guid. -->
    <GuidSymbol name="guidColumnGuideCommandsPkg"
                value="{e914e5de-0851-4904-b361-1a3a9d449704}" />

    <!-- This is the guid used to group the menu commands together -->
    <GuidSymbol name="guidColumnGuidesCommandSet"
                value="{c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e}">
      <IDSymbol name="GuidesContextMenuGroup" value="0x1020" />
      <IDSymbol name="GuidesMenuItemsGroup" value="0x1021" />
      <IDSymbol name="GuidesSubMenu" value="0x1022" />
      <IDSymbol name="cmdidAddColumnGuide" value="0x0100" />
      <IDSymbol name="cmdidRemoveColumnGuide" value="0x0101" />
      <IDSymbol name="cmdidChooseGuideColor" value="0x0102" />
      <IDSymbol name="cmdidRemoveAllColumnGuides" value="0x0103" />
    </GuidSymbol>

    <GuidSymbol name="guidImages" value="{2C99F852-587C-43AF-AA2D-F605DE2E46EF}">
      <IDSymbol name="bmpPicAddGuide" value="1" />
      <IDSymbol name="bmpPicRemoveGuide" value="2" />
      <IDSymbol name="bmpPicChooseColor" value="3" />
    </GuidSymbol>

    <GuidSymbol name="CMDSETID_HtmEdGrp_Dev12"
                value="{78F03954-2FB8-4087-8CE7-59D71710B3BB}">
      <IDSymbol name="IDMX_HTM_SOURCE_HTML_Dev12" value="0x1" />
    </GuidSymbol>

    <GuidSymbol name="CMDSETID_HtmEdGrp" value="{d7e8c5e1-bdb8-11d0-9c88-0000f8040a53}">
      <IDSymbol name="IDMX_HTM_SOURCE_HTML" value="0x33" />
      <IDSymbol name="IDMX_HTM_SOURCE_SCRIPT" value="0x34" />
      <IDSymbol name="IDMX_HTM_SOURCE_ASPX" value="0x35" />
    </GuidSymbol>

    <GuidSymbol name="guidXamlUiCmds" value="{4c87b692-1202-46aa-b64c-ef01faec53da}">
      <IDSymbol name="IDM_XAML_EDITOR" value="0x103" />
    </GuidSymbol>
  </Symbols>

</CommandTable>

GUIDS-. Opdat Visual Studio uw opdrachthandlers kan vinden en deze kan invoeren, moet u ervoor zorgen dat de pakket-GUID die is gedeclareerd in het ColumnGuideCommandsPackage.cs-bestand (gegenereerd op basis van de projectitemsjabloon) overeenkomt met de pakket-GUID die is gedeclareerd in het .vsct--bestand dat hierboven is gekopieerd. Als u deze voorbeeldcode opnieuw gebruikt, moet u ervoor zorgen dat u een andere GUID hebt, zodat u geen conflict veroorzaakt met iemand anders die deze code mogelijk heeft gekopieerd.

Zoek deze regel in ColumnGuideCommandsPackage.cs en kopieer de GUID tussen de aanhalingstekens:

public const string PackageGuidString = "ef726849-5447-4f73-8de5-01b9e930f7cd";

Plak vervolgens de GUID in het bestand .vsct zodat u de volgende regel in uw Symbols declaraties hebt:

<GuidSymbol name="guidColumnGuideCommandsPkg"
            value="{ef726849-5447-4f73-8de5-01b9e930f7cd}" />

De GUID's voor de opdrachtenset en het bitmapafbeeldingsbestand moeten ook uniek zijn voor uw extensies:

<GuidSymbol name="guidColumnGuidesCommandSet"
            value="{c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e}">
<GuidSymbol name="guidImages" value="{2C99F852-587C-43AF-AA2D-F605DE2E46EF}">

U hoeft in deze stapsgewijze handleiding echter niet de GUID's voor de opdrachtset en bitmapafbeelding te wijzigen om de code te laten functioneren. De GUID van de opdrachtset moet overeenkomen met de declaratie in het ColumnGuideCommands.cs bestand, maar u vervangt ook de inhoud van dat bestand; daarom komen de GUID's overeen.

Andere GUID's in het bestand .vsct identificeren vooraf bestaande menu's waaraan de kolomgidsopdrachten worden toegevoegd, zodat ze nooit worden gewijzigd.

bestandsecties. De .vsct bevat drie uitwendige secties: opdrachten, plaatsingen en symbolen. In de sectie Opdrachten worden opdrachtgroepen, menu's, knoppen of menu-items en bitmaps voor pictogrammen gedefinieerd. In de sectie plaatsingen wordt aangegeven waar groepen op menu's of extra plaatsingen op bestaande menu's worden toegevoegd. De sectie symbolen declareert identificatoren die elders in het bestand .vsct worden gebruikt, waardoor de .vsct code beter leesbaar is dan overal GUID's en hexadecimale nummers te hebben.

Opdrachten sectie, definities van groepen. In de sectie Opdrachten worden eerst opdrachtgroepen gedefinieerd. Groepen opdrachten zijn opdrachten die u ziet in menu's met lichte grijze lijnen die de groepen scheiden. Een groep kan ook een volledig submenu vullen, zoals in dit voorbeeld, en u ziet in dit geval niet de grijze scheidingslijnen. De .vsct--bestanden declareren twee groepen, de GuidesMenuItemsGroup die boven de IDM_VS_MENU_EDIT staat (het hoofdmenu Bewerken) en de GuidesContextMenuGroup die boven de IDM_VS_CTXT_CODEWIN (het contextmenu van de code-editor) staat.

De tweede groepsdeclaratie heeft een 0x0600 prioriteit:

<Group guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
             priority="0x0600">

Het idee is om het submenu kolomhulplijnen aan het einde van een contextmenu te plaatsen waaraan u de submenugroep toevoegt. Maar u moet er niet van uitgaan dat u het beste weet en afdwingen dat het submenu altijd de laatste is met behulp van een prioriteit van 0xFFFF. U moet experimenteren met het nummer om te zien waar uw submenu zich bevindt in de contextmenu's waar u het plaatst. In dit geval is 0x0600 hoog genoeg om het aan het einde van de menu's te plaatsen, voor zover u kunt zien, maar het laat ruimte voor iemand anders om de extensie zo te ontwerpen dat deze lager is dan de kolomhulplijnextensie als dat wenselijk is.

Opdrachtensectie, menudefinitie. Vervolgens definieert het opdrachtenmenu het submenu GuidesSubMenu, dat ondergeschikt is aan GuidesContextMenuGroup. De GuidesContextMenuGroup is de groep die u toevoegt aan alle relevante contextmenu's. In de plaatsingen-sectie plaatst de code de groep met de opdrachten voor vier kolommen op dit submenu.

sectie Opdrachten, knoppendefinities. De opdrachtensectie definieert vervolgens de menu-items of knoppen die de vierkoloms-gidsopdrachten zijn. CommandWellOnly, hierboven besproken, betekent dat de opdrachten onzichtbaar zijn wanneer ze in een hoofdmenu worden geplaatst. Twee van de declaraties van de menuopdrachtknop (hulplijn toevoegen en handleiding verwijderen) hebben ook een AllowParams vlag:

<CommandFlag>AllowParams</CommandFlag>

Met deze vlag kunt u, naast het hebben van hoofdmenu-plaatsingen, de opdracht inschakelen om argumenten te ontvangen wanneer Visual Studio de opdrachthandler aanroept. Als de gebruiker de opdracht uitvoert vanuit het opdrachtvenster, wordt het argument doorgegeven aan de opdrachthandler in de gebeurtenisargumenten.

Opdrachtsecties, bitmap-definities. Ten slotte declareert de sectie opdrachten de bitmaps of pictogrammen die voor de opdrachten worden gebruikt. Deze sectie is een eenvoudige declaratie die de projectresource identificeert en één index van gebruikte pictogrammen weergeeft. De symbolensectie van het bestand .vsct declareert de waarden van de id's die worden gebruikt als indexen. In deze walkthrough wordt de bitmapstrook gebruikt die is geleverd met de aangepaste opdrachtitem-sjabloon die aan het project is toegevoegd.

Plaatsingen sectie. Na de sectie opdrachten volgt de sectie plaatsingen. De eerste is de plaats waar de code de hierboven besproken eerste groep toevoegt, die de opdrachten van de vierkolomgids bevat, aan het submenu waarin de opdrachten verschijnen.

<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
                  priority="0x0100">
  <Parent guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" />
</CommandPlacement>

Alle andere plaatsingen voegen de GuidesContextMenuGroup (die de GuidesSubMenubevat) toe aan andere contextmenu's van de editor. Toen de code GuidesContextMenuGroupdeclareerde, werd het gekoppeld aan het contextmenu van de code-editor. Daarom ziet u geen plaatsing voor het contextmenu van de code-editor.

Symbolensectie. Zoals hierboven vermeld, declareert de sectie symbolen identificaties die elders in het .vsct--bestand worden gebruikt, waardoor de .vsct--code beter leesbaar is dan overal GUID's en hexnummers hebben. De belangrijkste punten in deze sectie zijn dat de pakket-GUID akkoord moet gaan met de declaratie in de pakketklasse. En de GUID van de opdrachtenset moet akkoord gaan met de declaratie in de opdracht-implementatieklasse.

De opdrachten implementeren

Het ColumnGuideCommands.cs-bestand implementeert de opdrachten en koppelt de handlers aan. Wanneer Visual Studio het pakket laadt en het initialiseert, roept het pakket op zijn beurt Initialize aan op de implementatieklasse van opdrachten. De initialisatie van de opdrachten instantieert eenvoudig de klasse en de constructor haakt alle opdrachthandlers aan.

Vervang de inhoud van het bestand ColumnGuideCommands.cs door de volgende code (zie hieronder):

using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio;

namespace ColumnGuides
{
    /// <summary>
    /// Command handler
    /// </summary>
    internal sealed class ColumnGuideCommands
    {

        const int cmdidAddColumnGuide = 0x0100;
        const int cmdidRemoveColumnGuide = 0x0101;
        const int cmdidChooseGuideColor = 0x0102;
        const int cmdidRemoveAllColumnGuides = 0x0103;

        /// <summary>
        /// Command menu group (command set GUID).
        /// </summary>
        static readonly Guid CommandSet =
            new Guid("c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e");

        /// <summary>
        /// VS Package that provides this command, not null.
        /// </summary>
        private readonly Package package;

        OleMenuCommand _addGuidelineCommand;
        OleMenuCommand _removeGuidelineCommand;

        /// <summary>
        /// Initializes the singleton instance of the command.
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        public static void Initialize(Package package)
        {
            Instance = new ColumnGuideCommands(package);
        }

        /// <summary>
        /// Gets the instance of the command.
        /// </summary>
        public static ColumnGuideCommands Instance
        {
            get;
            private set;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColumnGuideCommands"/> class.
        /// Adds our command handlers for menu (commands must exist in the command
        /// table file)
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        private ColumnGuideCommands(Package package)
        {
            if (package == null)
            {
                throw new ArgumentNullException("package");
            }

            this.package = package;

            // Add our command handlers for menu (commands must exist in the .vsct file)

            OleMenuCommandService commandService =
                this.ServiceProvider.GetService(typeof(IMenuCommandService))
                    as OleMenuCommandService;
            if (commandService != null)
            {
                // Add guide
                _addGuidelineCommand =
                    new OleMenuCommand(AddColumnGuideExecuted, null,
                                       AddColumnGuideBeforeQueryStatus,
                                       new CommandID(ColumnGuideCommands.CommandSet,
                                                     cmdidAddColumnGuide));
                _addGuidelineCommand.ParametersDescription = "<column>";
                commandService.AddCommand(_addGuidelineCommand);
                // Remove guide
                _removeGuidelineCommand =
                    new OleMenuCommand(RemoveColumnGuideExecuted, null,
                                       RemoveColumnGuideBeforeQueryStatus,
                                       new CommandID(ColumnGuideCommands.CommandSet,
                                                     cmdidRemoveColumnGuide));
                _removeGuidelineCommand.ParametersDescription = "<column>";
                commandService.AddCommand(_removeGuidelineCommand);
                // Choose color
                commandService.AddCommand(
                    new MenuCommand(ChooseGuideColorExecuted,
                                    new CommandID(ColumnGuideCommands.CommandSet,
                                                  cmdidChooseGuideColor)));
                // Remove all
                commandService.AddCommand(
                    new MenuCommand(RemoveAllGuidelinesExecuted,
                                    new CommandID(ColumnGuideCommands.CommandSet,
                                                  cmdidRemoveAllColumnGuides)));
            }
        }

        /// <summary>
        /// Gets the service provider from the owner package.
        /// </summary>
        private IServiceProvider ServiceProvider
        {
            get
            {
                return this.package;
            }
        }

        private void AddColumnGuideBeforeQueryStatus(object sender, EventArgs e)
        {
            int currentColumn = GetCurrentEditorColumn();
            _addGuidelineCommand.Enabled =
                GuidesSettingsManager.CanAddGuideline(currentColumn);
        }

        private void RemoveColumnGuideBeforeQueryStatus(object sender, EventArgs e)
        {
            int currentColumn = GetCurrentEditorColumn();
            _removeGuidelineCommand.Enabled =
                GuidesSettingsManager.CanRemoveGuideline(currentColumn);
        }

        private int GetCurrentEditorColumn()
        {
            IVsTextView view = GetActiveTextView();
            if (view == null)
            {
                return -1;
            }

            try
            {
                IWpfTextView textView = GetTextViewFromVsTextView(view);
                int column = GetCaretColumn(textView);

                // Note: GetCaretColumn returns 0-based positions. Guidelines are 1-based
                // positions.
                // However, do not subtract one here since the caret is positioned to the
                // left of
                // the given column and the guidelines are positioned to the right. We
                // want the
                // guideline to line up with the current caret position. e.g. When the
                // caret is
                // at position 1 (zero-based), the status bar says column 2. We want to
                // add a
                // guideline for column 1 since that will place the guideline where the
                // caret is.
                return column;
            }
            catch (InvalidOperationException)
            {
                return -1;
            }
        }

        /// <summary>
        /// Find the active text view (if any) in the active document.
        /// </summary>
        /// <returns>The IVsTextView of the active view, or null if there is no active
        /// document or the
        /// active view in the active document is not a text view.</returns>
        private IVsTextView GetActiveTextView()
        {
            IVsMonitorSelection selection =
                this.ServiceProvider.GetService(typeof(IVsMonitorSelection))
                                                    as IVsMonitorSelection;
            object frameObj = null;
            ErrorHandler.ThrowOnFailure(
                selection.GetCurrentElementValue(
                    (uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out frameObj));

            IVsWindowFrame frame = frameObj as IVsWindowFrame;
            if (frame == null)
            {
                return null;
            }

            return GetActiveView(frame);
        }

        private static IVsTextView GetActiveView(IVsWindowFrame windowFrame)
        {
            if (windowFrame == null)
            {
                throw new ArgumentException("windowFrame");
            }

            object pvar;
            ErrorHandler.ThrowOnFailure(
                windowFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out pvar));

            IVsTextView textView = pvar as IVsTextView;
            if (textView == null)
            {
                IVsCodeWindow codeWin = pvar as IVsCodeWindow;
                if (codeWin != null)
                {
                    ErrorHandler.ThrowOnFailure(codeWin.GetLastActiveView(out textView));
                }
            }
            return textView;
        }

        private static IWpfTextView GetTextViewFromVsTextView(IVsTextView view)
        {

            if (view == null)
            {
                throw new ArgumentNullException("view");
            }

            IVsUserData userData = view as IVsUserData;
            if (userData == null)
            {
                throw new InvalidOperationException();
            }

            object objTextViewHost;
            if (VSConstants.S_OK
                   != userData.GetData(Microsoft.VisualStudio
                                                .Editor
                                                .DefGuidList.guidIWpfTextViewHost,
                                       out objTextViewHost))
            {
                throw new InvalidOperationException();
            }

            IWpfTextViewHost textViewHost = objTextViewHost as IWpfTextViewHost;
            if (textViewHost == null)
            {
                throw new InvalidOperationException();
            }

            return textViewHost.TextView;
        }

        /// <summary>
        /// Given an IWpfTextView, find the position of the caret and report its column
        /// number. The column number is 0-based
        /// </summary>
        /// <param name="textView">The text view containing the caret</param>
        /// <returns>The column number of the caret's position. When the caret is at the
        /// leftmost column, the return value is zero.</returns>
        private static int GetCaretColumn(IWpfTextView textView)
        {
            // This is the code the editor uses to populate the status bar.
            Microsoft.VisualStudio.Text.Formatting.ITextViewLine caretViewLine =
                textView.Caret.ContainingTextViewLine;
            double columnWidth = textView.FormattedLineSource.ColumnWidth;
            return (int)(Math.Round((textView.Caret.Left - caretViewLine.Left)
                                       / columnWidth));
        }

        /// <summary>
        /// Determine the applicable column number for an add or remove command.
        /// The column is parsed from command arguments, if present. Otherwise
        /// the current position of the caret is used to determine the column.
        /// </summary>
        /// <param name="e">Event args passed to the command handler.</param>
        /// <returns>The column number. May be negative to indicate the column number is
        /// unavailable.</returns>
        /// <exception cref="ArgumentException">The column number parsed from event args
        /// was not a valid integer.</exception>
        private int GetApplicableColumn(EventArgs e)
        {
            var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
            if (!string.IsNullOrEmpty(inValue))
            {
                int column;
                if (!int.TryParse(inValue, out column) || column < 0)
                    throw new ArgumentException("Invalid column");
                return column;
            }

            return GetCurrentEditorColumn();
        }

        /// <summary>
        /// This function is the callback used to execute a command when 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 AddColumnGuideExecuted(object sender, EventArgs e)
        {
            int column = GetApplicableColumn(e);
            if (column >= 0)
            {
                GuidesSettingsManager.AddGuideline(column);
            }
        }

        private void RemoveColumnGuideExecuted(object sender, EventArgs e)
        {
            int column = GetApplicableColumn(e);
            if (column >= 0)
            {
                GuidesSettingsManager.RemoveGuideline(column);
            }
        }

        private void RemoveAllGuidelinesExecuted(object sender, EventArgs e)
        {
            GuidesSettingsManager.RemoveAllGuidelines();
        }

        private void ChooseGuideColorExecuted(object sender, EventArgs e)
        {
            System.Windows.Media.Color color = GuidesSettingsManager.GuidelinesColor;

            using (System.Windows.Forms.ColorDialog picker =
                new System.Windows.Forms.ColorDialog())
            {
                picker.Color = System.Drawing.Color.FromArgb(255, color.R, color.G,
                                                             color.B);
                if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    GuidesSettingsManager.GuidelinesColor =
                        System.Windows.Media.Color.FromRgb(picker.Color.R,
                                                           picker.Color.G,
                                                           picker.Color.B);
                }
            }
        }

    }
}

Verwijzingen herstellen. Er ontbreekt op dit moment een verwijzing. Druk op de rechterknop op het knooppunt Verwijzingen in Solution Explorer. Kies de opdracht Toevoegen.... Het dialoogvenster Verwijzing toevoegen bevat een zoekvak in de rechterbovenhoek. Voer 'editor' in (zonder de dubbele aanhalingstekens). Kies het Microsoft.VisualStudio.Editor item (u moet het selectievakje links van het item inschakelen, niet alleen het item selecteren) en kies OK- om de verwijzing toe te voegen.

initialisatie. Wanneer de pakketklasse wordt geïnitialiseerd, roept deze Initialize aan op de implementatieklasse van opdrachten. De ColumnGuideCommands initialisatie instantieert de klasse en slaat het klasse-exemplaar en de pakketreferentie in klasseleden op.

Laten we eens kijken naar een van de koppelingen van de opdrachthandler in de klasseconstructor:

_addGuidelineCommand =
    new OleMenuCommand(AddColumnGuideExecuted, null,
                       AddColumnGuideBeforeQueryStatus,
                       new CommandID(ColumnGuideCommands.CommandSet,
                                     cmdidAddColumnGuide));

U maakt een OleMenuCommand. Visual Studio maakt gebruik van het Microsoft Office-opdrachtsysteem. De belangrijkste argumenten bij het instantiëren van een OleMenuCommand is de functie waarmee de opdracht (AddColumnGuideExecuted) wordt geïmplementeerd, de functie die moet worden aangeroepen wanneer Visual Studio een menu met de opdracht (AddColumnGuideBeforeQueryStatus) en de opdracht-id weergeeft. Visual Studio roept de querystatusfunctie aan voordat een opdracht in een menu wordt weergegeven, zodat de opdracht zichzelf onzichtbaar of grijs kan maken voor een bepaalde weergave van het menu (bijvoorbeeld het uitschakelen van Kopiëren als er geen selectie is), het pictogram ervan kan wijzigen of zelfs de naam ervan wijzigen (bijvoorbeeld van Iets toevoegen om iets te verwijderen), enzovoort. De opdracht-id moet overeenkomen met een opdracht-id die is gedeclareerd in het bestand .vsct. De tekenreeksen voor de opdrachtenset en het kolomhulplijnen toevoegen commando moeten overeenkomen tussen het .vsct bestand en de ColumnGuideCommands.cs.

De volgende regel biedt hulp bij het aanroepen van de opdracht via het opdrachtvenster (zie hieronder):

_addGuidelineCommand.ParametersDescription = "<column>";

querystatus. De querystatusfuncties AddColumnGuideBeforeQueryStatus en RemoveColumnGuideBeforeQueryStatus controleren enkele instellingen (zoals het maximum aantal hulplijnen of het maximaal aantal kolommen) of er een kolomhulplijn is om te verwijderen. Ze schakelen de opdrachten in als de voorwaarden juist zijn. Querystatusfuncties moeten efficiënt zijn omdat ze worden uitgevoerd telkens wanneer Visual Studio een menu weergeeft en voor elke opdracht in het menu.

functie AddColumnGuideExecuted uitgevoerd. Het interessante aspect van het toevoegen van een gids is het bepalen van de weergave van de editor en de cursorpositie. Eerst roept deze functie GetApplicableColumnaan, waarmee wordt gecontroleerd of er een door de gebruiker opgegeven argument is in de gebeurtenisargumenten van de opdrachthandler en als er geen is, controleert de functie de weergave van de editor:

private int GetApplicableColumn(EventArgs e)
{
    var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
    if (!string.IsNullOrEmpty(inValue))
    {
        int column;
        if (!int.TryParse(inValue, out column) || column < 0)
            throw new ArgumentException("Invalid column");
        return column;
    }

    return GetCurrentEditorColumn();
}

GetCurrentEditorColumn moet een beetje graven om een IWpfTextView weergave van de code te krijgen. Als u tracering uitvoert via GetActiveTextView, GetActiveViewen GetTextViewFromVsTextView, kunt u zien hoe u dat doet. De volgende code is de relevante geabstraheerde code, te beginnen met de huidige selectie, vervolgens het frame van de selectie op te halen, daarna de DocView van het frame op te halen als een IVsTextView, vervolgens een IVsUserData op te halen uit de IVsTextView, daarna een weergavehost op te halen, en ten slotte de IWpfTextView te verkrijgen.

   IVsMonitorSelection selection =
       this.ServiceProvider.GetService(typeof(IVsMonitorSelection))
           as IVsMonitorSelection;
   object frameObj = null;

ErrorHandler.ThrowOnFailure(selection.GetCurrentElementValue(
                                (uint)VSConstants.VSSELELEMID.SEID_DocumentFrame,
                                out frameObj));

   IVsWindowFrame frame = frameObj as IVsWindowFrame;
   if (frame == null)
       <<do nothing>>;

...
   object pvar;
   ErrorHandler.ThrowOnFailure(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView,
                                                  out pvar));

   IVsTextView textView = pvar as IVsTextView;
   if (textView == null)
   {
       IVsCodeWindow codeWin = pvar as IVsCodeWindow;
       if (codeWin != null)
       {
           ErrorHandler.ThrowOnFailure(codeWin.GetLastActiveView(out textView));
       }
   }

...
   if (textView == null)
       <<do nothing>>

   IVsUserData userData = textView as IVsUserData;
   if (userData == null)
       <<do nothing>>

   object objTextViewHost;
   if (VSConstants.S_OK
           != userData.GetData(Microsoft.VisualStudio.Editor.DefGuidList
                                                            .guidIWpfTextViewHost,
                                out objTextViewHost))
   {
       <<do nothing>>
   }

   IWpfTextViewHost textViewHost = objTextViewHost as IWpfTextViewHost;
   if (textViewHost == null)
       <<do nothing>>

   IWpfTextView textView = textViewHost.TextView;

Zodra u een IWpfTextView hebt, kunt u de kolom ophalen waarin de caret zich bevindt:

private static int GetCaretColumn(IWpfTextView textView)
{
    // This is the code the editor uses to populate the status bar.
    Microsoft.VisualStudio.Text.Formatting.ITextViewLine caretViewLine =
        textView.Caret.ContainingTextViewLine;
    double columnWidth = textView.FormattedLineSource.ColumnWidth;
    return (int)(Math.Round((textView.Caret.Left - caretViewLine.Left)
                                / columnWidth));
}

Met de huidige kolom waarop de gebruiker heeft geklikt, roept de code alleen de instellingenbeheerder aan om de kolom toe te voegen of te verwijderen. De instellingenbeheerder activeert de gebeurtenis waarop alle ColumnGuideAdornment objecten luisteren. Wanneer de gebeurtenis wordt geactiveerd, werken deze objecten hun bijbehorende tekstweergaven bij met nieuwe instellingen voor kolomrichtlijnen.

Opdracht aanroepen vanuit het opdrachtvenster

Met het voorbeeld van kolomhulplijnen kunnen gebruikers twee opdrachten vanuit het opdrachtvenster aanroepen als een vorm van uitbreidbaarheid. Als u de opdracht Weergave | Andere Windows | Opdrachtvenster gebruikt, ziet u het opdrachtvenster. U kunt interageren met het opdrachtvenster door "bewerken" in te voeren. Met autocompletie van opdrachtnamen en het argument 120 krijgt u het volgende resultaat:

> Edit.AddColumnGuide 120
>

De onderdelen van het voorbeeld die dit gedrag mogelijk maken, bevinden zich in de .vsct bestandsdeclaraties, de ColumnGuideCommands klasseconstructor wanneer deze opdrachthandlers koppelt en de implementaties van de opdrachthandler die gebeurtenisargumenten controleren.

U hebt '<CommandFlag>CommandWellOnly</CommandFlag>' gezien in het .vsct-bestand en plaatsingen in het hoofdmenu Bewerken, ook al worden de opdrachten niet weergegeven in de gebruikersinterface van het Bewerken-menu. Als ze zich in het hoofdmenu Bewerken bevinden, krijgen ze namen zoals Edit.AddColumnGuide. De opdrachtgroepdeclaratie die de vier opdrachten bevat, plaatst de groep rechtstreeks in het menu Bewerken:

<Group guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
             priority="0xB801">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
      </Group>

De knoppensectie heeft later de opdracht CommandWellOnly gedeclareerd om deze onzichtbaar te houden in het hoofdmenu en ze met AllowParamsverklaard.

<Button guid="guidColumnGuidesCommandSet" id="cmdidAddColumnGuide"
        priority="0x0100" type="Button">
  <Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
  <Icon guid="guidImages" id="bmpPicAddGuide" />
  <CommandFlag>CommandWellOnly</CommandFlag>
  <CommandFlag>AllowParams</CommandFlag>

U merkte op dat de commandohandlercode in de constructor van de ColumnGuideCommands-klasse een beschrijving van de toegestane parameter gaf.

_addGuidelineCommand.ParametersDescription = "<column>";

U zag dat de functie GetApplicableColumn controleert of OleMenuCmdEventArgs een waarde heeft voordat deze de weergave van de editor controleert voor een huidige kolom.

private int GetApplicableColumn(EventArgs e)
{
    var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
    if (!string.IsNullOrEmpty(inValue))
    {
        int column;
        if (!int.TryParse(inValue, out column) || column < 0)
            throw new ArgumentException("Invalid column");
        return column;
    }

Probeer uw extensie

U kunt nu op F5 drukken om de extensie kolomhulplijnen uit te voeren. Open een tekstbestand en gebruik het contextmenu van de editor om hulplijnen toe te voegen, ze te verwijderen en hun kleur te wijzigen. Klik in de tekst (niet in lege ruimte voorbij het einde van de regel) om een kolomhulplijn toe te voegen, of anders voegt de editor deze toe aan de laatste kolom van deze regel. Als u het opdrachtvenster gebruikt en de opdrachten aanroept met een argument, kunt u overal kolomhulplijnen toevoegen.

Als u verschillende opdrachtplaatsingen wilt proberen, namen wilt wijzigen, pictogrammen wilt wijzigen, enzovoort, en u problemen ondervindt met Visual Studio met de meest recente code in menu's, kunt u de experimentele hive opnieuw instellen waarin u fouten opspoort. Open het startmenu van Windows en typ opnieuw instellen. Zoek de opdracht en voer deze uit: Stel de volgende Visual Studio Experimentele Instantie opnieuw in. Met deze opdracht wordt het experimentele registerbestand van alle extensieonderdelen opgeschoond. Er worden geen instellingen van onderdelen opgeschoond, dus alle handleidingen die u had toen u visual Studio's experimentele hive afsluit, zijn er nog steeds wanneer uw code het instellingenarchief leest bij de volgende lancering.

Het afgeronde codeproject

Er is binnenkort een GitHub-project van Visual Studio Extensibility-voorbeelden en het voltooide project is er. Dit artikel wordt bijgewerkt om daar te verwijzen wanneer dat gebeurt. Het voltooide voorbeeldproject kan verschillende guid's hebben en heeft een andere bitmapstrook voor de opdrachtpictogrammen.

U kunt een versie van de functie voor kolomhulplijnen uitproberen met deze Visual Studio Gallery extensie.