Share via


Procedura dettagliata: Creare una struttura di visualizzazione, comandi e impostazioni (guide alle colonne)

È possibile estendere l'editor di testo/codice di Visual Studio con comandi ed effetti di visualizzazione. Questo articolo illustra come iniziare a usare una funzionalità di estensione comune, le guide alle colonne. Le guide alle colonne sono linee visivamente chiare disegnate nella visualizzazione dell'editor di testo per facilitare la gestione del codice a specifiche larghezze di colonna. In particolare, il codice formattato può essere importante per gli esempi inclusi in documenti, post di blog o report sui bug.

Questa procedura dettagliata è costituita dai passaggi seguenti:

  • Creare un progetto VSIX

  • Aggiungere un elemento decorativo della visualizzazione dell'editor

  • Aggiunta del supporto per il salvataggio e il recupero delle impostazioni (dove disegnare le guide alle colonne e il relativo colore)

  • Aggiungere comandi (aggiungere/rimuovere le guide alle colonne, modificarne il colore)

  • Posizionare i comandi nel menu Modifica e nei menu di scelta rapida del documento di testo

  • Aggiungere il supporto per richiamare i comandi dalla finestra di comando di Visual Studio

    È possibile provare una versione della funzionalità guide alle colonne con questa estensione di Visual Studio Gallery.

    Nota

    In questa procedura dettagliata si incolla una grande quantità di codice in alcuni file generati dai modelli di estensione di Visual Studio. Tuttavia, a breve questa procedura dettagliata farà riferimento a una soluzione completata in GitHub con altri esempi di estensione. Il codice completato è leggermente diverso in quanto include icone di comando reali invece di usare icone generictemplate.

Configurare la soluzione

Prima di tutto, si crea un progetto VSIX, si aggiunge una struttura di visualizzazione dell'editor e quindi si aggiunge un comando (che aggiunge un VSPackage al proprio comando). L'architettura di base è la seguente:

  • È disponibile un listener di creazione della visualizzazione testo che crea un ColumnGuideAdornment oggetto per visualizzazione. Questo oggetto è in ascolto degli eventi relativi alla modifica o alla modifica delle impostazioni della visualizzazione, all'aggiornamento o alla ridisegnazione delle guide di colonna in base alle esigenze.

  • È disponibile un GuidesSettingsManager oggetto che gestisce la lettura e la scrittura dall'archiviazione delle impostazioni di Visual Studio. Gestione impostazioni include anche operazioni per aggiornare le impostazioni che supportano i comandi utente (aggiungere colonna, rimuovere colonna, modificare il colore).

  • È necessario un pacchetto VSIP se si hanno comandi utente, ma è solo codice boilerplate che inizializza l'oggetto implementazione dei comandi.

  • È presente un ColumnGuideCommands oggetto che esegue i comandi utente e associa i gestori dei comandi per i comandi dichiarati nel file vsct .

    VSIX. Usa file | Nuovo... comando per creare un progetto. Scegliere il nodo Estendibilità in C# nel riquadro di spostamento a sinistra e scegliere Progetto VSIX nel riquadro destro. Immettere il nome ColumnGuides e scegliere OK per creare il progetto.

    Visualizzazione della struttura. Premere il pulsante destro del puntatore sul nodo del progetto nel Esplora soluzioni. Scegliere Aggiungi | Nuovo elemento ... comando per aggiungere un nuovo elemento decorativo della visualizzazione. Scegliere Estendibilità | Editor nel riquadro di spostamento a sinistra e scegliere Editor Viewport Adornment nel riquadro destro. Immettere il nome ColumnGuideAdornment come nome dell'elemento e scegliere Aggiungi per aggiungerlo.

    È possibile visualizzare questo modello di elemento aggiunto due file al progetto (nonché riferimenti e così via): ColumnGuideAdornment.cs e ColumnGuideAdornmentTextViewCreationListener.cs. I modelli disegnano un rettangolo viola nella visualizzazione. Nella sezione seguente si modificano due righe nel listener di creazione della visualizzazione e si sostituisce il contenuto di ColumnGuideAdornment.cs.

    Comandi. In Esplora soluzioni premere il pulsante del puntatore destro sul nodo del progetto. Scegliere Aggiungi | Nuovo elemento ... comando per aggiungere un nuovo elemento decorativo della visualizzazione. Scegliere Estendibilità | VSPackage nel riquadro di spostamento sinistro e scegliere Comando personalizzato nel riquadro destro. Immettere il nome ColumnGuideCommands come nome dell'elemento e scegliere Aggiungi. Oltre a diversi riferimenti, l'aggiunta dei comandi e del pacchetto ha aggiunto anche ColumnGuideCommands.cs, ColumnGuideCommandsPackage.cs e ColumnGuideCommandsPackage.vsct. Nella sezione seguente sostituire il contenuto del primo e dell'ultimo file per definire e implementare i comandi.

Configurare il listener di creazione della visualizzazione testo

Aprire ColumnGuideAdornmentTextViewCreationListener.cs nell'editor. Questo codice implementa un gestore per ogni volta che Visual Studio crea visualizzazioni di testo. Esistono attributi che controllano quando viene chiamato il gestore a seconda delle caratteristiche della visualizzazione.

Il codice deve anche dichiarare un livello di adornazione. Quando l'editor aggiorna le visualizzazioni, ottiene i livelli di adornazione per la visualizzazione e da questo ottiene gli elementi di adornazione. È possibile dichiarare l'ordinamento del livello rispetto ad altri con attributi. Sostituire la riga seguente:

[Order(After = PredefinedAdornmentLayers.Caret)]

con queste due righe:

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

La riga sostituita si trova in un gruppo di attributi che dichiarano un livello decorativo. La prima riga modificata cambia solo quando vengono visualizzate le righe della guida di colonna. Disegnando le linee "prima" il testo nella visualizzazione significa che appaiono dietro o sotto il testo. La seconda riga dichiara che le decorazioni della guida di colonna sono applicabili alle entità di testo che soddisfano la nozione di documento, ma è possibile dichiarare la decorazione, ad esempio, solo per il testo modificabile. Sono disponibili altre informazioni nei punti di estensione del servizio di linguaggio e dell'editor

Implementare gestione impostazioni

Sostituire il contenuto di Guides Impostazioni Manager.cs con il codice seguente (illustrato di seguito):

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;

    }
}

La maggior parte di questo codice crea e analizza il formato delle impostazioni: "RGB(int, int,int>)< int>, <int<>>, ...".<>< Gli interi alla fine sono le colonne basate su una colonna in cui si desiderano guide di colonna. L'estensione delle guide di colonna acquisisce tutte le relative impostazioni in una singola stringa di valori di impostazione.

Ci sono alcune parti del codice che vale la pena evidenziare. La riga di codice seguente ottiene il wrapper gestito di Visual Studio per l'archiviazione delle impostazioni. Nella maggior parte dei casi, questa operazione viene astratta nel Registro di sistema di Windows, ma questa API è indipendente dal meccanismo di archiviazione.

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

L'archiviazione delle impostazioni di Visual Studio usa un identificatore di categoria e un identificatore di impostazione per identificare in modo univoco tutte le impostazioni:

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

Non è necessario usare "Text Editor" come nome di categoria. Puoi scegliere qualsiasi cosa ti piaccia.

Le prime funzioni sono i punti di ingresso per modificare le impostazioni. Controllano vincoli di alto livello, ad esempio il numero massimo di guide consentite. Chiamano WriteSettingsquindi , che compone una stringa di impostazioni e imposta la proprietà GuideLinesConfiguration. L'impostazione di questa proprietà salva il valore delle impostazioni nell'archivio delle impostazioni di Visual Studio e attiva l'evento SettingsChanged per aggiornare tutti gli ColumnGuideAdornment oggetti, ognuno associato a una visualizzazione testo.

Esistono un paio di funzioni del punto di ingresso, ad esempio CanAddGuideline, che vengono usate per implementare comandi che modificano le impostazioni. Quando Visual Studio visualizza i menu, esegue una query sulle implementazioni dei comandi per verificare se il comando è attualmente abilitato, qual è il nome e così via. Di seguito viene illustrato come associare questi punti di ingresso per le implementazioni dei comandi. Per altre informazioni sui comandi, vedere Estendere menu e comandi.

Implementare la classe ColumnGuideAdornment

Viene creata un'istanza della ColumnGuideAdornment classe per ogni visualizzazione di testo che può avere oggetti decorativi. Questa classe è in ascolto degli eventi relativi alla modifica delle impostazioni o alla modifica della visualizzazione e alle guide di aggiornamento o ridisegno delle colonne in base alle esigenze.

Sostituire il contenuto di ColumnGuideAdornment.cs con il codice seguente (illustrato di seguito):

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);
        }
    }

}

Le istanze di questa classe mantengono l'oggetto associato IWpfTextView e un elenco di Line oggetti disegnati nella visualizzazione.

Il costruttore (chiamato da ColumnGuideAdornmentTextViewCreationListener quando Visual Studio crea nuove visualizzazioni) crea gli oggetti della guida Line di colonna. Il costruttore aggiunge anche i gestori per l'evento SettingsChanged (definito in GuidesSettingsManager) e gli eventi LayoutChanged di visualizzazione e Closed.

L'evento LayoutChanged viene generato a causa di diversi tipi di modifiche nella visualizzazione, tra cui quando Visual Studio crea la visualizzazione. Il OnViewLayoutChanged gestore chiama AddGuidelinesToAdornmentLayer per l'esecuzione. Il codice in OnViewLayoutChanged determina se è necessario aggiornare le posizioni delle righe in base a modifiche quali modifiche alle dimensioni del carattere, alla visualizzazione dei margini, allo scorrimento orizzontale e così via. Il codice in UpdatePositions fa in modo che le righe della guida di disegno tra caratteri o subito dopo la colonna di testo che si trova nell'offset di caratteri specificato nella riga di testo.

Ogni volta che le impostazioni modificano la SettingsChanged funzione ricreano tutti gli Line oggetti con le nuove impostazioni. Dopo aver impostato le posizioni della riga, il codice rimuove tutti gli oggetti precedenti Line dal ColumnGuideAdornment livello di adornazione e ne aggiunge i nuovi.

Definire i comandi, i menu e i posizionamento dei menu

Può essere necessario dichiarare comandi e menu, posizionare gruppi di comandi o menu in vari altri menu e associare i gestori dei comandi. Questa procedura dettagliata illustra il funzionamento dei comandi in questa estensione, ma per informazioni più approfondite, vedere Estendere menu e comandi.

Introduzione al codice

L'estensione Guide di colonna mostra la dichiarazione di un gruppo di comandi che appartengono insieme (aggiungere colonna, rimuovere colonna, modificare il colore della riga) e quindi posizionare tale gruppo in un sottomenu del menu di scelta rapida dell'editor. L'estensione Guide di colonna aggiunge anche i comandi al menu Modifica principale, ma li mantiene invisibili, descritti come modello comune di seguito.

Esistono tre parti dell'implementazione dei comandi: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct e ColumnGuideCommands.cs. Il codice generato dai modelli inserisce un comando nel menu Strumenti che apre una finestra di dialogo come implementazione. È possibile esaminare come viene implementato nei file con estensione vsct e ColumnGuideCommands.cs poiché è semplice. Sostituire il codice in questi file di seguito.

Il codice del pacchetto contiene dichiarazioni boilerplate necessarie per Visual Studio per individuare che l'estensione offre comandi e per trovare dove inserire i comandi. Quando il pacchetto viene inizializzato, crea un'istanza della classe di implementazione dei comandi. Per altre informazioni sui pacchetti relativi ai comandi, vedere Estendere menu e comandi.

Un modello di comandi comune

I comandi nell'estensione Guide di colonna sono un esempio di modello molto comune in Visual Studio. I comandi correlati sono stati inseriti in un gruppo e tale gruppo viene inserito in un menu principale, spesso con "<CommandFlag>CommandWellOnly</CommandFlag>" impostato per rendere invisibile il comando. L'inserimento dei comandi nei menu principali (ad esempio Modifica) assegna loro nomi interessanti (ad esempio Edit.AddColumnGuide), utili per trovare i comandi quando si riassegnare i tasti di scelta rapida in Opzioni strumenti. È utile anche per ottenere il completamento quando si richiamano comandi dalla finestra di comando.

Si aggiunge quindi il gruppo di comandi ai menu di scelta rapida o ai sottomenu in cui si prevede che l'utente usi i comandi. Visual Studio considera CommandWellOnly come un flag di invisibilità solo per i menu principali. Quando si inserisce lo stesso gruppo di comandi in un menu di scelta rapida o in un sottomenu, i comandi sono visibili.

Come parte del modello comune, l'estensione Guide di colonna crea un secondo gruppo che contiene un singolo sottomenu. Il sottomenu a sua volta contiene il primo gruppo con i comandi della guida a quattro colonne. Il secondo gruppo che contiene il sottomenu è l'asset riutilizzabile inserito in vari menu di scelta rapida, che inserisce un sottomenu in tali menu di scelta rapida.

File con estensione vsct

Il file con estensione vsct dichiara i comandi e dove si trovano, insieme alle icone e così via. Sostituire il contenuto del file con estensione vsct con il codice seguente (illustrato di seguito):

<?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. Per consentire a Visual Studio di trovare i gestori dei comandi e richiamarli, è necessario assicurarsi che il GUID del pacchetto dichiarato nel file ColumnGuideCommandsPackage.cs (generato dal modello di elemento di progetto) corrisponda al GUID del pacchetto dichiarato nel file con estensione vsct (copiato da sopra). Se si usano nuovamente questo codice di esempio, assicurarsi di avere un GUID diverso in modo che non si sia in conflitto con altri utenti che potrebbero aver copiato questo codice.

Trovare questa riga in ColumnGuideCommandsPackage.cs e copiare il GUID tra le virgolette:

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

Incollare quindi il GUID nel file vsct in modo da avere la riga seguente nelle Symbols dichiarazioni:

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

I GUID per il set di comandi e il file di immagine bitmap devono essere univoci anche per le estensioni:

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

Tuttavia, non è necessario modificare il set di comandi e i GUID dell'immagine bitmap in questa procedura dettagliata per ottenere il codice da usare. Il GUID del set di comandi deve corrispondere alla dichiarazione nel file ColumnGuideCommands.cs , ma si sostituisce anche il contenuto del file, quindi i GUID corrispondono.

Altri GUID nel file vsct identificano i menu preesistenti a cui vengono aggiunti i comandi della guida di colonna, in modo che non cambino mai.

Sezioni di file. . vsct include tre sezioni esterne: comandi, posizionamento e simboli. La sezione comandi definisce gruppi di comandi, menu, pulsanti o voci di menu e bitmap per le icone. La sezione posizionamenti dichiara dove i gruppi passano ai menu o posizionamenti aggiuntivi nei menu preesistenti. La sezione simboli dichiara gli identificatori usati altrove nel file con estensione vsct, che rende il codice vsct più leggibile rispetto all'uso di GUID e numeri esadecimale ovunque.

Sezione Comandi, definizioni di gruppi. La sezione dei comandi definisce innanzitutto i gruppi di comandi. I gruppi di comandi sono comandi visualizzati nei menu con righe grigie leggere che separano i gruppi. Un gruppo può anche riempire un intero sottomenu, come in questo esempio, e in questo caso non viene visualizzato il grigio che separa le righe. I file con estensione vsct dichiarano due gruppi, l'elemento GuidesMenuItemsGroup padre del IDM_VS_MENU_EDIT (menu Modifica principale) e l'elemento GuidesContextMenuGroup padre del IDM_VS_CTXT_CODEWIN (menu di scelta rapida dell'editor di codice).

La seconda dichiarazione di gruppo ha una 0x0600 priorità:

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

L'idea è inserire il sottomenu delle guide di colonna alla fine di qualsiasi menu di scelta rapida a cui aggiungere il gruppo di menu secondario. Tuttavia, non si dovrebbe presupporre che si conosca meglio e forzare il sottomenu per essere sempre ultimo usando una priorità di 0xFFFF. È necessario sperimentare con il numero per vedere dove si trova il sottomenu nei menu di scelta rapida in cui è posizionato. In questo caso, 0x0600 è abbastanza alto da inserirlo alla fine dei menu per quanto si può vedere, ma lascia spazio a qualcun altro per progettare la loro estensione in modo che sia inferiore all'estensione delle guide di colonna, se opportuno.

Sezione Comandi, definizione del menu. La sezione del comando definisce quindi il sottomenu GuidesSubMenu, padre dell'oggetto GuidesContextMenuGroup. GuidesContextMenuGroup è il gruppo aggiunto a tutti i menu di scelta rapida pertinenti. Nella sezione posizionamento il codice inserisce il gruppo con i comandi della guida a quattro colonne in questo sottomenu.

Sezione Comandi, definizioni di pulsanti. La sezione comandi definisce quindi le voci di menu o i pulsanti che sono i comandi delle guide a quattro colonne. CommandWellOnly, illustrato in precedenza, indica che i comandi sono invisibili quando vengono posizionati in un menu principale. Due delle dichiarazioni dei pulsanti delle voci di menu (aggiungi guida e rimuovi guida) hanno anche un AllowParams flag:

<CommandFlag>AllowParams</CommandFlag>

Questo flag abilita, insieme alla presenza di posizionamento dei menu principali, il comando per ricevere argomenti quando Visual Studio richiama il gestore dei comandi. Se l'utente esegue il comando dalla finestra di comando, l'argomento viene passato al gestore comandi negli argomenti dell'evento.

Sezioni di comando, definizioni bitmap. Infine, la sezione comandi dichiara le bitmap o le icone usate per i comandi. Questa sezione è una semplice dichiarazione che identifica la risorsa del progetto ed elenca gli indici basati su un'unica delle icone usate. La sezione simboli del file con estensione vsct dichiara i valori degli identificatori usati come indici. Questa procedura dettagliata usa la striscia bitmap fornita con il modello di elemento di comando personalizzato aggiunto al progetto.

Sezione Posizionamenti. Dopo che la sezione dei comandi è la sezione placements. Il primo è il punto in cui il codice aggiunge il primo gruppo descritto in precedenza che contiene i comandi della guida a quattro colonne al sottomenu in cui vengono visualizzati i comandi:

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

Tutti gli altri posizionamenti aggiungono GuidesContextMenuGroup (che contiene ) ad altri menu di scelta rapida dell'editor GuidesSubMenu. Quando il codice ha dichiarato , GuidesContextMenuGroupè stato padre del menu di scelta rapida dell'editor di codice. Ecco perché non viene visualizzato un posizionamento per il menu di scelta rapida dell'editor di codice.

Sezione Simboli. Come indicato in precedenza, la sezione simboli dichiara gli identificatori usati altrove nel file con estensione vsct, in modo da rendere il codice vsct più leggibile rispetto all'uso di GUID e numeri esadecimale ovunque. I punti importanti di questa sezione sono che il GUID del pacchetto deve essere d'accordo con la dichiarazione nella classe del pacchetto. E il GUID del set di comandi deve accettare la dichiarazione nella classe di implementazione del comando.

Implementare i comandi

Il file ColumnGuideCommands.cs implementa i comandi e associa i gestori. Quando Visual Studio carica il pacchetto e lo inizializza, il pacchetto chiama Initialize a sua volta la classe di implementazione dei comandi. L'inizializzazione dei comandi crea semplicemente un'istanza della classe e il costruttore associa tutti i gestori dei comandi.

Sostituire il contenuto del file ColumnGuideCommands.cs con il codice seguente (illustrato di seguito):

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);
                }
            }
        }

    }
}

Correzione dei riferimenti. A questo punto manca un riferimento. Premere il pulsante destro del puntatore sul nodo Riferimenti nella Esplora soluzioni. Scegliere il comando Aggiungi ... . Nella finestra di dialogo Aggiungi riferimento è presente una casella di ricerca nell'angolo superiore destro. Immettere "editor" (senza virgolette doppie). Scegliere l'elemento Microsoft.VisualStudio.Editor (è necessario selezionare la casella a sinistra dell'elemento, non solo selezionare l'elemento) e scegliere OK per aggiungere il riferimento.

Inizializzazione. Quando la classe del pacchetto viene inizializzata, chiama Initialize sulla classe di implementazione dei comandi. L'inizializzazione ColumnGuideCommands crea un'istanza della classe e salva l'istanza della classe e il riferimento al pacchetto nei membri della classe.

Si esaminerà ora uno degli hook-up del gestore dei comandi dal costruttore della classe:

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

Si crea un oggetto OleMenuCommand. Visual Studio usa il sistema di comandi di Microsoft Office. Gli argomenti chiave quando si crea un'istanza OleMenuCommand di è la funzione che implementa il comando (AddColumnGuideExecuted), la funzione da chiamare quando Visual Studio visualizza un menu con il comando (AddColumnGuideBeforeQueryStatus) e l'ID comando. Visual Studio chiama la funzione di stato della query prima di visualizzare un comando in un menu in modo che il comando possa rendere il comando invisibile o disattivato per una visualizzazione specifica del menu (ad esempio, disabilitando Copia se non è presente alcuna selezione), modificarne l'icona o persino modificarne il nome (ad esempio, da Aggiungi qualcosa a rimuovi), E così via. L'ID comando deve corrispondere a un ID comando dichiarato nel file vsct . Le stringhe per il set di comandi e le guide di colonna aggiungono il comando devono corrispondere tra il file vsct e ColumnGuideCommands.cs.

La riga seguente fornisce assistenza per quando gli utenti richiamano il comando tramite la finestra di comando (illustrata di seguito):

_addGuidelineCommand.ParametersDescription = "<column>";

Stato della query. Le funzioni AddColumnGuideBeforeQueryStatus di stato della query e RemoveColumnGuideBeforeQueryStatus controllano alcune impostazioni(ad esempio il numero massimo di guide o la colonna massima) o se è disponibile una guida alla colonna da rimuovere. Abilitano i comandi se le condizioni sono corrette. Le funzioni di stato delle query devono essere efficienti perché vengono eseguite ogni volta che Visual Studio visualizza un menu e per ogni comando nel menu.

Funzione AddColumnGuideExecuted. La parte interessante dell'aggiunta di una guida è capire la visualizzazione dell'editor corrente e la posizione del cursore. Prima di tutto, questa funzione chiama GetApplicableColumn, che controlla se è presente un argomento fornito dall'utente negli argomenti dell'evento del gestore comandi e, se non è presente, la funzione controlla la visualizzazione dell'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 deve scavare un po 'per ottenere una IWpfTextView visualizzazione del codice. Se si esegue la traccia tramite GetActiveTextView, GetActiveViewe GetTextViewFromVsTextView, è possibile vedere come eseguire questa operazione. Il codice seguente è il codice pertinente astratto, a partire dalla selezione corrente, quindi ottenere il frame della selezione, quindi ottenere il docView del frame come , IVsTextViewquindi ottenere un oggetto IVsUserData da IVsTextView, quindi ottenere un host di visualizzazione e infine IWpfTextView:

   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;

Dopo aver creato un oggetto IWpfTextView, è possibile ottenere la colonna in cui si trova il cursore:

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));
}

Con la colonna corrente in cui l'utente ha fatto clic, il codice chiama semplicemente il gestore delle impostazioni per aggiungere o rimuovere la colonna. Gestione impostazioni attiva l'evento in cui tutti gli ColumnGuideAdornment oggetti sono in ascolto. Quando l'evento viene generato, questi oggetti aggiornano le visualizzazioni di testo associate con le nuove impostazioni della guida alle colonne.

Richiamare il comando dalla finestra di comando

L'esempio di guide di colonna consente agli utenti di richiamare due comandi dalla finestra di comando come forma di estendibilità. Se si usa la visualizzazione | Altre finestre | Comando Finestra di comando, è possibile visualizzare la finestra di comando. È possibile interagire con la finestra di comando immettendo "edit." e con il completamento del nome del comando e specificando l'argomento 120, si ottiene il risultato seguente:

> Edit.AddColumnGuide 120
>

Le parti dell'esempio che abilitano questo comportamento si trovano nelle dichiarazioni di file con estensione vsct , nel ColumnGuideCommands costruttore della classe quando associa i gestori dei comandi e nelle implementazioni del gestore comandi che controllano gli argomenti dell'evento.

È stato visualizzato "<CommandFlag>CommandWellOnly</CommandFlag>" nel file con estensione vsct , nonché nel menu principale Modifica anche se i comandi non vengono visualizzati nell'interfaccia utente del menu Modifica . La loro presenza nel menu principale Modifica assegna loro nomi come Edit.AddColumnGuide. La dichiarazione di gruppo dei comandi che contiene i quattro comandi posizionati direttamente nel menu Modifica :

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

La sezione dei pulsanti in seguito ha dichiarato i comandi per mantenerli CommandWellOnly invisibili nel menu principale e li ha dichiarati con AllowParams:

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

Nel costruttore della classe è stato visualizzato il codice di associazione del ColumnGuideCommands gestore dei comandi fornito una descrizione del parametro consentito:

_addGuidelineCommand.ParametersDescription = "<column>";

La funzione ha OleMenuCmdEventArgs verificato la GetApplicableColumn presenza di un valore prima di controllare la visualizzazione dell'editor per una colonna corrente:

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;
    }

Provare l'estensione

È ora possibile premere F5 per eseguire l'estensione Guide di colonna. Aprire un file di testo e usare il menu di scelta rapida dell'editor per aggiungere linee guida, rimuoverle e modificarne il colore. Fare clic sul testo (non su spazi vuoti passati alla fine della riga) per aggiungere una guida di colonna oppure l'editor lo aggiunge all'ultima colonna della riga. Se si usa la finestra di comando e si richiamano i comandi con un argomento, è possibile aggiungere guide di colonna ovunque.

Se si vogliono provare posizioni di comando diverse, modificare i nomi, modificare le icone e così via e si verificano problemi con Visual Studio che mostra il codice più recente nei menu, è possibile reimpostare l'hive sperimentale in cui si sta eseguendo il debug. Visualizzare il menu Start di Windows e digitare "reset". Cercare ed eseguire il comando Reimpostare l'istanza sperimentale di Visual Studio successiva. Questo comando pulisce l'hive sperimentale del Registro di sistema di tutti i componenti di estensione. Non rimuove le impostazioni dai componenti, quindi tutte le guide presenti quando si arresta l'hive sperimentale di Visual Studio sono ancora presenti quando il codice legge l'archivio delle impostazioni al successivo avvio.

Progetto di codice completato

Sarà presto disponibile un progetto GitHub di esempi di estendibilità di Visual Studio e il progetto completato sarà disponibile. Questo articolo verrà aggiornato in modo che punti in questo caso. Il progetto di esempio completato può avere guid diversi e avrà una strip di bitmap diversa per le icone dei comandi.

È possibile provare una versione della funzionalità guide alle colonne con questa estensione di Visual Studio Gallery.