Návod: Vytvoření doplňku zobrazení, příkazů a nastavení (vodítka sloupců)

Editor textu a kódu sady Visual Studio můžete rozšířit příkazy a efekty zobrazení. V tomto článku se dozvíte, jak začít s oblíbenou funkcí rozšíření, průvodci sloupci. Vodítka sloupců jsou vizuálně světlé čáry nakreslené v zobrazení textového editoru, které vám pomůžou spravovat kód na konkrétní šířku sloupců. Konkrétně formátovaný kód může být důležitý pro ukázky, které zahrnete do dokumentů, blogových příspěvků nebo zpráv o chybách.

V tomto názorném postupu:

  • Vytvoření projektu VSIX

  • Přidání doplňku zobrazení editoru

  • Přidání podpory pro ukládání a získávání nastavení (kde kreslit vodítka sloupců a jejich barvu)

  • Přidání příkazů (přidání nebo odebrání vodítek sloupců, změna jejich barvy)

  • Umístěte příkazy do místní nabídky Upravit a kontextové nabídky textového dokumentu.

  • Přidání podpory pro vyvolání příkazů z příkazového okna sady Visual Studio

    Pomocí tohoto rozšíření galerie sady Visual Studio si můžete vyzkoušet verzi funkce vodítek sloupců.

    Poznámka:

    V tomto názorném postupu vložíte velké množství kódu do několika souborů generovaných šablonami rozšíření sady Visual Studio. Brzy ale tento názorný postup bude odkazovat na dokončené řešení na GitHubu s dalšími příklady rozšíření. Dokončený kód se mírně liší v tom, že má skutečné ikony příkazů místo použití generictemplate ikon.

Nastavení řešení

Nejprve vytvoříte projekt VSIX, přidáte doplněk zobrazení editoru a pak přidáte příkaz (který přidá balíček VSPackage do vlastního příkazu). Základní architektura je následující:

  • Máte naslouchací proces pro vytváření textového ColumnGuideAdornment zobrazení, který vytvoří objekt pro každé zobrazení. Tento objekt naslouchá událostem o změně zobrazení nebo změně nastavení, aktualizaci nebo překreslení vodítek sloupců podle potřeby.

  • Existuje řešení GuidesSettingsManager pro čtení a zápis z úložiště nastavení sady Visual Studio. Správce nastavení má také operace pro aktualizaci nastavení, která podporují uživatelské příkazy (přidání sloupce, odebrání sloupce, změna barvy).

  • Existuje balíček VSIP, který je nezbytný, pokud máte uživatelské příkazy, ale je to jen často používaný kód, který inicializuje objekt implementace příkazů.

  • ColumnGuideCommands Existuje objekt, který spouští uživatelské příkazy a připojí obslužné rutiny příkazů pro příkazy deklarované v souboru .vsct.

    VSIX. Použít soubor | Nový... k vytvoření projektu. V levém navigačním podokně zvolte uzel rozšiřitelnosti v jazyce C# a v pravém podokně zvolte Projekt VSIX. Zadejte název ColumnGuides a zvolte OK a vytvořte projekt.

    Zobrazení doplňku Stiskněte pravé tlačítko ukazatele na uzlu projektu v Průzkumník řešení. Zvolte přidat | Nová položka ... přidat novou položku doplňku zobrazení. Volba rozšiřitelnosti | Editor v levém navigačním podokně a v pravém podokně zvolte Editor Viewport Adornment . Jako název položky zadejte název ColumnGuideAdornment a zvolte Přidat .

    Tato šablona položky přidala do projektu dva soubory (i odkazy atd.): ColumnGuideAdornment.cs a ColumnGuideAdornmentTextViewCreationListener.cs. Šablony nakreslely fialový obdélník v zobrazení. V následující části změníte několik řádků v naslouchacím procesu vytváření zobrazení a nahradíte obsah ColumnGuideAdornment.cs.

    Příkazy. V Průzkumník řešení stiskněte pravé tlačítko ukazatele na uzlu projektu. Zvolte přidat | Nová položka ... přidat novou položku doplňku zobrazení. Volba rozšiřitelnosti | Balíček VSPackage v levém navigačním podokně a v pravém podokně zvolte Vlastní příkaz . Jako název položky zadejte název ColumnGuideCommands a zvolte Přidat. Kromě několika odkazů přidání příkazů a balíčku také přidali ColumnGuideCommands.cs, ColumnGuideCommandsPackage.cs a ColumnGuideCommandsPackage.vsct. V následující části nahradíte obsah prvního a posledního souboru, který definuje a implementuje příkazy.

Nastavení naslouchacího procesu vytváření textového zobrazení

Otevřete ColumnGuideAdornmentTextViewCreationListener.cs v editoru. Tento kód implementuje obslužnou rutinu pro pokaždé, když Visual Studio vytvoří zobrazení textu. Existují atributy, které řídí, kdy obslužná rutina je volána v závislosti na charakteristikách zobrazení.

Kód také musí deklarovat vrstvu doplňku. Když editor aktualizuje zobrazení, získá vrstvy doplňku pro zobrazení a z toho získá prvky doplňku. Můžete deklarovat pořadí vrstvy vzhledem k ostatním s atributy. Nahraďte následující řádek:

[Order(After = PredefinedAdornmentLayers.Caret)]

s těmito dvěma řádky:

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

Řádek, který jste nahradili, je ve skupině atributů, které deklarují vrstvu doplňku. První řádek, který jste změnili, změní jenom tam, kde se zobrazují vodicí čáry sloupce. Kreslení čar "před" textu v zobrazení znamená, že se zobrazují za textem nebo pod textem. Druhý řádek deklaruje, že doplňky vodítka sloupců se vztahují na textové entity, které odpovídají vašemu pojmu dokumentu, ale můžete deklarovat ozdobné prvky, například pro práci pouze pro upravitelný text. Další informace najdete v bodech rozšíření služby jazyka a editoru.

Implementace správce nastavení

Obsah GuidesSettingsManager.cs nahraďte následujícím kódem (vysvětleno níže):

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;

    }
}

Většina tohoto kódu vytvoří a parsuje formát nastavení: "RGB(<int;<int>>;<int>) <int>, <int, int>, ...". Celá čísla na konci jsou sloupce založené na jednom místě, kde chcete vodítka sloupců. Rozšíření vodítka sloupců zachycuje všechna jeho nastavení v jednom řetězci hodnoty nastavení.

Existuje několik částí kódu, které stojí za zvýraznění. Následující řádek kódu získá obálku spravovanou v sadě Visual Studio pro úložiště nastavení. Ve většině případů to abstrahuje nad registrem Windows, ale toto rozhraní API je nezávislé na mechanismu úložiště.

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

Úložiště nastavení sady Visual Studio používá identifikátor kategorie a identifikátor nastavení k jednoznačné identifikaci všech nastavení:

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

Jako název kategorie nemusíte používat "Text Editor" . Můžete si vybrat cokoliv, co se vám líbí.

Prvních několik funkcí je vstupními body, které změní nastavení. Kontrolují omezení vysoké úrovně, jako je maximální povolený počet průvodců. Potom volají WriteSettings, který vytvoří řetězec nastavení a nastaví vlastnost GuideLinesConfiguration. Nastavení této vlastnosti uloží hodnotu nastavení do úložiště nastavení sady Visual Studio a aktivuje SettingsChanged událost, aby se aktualizovaly všechny ColumnGuideAdornment objekty, které jsou přidružené k textovému zobrazení.

Existuje několik funkcí vstupního bodu, například CanAddGuideline, které se používají k implementaci příkazů, které mění nastavení. Když Visual Studio zobrazuje nabídky, dotazuje se implementace příkazů, aby zjistil, jestli je příkaz aktuálně povolený, jaký je jeho název atd. Níže se dozvíte, jak tyto vstupní body připojit k implementaci příkazů. Další informace opříkazch

Implementace ColumnGuideAdornment – třída

Třída ColumnGuideAdornment se vytvoří instance pro každé textové zobrazení, které může obsahovat doplňky. Tato třída naslouchá událostem o změně zobrazení nebo změně nastavení a podle potřeby aktualizuje nebo překresluje vodítka sloupců.

Obsah ColumnGuideAdornment.cs nahraďte následujícím kódem (vysvětleno níže):

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

}

Instance této třídy uchovávají přidružené IWpfTextView objekty a seznam Line objektů nakreslených v zobrazení.

Konstruktor (volaný při ColumnGuideAdornmentTextViewCreationListener vytváření nových zobrazení sady Visual Studio) vytvoří objekty průvodce Line sloupcem. Konstruktor také přidá obslužné rutiny pro SettingsChanged událost (definované v GuidesSettingsManager) a zobrazit události LayoutChanged a Closed.

Událost se LayoutChanged aktivuje z důvodu několika druhů změn v zobrazení, včetně toho, kdy Visual Studio vytvoří zobrazení. Volání OnViewLayoutChanged obslužné rutiny AddGuidelinesToAdornmentLayer ke spuštění. Kód určuje OnViewLayoutChanged , jestli potřebuje aktualizovat pozice řádků na základě změn, jako jsou změny velikosti písma, zobrazení hřbetů, vodorovné posouvání atd. Kód způsobí UpdatePositions , že vodicí čáry budou kreslit mezi znaky nebo těsně za sloupcem textu, který je v zadaném posunu znaků na řádku textu.

Pokaždé, když nastavení změní SettingsChanged funkci, znovu vytvoří všechny Line objekty bez ohledu na to, co jsou nová nastavení. Po nastavení pozic čáry kód odebere všechny předchozí Line objekty z ColumnGuideAdornment vrstvy doplňku a přidá nové objekty.

Definování příkazů, nabídek a umístění nabídek

K deklarování příkazů a nabídek, umístění skupin příkazů nebo nabídek do různých dalších nabídek a připojení obslužných rutin příkazů může být hodně. Tento názorný postup ukazuje, jak fungují příkazy v tomto rozšíření, ale podrobnější informace najdete v tématu Rozšíření nabídek a příkazů.

Úvod do kódu

Rozšíření Vodítka sloupců zobrazuje deklarování skupiny příkazů, které patří dohromady (přidání sloupce, odebrání sloupce, změna barvy čáry) a následné umístění této skupiny do pod menu místní nabídky editoru. Rozšíření Vodítka sloupců také přidá příkazy do hlavní nabídky pro úpravy , ale zachová je neviditelné, probírané jako běžný vzor níže.

Implementace příkazů má tři části: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct a ColumnGuideCommands.cs. Kód vygenerovaný šablonami vloží příkaz do nabídky Nástroje , která jako implementaci zobrazí dialogové okno. Můžete se podívat, jak se implementuje v souborech .vsct a ColumnGuideCommands.cs , protože je to jednoduché. Kód v těchto souborech nahradíte níže.

Kód balíčku obsahuje často používané deklarace vyžadované pro Visual Studio, aby zjistilo, že rozšíření nabízí příkazy a najít, kam se mají příkazy umístit. Při inicializaci balíčku vytvoří instanci třídy implementace příkazů. Další informace o balíčcích souvisejících s příkazy naleznete v tématu Rozšíření nabídek a příkazů.

Vzor běžných příkazů

Příkazy v rozšíření Vodítka sloupců představují příklad velmi běžného vzoru v sadě Visual Studio. Související příkazy umístíte do skupiny a tuto skupinu umístíte do hlavní nabídky, často s nastavením "<CommandFlag>CommandWellOnly</CommandFlag>", aby byl příkaz neviditelný. Když umístíte příkazy do hlavních nabídek (například Upravit), dají se jim pěkné názvy (například Edit.AddColumnGuide), které jsou užitečné pro hledání příkazů při opětovném přiřazování klíčových vazeb v možnostech nástrojů. Při vyvolání příkazů z příkazového okna je také užitečné získat dokončení.

Potom přidáte skupinu příkazů do kontextových nabídek nebo dílčích nabídek, kde očekáváte, že uživatel bude tyto příkazy používat. Visual Studio se považuje CommandWellOnly za příznak nepřístupnosti pouze pro hlavní nabídky. Když umístíte stejnou skupinu příkazů do místní nabídky nebo dílčí nabídky, příkazy se zobrazí.

V rámci společného vzoru vytvoří rozšíření Vodítka sloupců druhou skupinu, která obsahuje jednu dílčí nabídku. Dílčí nabídka zase obsahuje první skupinu s příkazy průvodce čtyřmi sloupci. Druhá skupina, která obsahuje pod menu, je opakovaně použitelný prostředek, který umístíte do různých kontextových nabídek, což umístí pod menu do těchto místních nabídek.

Soubor .vsct

Soubor .vsct deklaruje příkazy a jejich umístění spolu s ikonami atd. Obsah souboru .vsct nahraďte následujícím kódem (vysvětleno níže):

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

IDENTIFIKÁTORY GUID. Aby Sada Visual Studio našla obslužné rutiny příkazů a vyvolala je, musíte zajistit, aby identifikátor GUID balíčku deklarovaný v souboru ColumnGuideCommandsPackage.cs (vygenerovaný ze šablony položky projektu) odpovídal identifikátoru GUID balíčku deklarovanému v souboru .vsct (zkopírované z výše). Pokud tento vzorový kód znovu použijete, měli byste se ujistit, že máte jiný identifikátor GUID, abyste nebyli v konfliktu s kýmkoli jiným, kdo by mohl tento kód zkopírovat.

Najděte tento řádek v ColumnGuideCommandsPackage.cs a zkopírujte identifikátor GUID z uvozovek:

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

Pak do souboru .vsct vložte identifikátor GUID, abyste měli v Symbols deklaracích následující řádek:

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

Identifikátory GUID sady příkazů a soubor obrázku rastrového obrázku by měly být jedinečné i pro vaše rozšíření:

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

Nemusíte ale měnit identifikátory GUID pro sadu příkazů a rastrové obrázky v tomto návodu, aby kód fungoval. Identifikátor GUID sady příkazů musí odpovídat deklaraci v souboru ColumnGuideCommands.cs , ale obsah tohoto souboru také nahradíte. Identifikátory GUID se proto budou shodovat.

Ostatní identifikátory GUID v souboru .vsct identifikují předem existující nabídky, do kterých se přidávají příkazy průvodce sloupcem, takže se nikdy nemění.

Oddíly souborů. . vsct má tři vnější části: příkazy, umístění a symboly. Oddíl příkazů definuje skupiny příkazů, nabídky, tlačítka nebo položky nabídky a rastrové obrázky pro ikony. Oddíl umístění deklaruje, kde skupiny přecházejí do nabídek nebo dalších umístění do již existujících nabídek. Oddíl symbolů deklaruje identifikátory používané jinde v souboru .vsct, takže kód .vsct je čitelnější než mít identifikátory GUID a šestnáctkové čísla všude.

Oddíl Příkazy, definice skupin Oddíl příkazů nejprve definuje skupiny příkazů. Skupiny příkazů jsou příkazy, které vidíte v nabídkách s mírným šedým oddělením skupin. Skupina může také vyplnit celou dílčí nabídku, jako v tomto příkladu, a v tomto případě nevidíte šedé oddělovače řádků. Soubory .vsct deklarují dvě skupiny, GuidesMenuItemsGroup které jsou nadřazené ( IDM_VS_MENU_EDIT hlavní nabídce pro úpravy ) a GuidesContextMenuGroup které jsou nadřazené IDM_VS_CTXT_CODEWIN (místní nabídka editoru kódu).

Druhá deklarace skupiny má prioritu 0x0600 :

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

Cílem je umístit dílčí nabídku vodítek sloupců na konec jakékoli místní nabídky, do které přidáte skupinu dílčích nabídek. Neměli byste ale předpokládat, že znáte nejlepší a vynutit, aby podnabídka byla vždy poslední pomocí priority 0xFFFF. Musíte experimentovat s číslem, abyste viděli, kde se nachází podadresní nabídka v místních nabídkách, kam ho umístíte. V tomto případě je dostatečně vysoký, aby ho umístil na konec nabídek, pokud vidíte, ale ponechá prostor pro někoho jiného navrhnout jeho rozšíření tak, 0x0600 aby bylo nižší než rozšíření vodítek sloupců, pokud je to žádoucí.

Oddíl Příkazy, definice nabídky. Dále oddíl příkazu definuje podnabídku GuidesSubMenu, která je nadřazená .GuidesContextMenuGroup Jedná se GuidesContextMenuGroup o skupinu, kterou přidáte do všech relevantních kontextových nabídek. V části Umístění kód umístí skupinu pomocí čtyřsloupců vodicích příkazů v této dílčí nabídce.

Oddíl Příkazy, definice tlačítek Oddíl příkazů pak definuje položky nebo tlačítka nabídky, které jsou příkazy vodítek se čtyřmi sloupci. CommandWellOnly, které je popsáno výše, znamená, že příkazy jsou při umístění do hlavní nabídky neviditelné. Dvě deklarace tlačítek položky nabídky (přidání průvodce a průvodce odebráním) mají AllowParams také příznak:

<CommandFlag>AllowParams</CommandFlag>

Tento příznak umožňuje spolu s umístěním hlavní nabídky příkaz přijímat argumenty, když Visual Studio vyvolá obslužnou rutinu příkazu. Pokud uživatel spustí příkaz z příkazového okna, předá se do obslužné rutiny příkazu v argumentech události.

Oddíly příkazů, definice rastrových obrázků Oddíl příkazů nakonec deklaruje rastrové obrázky nebo ikony použité pro příkazy. Tato část je jednoduchá deklarace, která identifikuje zdroj projektu a obsahuje jeden index použitých ikon. Oddíl symbolů souboru .vsct deklaruje hodnoty identifikátorů použitých jako indexy. Tento názorný postup používá rastrový pruh, který je součástí vlastní šablony položky příkazu přidané do projektu.

Oddíl Umístění. Jakmile je oddíl příkazů oddílem umístění. První je místo, kde kód přidá první skupinu probíranou výše, která obsahuje příkazy vodítka se čtyřmi sloupci do podnabídky, kde se příkazy zobrazí:

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

Všechna ostatní umístění přidávají GuidesContextMenuGroup (která obsahuje) do dalších kontextových nabídek editoru GuidesSubMenu. Když kód deklaroval GuidesContextMenuGroup, byl nadřazený místní nabídce editoru kódu. Proto nevidíte umístění místní nabídky editoru kódu.

Oddíl Symboly Jak je uvedeno výše, oddíl symbolů deklaruje identifikátory používané jinde v souboru .vsct, což kód .vsct je čitelnější než mít identifikátory GUID a šestnáctkové čísla všude. Důležité body v této části jsou, že identifikátor GUID balíčku musí souhlasit s deklarací ve třídě balíčku. A identifikátor GUID sady příkazů musí souhlasit s deklarací ve třídě implementace příkazu.

Implementace příkazů

Soubor ColumnGuideCommands.cs implementuje příkazy a připojí obslužné rutiny. Když Visual Studio načte balíček a inicializuje ho, balíček pak zavolá Initialize třídu implementace příkazů. Inicializace příkazů jednoduše vytvoří instanci třídy a konstruktor připojí všechny obslužné rutiny příkazů.

Obsah souboru ColumnGuideCommands.cs nahraďte následujícím kódem (vysvětleno níže):

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

    }
}

Opravte odkazy. V tuto chvíli chybí odkaz. Stiskněte tlačítko pravého ukazatele na uzlu Reference v Průzkumník řešení. Zvolte příkaz Přidat ... . Dialogové okno Přidat odkaz obsahuje vyhledávací pole v pravém horním rohu. Zadejte "editor" (bez dvojitých uvozovek). Zvolte položku Microsoft.VisualStudio.Editor (musíte zaškrtnout políčko vlevo od položky, ne jen vybrat položku) a kliknutím na OK přidejte odkaz.

Inicializace. Při inicializaci třídy balíčku volá Initialize třídy implementace příkazů. Inicializace ColumnGuideCommands vytvoří instanci třídy a uloží instanci třídy a odkaz na balíček ve členech třídy.

Pojďme se podívat na jeden z připojení obslužné rutiny příkazů z konstruktoru třídy:

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

Vytvoříte .OleMenuCommand Visual Studio používá systém příkazů systém Microsoft Office. Klíčové argumenty při vytváření instance OleMenuCommand je funkce, která implementuje příkaz (AddColumnGuideExecuted), funkce, která se má volat, když Visual Studio zobrazí nabídku s příkazem (AddColumnGuideBeforeQueryStatus) a ID příkazu. Visual Studio volá funkci stavu dotazu před zobrazením příkazu v nabídce, aby se příkaz mohl pro konkrétní zobrazení nabídky zobrazit nebo zobrazit šedě (například zakázat kopírování , pokud není vybrán), změnit její ikonu nebo dokonce změnit jeho název (například z možnosti Přidat něco k odebrání), a tak dále. ID příkazu musí odpovídat ID příkazu deklarovanému v souboru .vsct . Řetězce sady příkazů a vodítka sloupců přidávají příkaz musí odpovídat souboru .vsct a ColumnGuideCommands.cs.

Následující řádek poskytuje pomoc při vyvolání příkazu pomocí příkazového okna (vysvětleno níže):

_addGuidelineCommand.ParametersDescription = "<column>";

Stav dotazu. Funkce stavu AddColumnGuideBeforeQueryStatus dotazu a RemoveColumnGuideBeforeQueryStatus kontrola některých nastavení (například maximální počet vodítek nebo maximálního sloupce) nebo odebrání průvodce sloupcem. Pokud jsou podmínky správné, povolí příkazy. Funkce stavu dotazů musí být efektivní, protože se spustí pokaždé, když sada Visual Studio zobrazí nabídku a pro každý příkaz v nabídce.

AddColumnGuideExecuted – funkce. Zajímavou součástí přidání průvodce je zjištění aktuálního zobrazení editoru a umístění stříšky. Nejprve tato funkce volá GetApplicableColumn, která kontroluje, jestli argument zadaný uživatelem v argumentech události obslužné rutiny příkazu a pokud neexistuje, funkce zkontroluje zobrazení editoru:

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 musí se trochu podívat na IWpfTextView kód. Pokud trasujete GetActiveTextViewpřes , GetActiveViewa GetTextViewFromVsTextView, uvidíte, jak to udělat. Následující kód je relevantní kód abstrahovaný, počínaje aktuálním výběrem, následným získáním rámce výběru a získáním rámeček DocView jako objektu IVsTextView, následným získáním IVsUserData z prvku IVsTextView, získáním hostitele zobrazení a nakonec 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;

Jakmile máte IWpfTextView, můžete získat sloupec, ve kterém se nachází stříška:

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

S aktuálním sloupcem, na který uživatel klikl, stačí, když kód zavolá správci nastavení, aby sloupec přidal nebo odebral. Správce nastavení aktivuje událost, na kterou všechny ColumnGuideAdornment objekty naslouchají. Když se událost aktivuje, tyto objekty aktualizují přidružená textová zobrazení pomocí nastavení průvodce novým sloupcem.

Vyvolání příkazu z příkazového okna

Ukázka příruček sloupců umožňuje uživatelům vyvolat dva příkazy z příkazového okna jako formu rozšiřitelnosti. Pokud použijete zobrazení | Ostatní okna | Příkaz příkazového okna se zobrazí v příkazovém okně. S příkazovým oknem můžete pracovat tak, že zadáte "edit" a s dokončováním názvu příkazu a zadáním argumentu 120 získáte následující výsledek:

> Edit.AddColumnGuide 120
>

Části ukázky, které umožňují toto chování, jsou v deklarací souboru .vsct , ColumnGuideCommands konstruktor třídy při připojení obslužných rutin příkazů a implementace obslužné rutiny příkazů, které kontrolují argumenty události.

V souboru .vsct a umístění v hlavní nabídce Pro úpravy jste viděli "<CommandFlag>CommandWellOnly</CommandFlag>" i když se příkazy nezobrazují v uživatelském rozhraní nabídky Upravit. Když je budete mít v hlavní nabídce Pro úpravy , dají se jim názvy jako Edit.AddColumnGuide. Deklarace skupiny příkazů, která obsahuje čtyři příkazy, které skupinu umístily přímo do nabídky Upravit :

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

Oddíl tlačítka později deklaroval příkazy CommandWellOnly , aby byly neviditelné v hlavní nabídce a deklarovaly je pomocí 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>

Viděli jste, že obslužná rutina příkazu připojí kód v konstruktoru ColumnGuideCommands třídy za předpokladu popisu povoleného parametru:

_addGuidelineCommand.ParametersDescription = "<column>";

Před kontrolou zobrazení editoru pro aktuální sloupec jste viděli GetApplicableColumn , jestli funkce kontroluje OleMenuCmdEventArgs hodnotu:

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

Vyzkoušejte si rozšíření.

Teď můžete stisknutím klávesy F5 spustit rozšíření Vodítka sloupců. Otevřete textový soubor a pomocí místní nabídky editoru přidejte vodicí čáry, odeberte je a změňte jejich barvu. Kliknutím na text (nepředávejte konec řádku prázdné znaky) přidejte vodítko sloupce nebo ho editor přidá do posledního sloupce na řádku. Pokud použijete příkazové okno a vyvoláte příkazy s argumentem, můžete vodítka sloupců přidat kamkoli.

Pokud chcete vyzkoušet různá umístění příkazů, změnit názvy, změnit ikony a tak dále a máte problémy se sadou Visual Studio zobrazující nejnovější kód v nabídkách, můžete resetovat experimentální podregistr, ve kterém ladíte. Otevřete nabídku Start systému Windows a zadejte "resetovat". Vyhledejte a spusťte příkaz, resetujte další experimentální instanci sady Visual Studio. Tento příkaz vyčistí podregistr experimentálního registru všech komponent rozšíření. Nevyčistí nastavení ze součástí, takže při příštím spuštění se stále nacházejí žádné příručky, které jste měli při vypnutí experimentálního podregistru sady Visual Studio.

Dokončený projekt kódu

Brzy bude k dispozici projekt GitHubu s ukázkami rozšiřitelnosti sady Visual Studio a dokončený projekt tam bude. Tento článek se aktualizuje tak, aby odkazovat tam, když k tomu dojde. Dokončený ukázkový projekt může mít různé identifikátory GUID a pro ikony příkazů bude mít různé rastrové obrázky.

Pomocí tohoto rozšíření galerie sady Visual Studio si můžete vyzkoušet verzi funkce vodítek sloupců.