Bagikan melalui


Panduan: Membuat hiasan tampilan, perintah, dan pengaturan (panduan kolom)

Anda dapat memperluas editor teks/kode Visual Studio dengan perintah dan melihat efek. Artikel ini memperlihatkan kepada Anda cara memulai fitur ekstensi populer, panduan kolom. Panduan kolom adalah garis pembatas visual yang digambar pada tampilan editor teks untuk membantu Anda mengelola kode dengan lebar kolom tertentu. Secara khusus, kode yang diformat bisa penting untuk sampel yang Anda sertakan dalam dokumen, posting blog, atau laporan bug.

Dalam panduan ini, Anda:

  • Membuat proyek VSIX

  • Menambahkan hiasan tampilan editor

  • Menambahkan dukungan untuk menyimpan dan mendapatkan pengaturan (tempat menggambar panduan kolom dan warnanya)

  • Tambahkan perintah (tambahkan/hapus panduan kolom, ubah warnanya)

  • Tempatkan perintah pada menu Edit dan menu konteks dokumen teks

  • Menambahkan dukungan untuk memanggil perintah dari Jendela Perintah Visual Studio

    Anda dapat mencoba versi fitur panduan kolom dengan ekstensi Visual Studio Gallery ini.

    Nota

    Dalam panduan ini, Anda menempelkan sejumlah besar kode ke dalam beberapa file yang dihasilkan oleh templat ekstensi Visual Studio. Tetapi, segera panduan ini akan merujuk ke solusi lengkap di GitHub dengan contoh ekstensi lainnya. Kode yang diselesaikan sedikit berbeda karena memiliki ikon perintah nyata alih-alih menggunakan ikon generiktemplate.

Menyiapkan solusi

Pertama, Anda membuat proyek VSIX, menambahkan hiasan tampilan editor, kemudian menambahkan perintah (yang menambahkan VSPackage agar memiliki perintah tersebut). Arsitektur dasarnya adalah sebagai berikut:

  • Anda memiliki pendengar pembuatan tampilan teks yang membuat objek ColumnGuideAdornment per tampilan. Objek ini mendengarkan peristiwa terkait perubahan tampilan atau pengaturan, memperbarui atau menggambar ulang panduan kolom sesuai kebutuhan.

  • Ada GuidesSettingsManager yang menangani pembacaan dan penulisan dari penyimpanan pengaturan Visual Studio. Manajer pengaturan juga memiliki operasi untuk memperbarui pengaturan yang mendukung perintah pengguna (tambahkan kolom, hapus kolom, ubah warna).

  • Ada paket VSIP yang diperlukan jika Anda memiliki perintah pengguna, tetapi hanya kode boilerplate yang menginisialisasi objek implementasi perintah.

  • Ada objek ColumnGuideCommands yang menjalankan perintah pengguna dan menghubungkan handler perintah untuk perintah yang dideklarasikan dalam file .vsct.

    VSIX. Gunakan perintah File | Baru ... untuk membuat proyek. Pilih Ekstensibilitas simpul di bawah C# di panel navigasi kiri dan pilih Proyek VSIX di panel kanan. Masukkan nama ColumnGuides dan pilih OK untuk membuat proyek.

    Lihat hiasan. Tekan tombol penunjuk kanan pada simpul proyek di Penjelajah Solusi. Pilih Tambahkan | Item Baru ... perintah untuk menambahkan item hiasan tampilan baru. Pilih ekstensibilitas | Editor di panel navigasi kiri dan pilih Hiasan Tampilan Editor di panel kanan. Masukkan nama ColumnGuideAdornment sebagai nama item dan pilih Tambahkan untuk menambahkannya.

    Anda dapat melihat templat item ini menambahkan dua file ke proyek (serta referensi, dan sebagainya): ColumnGuideAdornment.cs dan ColumnGuideAdornmentTextViewCreationListener.cs. Template-template menggambar sebuah persegi panjang ungu pada tampilan. Di bagian berikut, Anda mengubah beberapa baris dalam pendengar pembuatan tampilan dan mengganti konten ColumnGuideAdornment.cs.

    Perintah. Di Penjelajah Solusi, tekan tombol penunjuk kanan pada simpul proyek. Pilih Tambahkan | Item Baru ... perintah untuk menambahkan item hiasan tampilan baru. Pilih ekstensibilitas | VSPackage di panel navigasi kiri dan pilih Perintah Kustom di panel kanan. Masukkan nama ColumnGuideCommands sebagai nama item dan pilih Tambahkan. Selain menambahkan beberapa referensi, menambahkan perintah dan paket juga menambahkan ColumnGuideCommands.cs, ColumnGuideCommandsPackage.cs, dan ColumnGuideCommandsPackage.vsct. Di bagian berikut, Anda mengganti konten file pertama dan terakhir untuk menentukan dan mengimplementasikan perintah.

Menyiapkan pemantau pembuatan tampilan teks

Buka ColumnGuideAdornmentTextViewCreationListener.cs pada editor. Kode ini mengimplementasikan handler setiap kali Visual Studio membuat tampilan teks. Ada atribut yang mengontrol kapan handler dipanggil tergantung pada karakteristik tampilan.

Kode juga harus mendeklarasikan lapisan hiasan. Ketika editor memperbarui tampilan, ia mendapatkan lapisan hiasan untuk tampilan tersebut dan dari situ mendapatkan elemen hiasan. Anda dapat mendeklarasikan pengurutan lapisan Anda relatif terhadap lapisan lainnya dengan atribut. Ganti baris berikut:

[Order(After = PredefinedAdornmentLayers.Caret)]

dengan dua baris ini:

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

Baris yang Anda ganti berada dalam sekelompok atribut yang mendeklarasikan lapisan hiasan. Baris pertama yang Anda ubah hanya mengubah lokasi kemunculan panduan kolom. Menggambar garis "sebelum" teks dalam tampilan berarti garis tersebut muncul di belakang atau di bawah teks. Baris kedua menyatakan bahwa hiasan panduan kolom berlaku untuk entitas teks yang sesuai dengan gagasan Anda tentang dokumen, tetapi Anda dapat mendeklarasikan hiasan, misalnya, untuk hanya berfungsi untuk teks yang dapat diedit. Ada informasi lebih lanjut di layanan Bahasa dan titik ekstensi editor

Menerapkan pengelola pengaturan

Ganti konten GuidesSettingsManager.cs dengan kode berikut (dijelaskan di bawah):

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 thread, 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;

    }
}

Sebagian besar kode ini membuat dan mengurai format pengaturan: "RGB(<int>,<int>,<int>) <int>, <int>, ...". Bilangan bulat di akhir adalah kolom berbasis satu tempat Anda menginginkan panduan kolom. Ekstensi panduan kolom menyimpan semua pengaturannya dalam satu string nilai pengaturan.

Ada beberapa bagian dari kode yang layak disorot. Baris kode berikut mendapatkan pembungkus yang dikelola oleh Visual Studio untuk penyimpanan pengaturan. Sebagian besar ini mengabstraksi registri Windows, tetapi API ini tidak bergantung pada mekanisme penyimpanan.

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

Penyimpanan pengaturan Visual Studio menggunakan pengidentifikasi kategori dan pengidentifikasi pengaturan untuk mengidentifikasi semua pengaturan secara unik:

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

Anda tidak perlu menggunakan "Text Editor" sebagai nama kategori. Anda dapat memilih apa pun yang Anda suka.

Beberapa fungsi pertama adalah titik masuk untuk mengubah pengaturan. Mereka memeriksa batasan tingkat tinggi seperti jumlah maksimum panduan yang diizinkan. Kemudian, mereka memanggil WriteSettings, yang menyusun string pengaturan dan mengatur properti GuideLinesConfiguration. Mengatur properti ini menyimpan nilai pengaturan ke penyimpanan pengaturan Visual Studio dan mengaktifkan peristiwa SettingsChanged untuk memperbarui semua objek ColumnGuideAdornment, masing-masing terkait dengan tampilan teks.

Ada beberapa fungsi titik masuk, seperti CanAddGuideline, yang digunakan untuk mengimplementasikan perintah yang mengubah pengaturan. Saat Visual Studio menampilkan menu, visual Studio meminta implementasi perintah untuk melihat apakah perintah saat ini diaktifkan, apa namanya, dan sebagainya. Di bawah ini Anda melihat cara menghubungkan titik masuk ini untuk implementasi perintah. Untuk informasi selengkapnya tentang perintah, lihat Perluas menu dan perintah.

Mengimplementasikan kelas ColumnGuideAdornment

Kelas ColumnGuideAdornment dibuat untuk setiap tampilan teks yang dapat memiliki hiasan. Kelas ini mendengarkan peristiwa tentang perubahan tampilan atau pengaturan, dan memperbarui atau menggambar ulang panduan kolom seperlunya.

Ganti konten ColumnGuideAdornment.cs dengan kode berikut (dijelaskan di bawah):

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

}

Instans kelas ini menyimpan IWpfTextView terkait dan daftar objek Line yang ditampilkan pada tampilan.

Konstruktor (dipanggil dari ColumnGuideAdornmentTextViewCreationListener ketika Visual Studio membuat tampilan baru) menciptakan objek panduan kolom Line. Konstruktor juga menambahkan handler untuk peristiwa SettingsChanged (yang ditentukan dalam GuidesSettingsManager) serta peristiwa tampilan LayoutChanged dan Closed.

Peristiwa LayoutChanged dijalankan karena beberapa jenis perubahan pada tampilan, termasuk saat Visual Studio membuat tampilan. Handler OnViewLayoutChanged memanggil AddGuidelinesToAdornmentLayer untuk menjalankan. Kode dalam OnViewLayoutChanged menentukan apakah posisi baris perlu diperbarui berdasarkan perubahan-perubahan seperti perubahan ukuran font, tampilan gutter, pengguliran secara horizontal, dan sebagainya. Kode di UpdatePositions menyebabkan garis panduan ditampilkan di antara karakter atau tepat setelah kolom teks pada offset karakter yang ditentukan di baris teks.

Setiap kali pengaturan berubah, fungsi SettingsChanged akan membuat ulang semua objek Line sesuai dengan pengaturan baru. Setelah mengatur posisi baris, kode menghapus semua objek Line sebelumnya dari lapisan hiasan ColumnGuideAdornment dan menambahkan yang baru.

Menentukan perintah, menu, dan penempatan menu

Deklarasi perintah dan menu, penempatan kelompok perintah atau menu pada berbagai menu lainnya, serta penghubungan penangan perintah bisa cukup rumit. Panduan ini menyoroti cara kerja perintah dalam ekstensi ini, tetapi untuk informasi yang lebih mendalam, lihat Perluas menu dan perintah.

Pengantar kode

Ekstensi Panduan Kolom memperlihatkan mendeklarasikan sekelompok perintah yang dimiliki bersama -sama (tambahkan kolom, hapus kolom, ubah warna garis), lalu tempatkan grup tersebut pada sub menu menu konteks editor. Ekstensi Panduan Kolom juga menambahkan perintah ke menu utama Edit tetapi membuatnya tidak terlihat, dibahas sebagai pola umum di bawah ini.

Ada tiga bagian untuk implementasi perintah: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct, dan ColumnGuideCommands.cs. Kode yang dihasilkan oleh templat menempatkan perintah pada menu Alat yang menampilkan kotak dialog sebagai implementasi. Anda dapat melihat bagaimana itu diimplementasikan dalam file .vsct dan ColumnGuideCommands.cs karena mudah. Anda mengganti kode dalam file-file ini di bawah ini.

Kode paket berisi deklarasi boilerplate yang diperlukan agar Visual Studio menemukan bahwa ekstensi menawarkan perintah dan untuk menemukan tempat menempatkan perintah. Saat paket diinisialisasi, ia menginstansiasi kelas implementasi perintah. Untuk informasi selengkapnya tentang paket yang berkaitan dengan perintah, lihat Perluas menu dan perintah.

Pola perintah umum

Perintah dalam ekstensi Panduan Kolom adalah contoh pola yang sangat umum di Visual Studio. Anda menempatkan perintah terkait dalam grup, dan Anda menempatkan grup tersebut pada menu utama, sering kali dengan "<CommandFlag>CommandWellOnly</CommandFlag>" diatur untuk membuat perintah tidak terlihat. Menempatkan perintah pada menu utama (seperti Edit) memberi mereka nama yang bagus (seperti Edit.AddColumnGuide), yang berguna untuk menemukan perintah saat menetapkan kembali pengikatan kunci di Opsi Alat. Ini juga berguna untuk mendapatkan penyelesaian saat memanggil perintah dari Command Window.

Anda kemudian menambahkan grup perintah ke menu konteks atau sub menu tempat Anda mengharapkan pengguna menggunakan perintah. Visual Studio memperlakukan CommandWellOnly sebagai bendera tak terlihat hanya untuk menu utama. Saat Anda menempatkan grup perintah yang sama pada menu konteks atau sub menu, perintah akan terlihat.

Sebagai bagian dari pola umum, ekstensi Panduan Kolom membuat grup kedua yang menyimpan satu sub menu. Sub menu pada gilirannya berisi grup pertama dengan perintah panduan yang terdiri dari empat kolom. Grup kedua yang menyimpan sub menu adalah aset yang dapat digunakan kembali yang Anda tempatkan di berbagai menu konteks, yang menempatkan sub menu pada menu konteks tersebut.

File .vsct

File .vsct mendeklarasikan perintah dan ke mana mereka pergi, bersama dengan ikon dan sebagainya. Ganti konten file .vsct dengan kode berikut (dijelaskan di bawah):

<?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 . Agar Visual Studio menemukan penangan perintah Anda dan memanggilnya, Anda perlu memastikan GUID paket yang dideklarasikan dalam file ColumnGuideCommandsPackage.cs (dihasilkan dari templat item proyek) cocok dengan GUID paket yang dideklarasikan dalam file .vsct (disalin dari atas). Jika Anda menggunakan kembali kode sampel ini, Anda harus memastikan Anda memiliki GUID yang berbeda sehingga Anda tidak berkonflik dengan orang lain yang mungkin telah menyalin kode ini.

Temukan baris ini di ColumnGuideCommandsPackage.cs dan salin GUID dari antara tanda kutip:

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

Kemudian, tempelkan GUID di file .vsct sehingga Anda memiliki baris berikut dalam deklarasi Symbols Anda:

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

GUID untuk set perintah dan file gambar bitmap juga harus unik untuk ekstensi Anda:

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

Tetapi, Anda tidak perlu mengubah set perintah dan GUID gambar bitmap dalam tutorial ini untuk membuat kode berfungsi. GUID set perintah harus cocok dengan deklarasi dalam file ColumnGuideCommands.cs. Namun, Anda juga mengganti konten file tersebut, sehingga GUID akan cocok.

GUID lain dalam file .vsct mengidentifikasi menu yang sudah ada sebelumnya tempat perintah panduan kolom ditambahkan, sehingga tidak pernah berubah.

Bagian file. .vsct memiliki tiga bagian luar: perintah, penempatan, dan simbol. Bagian perintah menentukan grup perintah, menu, tombol atau item menu, dan bitmap untuk ikon. Bagian penempatan menyatakan tempat grup masuk ke menu atau penempatan tambahan ke menu yang sudah ada. Bagian simbol mendeklarasikan pengidentifikasi yang digunakan di tempat lain dalam file .vsct, yang membuat kode .vsct lebih mudah dibaca daripada memiliki GUID dan angka heksa di mana-mana.

Bagian Perintah , definisi grup. Bagian perintah pertama-tama mendefinisikan grup perintah. Grup perintah adalah perintah yang Anda lihat di menu dengan sedikit garis abu-abu yang memisahkan grup. Grup juga dapat mengisi seluruh sub menu, seperti dalam contoh ini, dan Anda tidak melihat garis pemisah abu-abu dalam hal ini. File .vsct mendeklarasikan dua grup, GuidesMenuItemsGroup yang menjadi anak dari IDM_VS_MENU_EDIT (menu Edit utama) dan GuidesContextMenuGroup yang menjadi anak dari IDM_VS_CTXT_CODEWIN (menu konteks editor kode).

Deklarasi kelompok kedua memiliki prioritas 0x0600:

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

Idenya adalah menempatkan sub menu panduan kolom di akhir menu konteks apa pun yang Anda tambahkan grup sub menu. Tetapi, Anda tidak boleh berasumsi bahwa Anda tahu yang terbaik dan memaksa sub menu untuk selalu menjadi yang terakhir dengan menggunakan prioritas 0xFFFF. Anda harus bereksperimen dengan angka untuk melihat di mana sub menu Anda berada di menu konteks tempat Anda menempatkannya. Dalam hal ini, 0x0600 cukup tinggi untuk meletakkannya di akhir menu sejauh yang Anda lihat, tetapi meninggalkan ruang bagi orang lain untuk merancang ekstensi mereka agar lebih rendah dari ekstensi panduan kolom jika itu diinginkan.

Bagian Perintah, definisi menu. Selanjutnya, seksi perintah menetapkan sub menu GuidesSubMenu, dijadikan induk dari GuidesContextMenuGroup. GuidesContextMenuGroup adalah grup yang Anda tambahkan ke semua menu konteks yang relevan. Di bagian penempatan, kode menempatkan grup dengan perintah panduan empat kolom pada sub menu ini.

bagian Perintah, definisi tombol. Bagian perintah kemudian menentukan item menu atau tombol yang merupakan perintah panduan empat kolom. CommandWellOnly, yang dibahas di atas, berarti perintah tidak terlihat ketika ditempatkan pada menu utama. Dua deklarasi tombol item menu (tambahkan panduan dan hapus panduan) juga memiliki bendera AllowParams:

<CommandFlag>AllowParams</CommandFlag>

Bendera ini memungkinkan perintah untuk menerima argumen ketika Visual Studio memanggil handler perintah, selain itu juga memiliki penempatan di menu utama. Jika pengguna menjalankan perintah dari Jendela Perintah, argumen diteruskan ke handler perintah dalam argumen kejadian.

Bagian perintah, definisi bitmap. Terakhir, bagian perintah mendeklarasikan bitmap atau ikon yang digunakan untuk perintah. Bagian ini adalah deklarasi sederhana yang mengidentifikasi sumber daya proyek dan mencantumkan indeks berbasis satu ikon yang digunakan. Bagian simbol dari file .vsct mendeklarasikan nilai pengidentifikasi yang digunakan sebagai indeks. Panduan ini menggunakan strip bitmap yang disediakan dengan templat item perintah kustom yang ditambahkan ke proyek.

Bagian Penempatan. Setelah bagian perintah adalah bagian penempatan. Yang pertama adalah tempat kode menambahkan grup pertama yang dibahas di atas yang menyimpan perintah panduan empat kolom ke sub menu tempat perintah muncul:

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

Semua penempatan lainnya menambahkan GuidesContextMenuGroup (yang berisi GuidesSubMenu) ke menu konteks editor lainnya. Ketika kode mendeklarasikan GuidesContextMenuGroup, kode tersebut diintegrasikan ke menu konteks editor kode. Itulah sebabnya Anda tidak melihat penempatan menu konteks untuk editor kode.

Bagian Simbol. Seperti yang dinyatakan di atas, bagian simbol menyatakan pengidentifikasi yang digunakan di tempat lain dalam file .vsct, yang membuat kode .vsct lebih mudah dibaca daripada memiliki GUID dan angka heksa di mana-mana. Poin penting di bagian ini adalah bahwa GUID paket harus sesuai dengan deklarasi di kelas paket. GUID set perintah harus sesuai dengan deklarasi di kelas implementasi perintah.

Menerapkan perintah

File ColumnGuideCommands.cs mengimplementasikan perintah dan menghubungkan handler. Saat Visual Studio memuat paket dan menginisialisasinya, paket pada gilirannya memanggil Initialize pada kelas implementasi perintah. Inisialisasi perintah hanya menginstansiasi kelas, dan konstruktor mengaitkan semua pengendali perintah.

Ganti konten file ColumnGuideCommands.cs dengan kode berikut (dijelaskan di bawah):

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

    }
}

Perbaiki referensi. Anda kehilangan referensi pada saat ini. Tekan tombol penunjuk kanan pada simpul Referensi di Penjelajah Solusi. Pilih perintah Tambahkan .... Dialog Tambahkan Referensi memiliki kotak pencarian di sudut kanan atas. Masukkan "editor" (tanpa tanda kutip ganda). Pilih item Microsoft.VisualStudio.Editor (Anda harus mencentang kotak di sebelah kiri item, bukan hanya memilih item) dan memilih OK untuk menambahkan referensi.

Inisialisasi. Saat kelas paket diinisialisasi, kelas ini memanggil Initialize pada kelas implementasi perintah. Inisialisasi ColumnGuideCommands membuat instans kelas dan menyimpan instans kelas dan referensi paket di anggota kelas.

Mari kita lihat salah satu penghandel perintah hook-up dari konstruktor kelas:

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

Anda membuat sebuah OleMenuCommand. Visual Studio menggunakan sistem perintah Microsoft Office. Argumen kunci saat membuat instans OleMenuCommand adalah fungsi yang mengimplementasikan perintah (AddColumnGuideExecuted), fungsi yang akan dipanggil saat Visual Studio menampilkan menu dengan perintah (AddColumnGuideBeforeQueryStatus), dan ID perintah. Visual Studio memanggil fungsi status kueri sebelum menampilkan perintah pada menu sehingga perintah dapat membuatnya tidak terlihat atau berwarna abu-abu untuk tampilan menu tertentu (misalnya, menonaktifkan Salin jika tidak ada pilihan), mengubah ikonnya, atau bahkan mengubah namanya (misalnya, dari Tambahkan Sesuatu untuk Menghapus Sesuatu), dan sebagainya. ID perintah harus cocok dengan ID perintah yang dideklarasikan dalam file .vsct. String untuk set perintah dan perintah penambahan panduan kolom harus cocok antara file .vsct dan ColumnGuideCommands.cs.

Baris berikut memberikan bantuan saat pengguna memanggil perintah melalui Jendela Perintah (dijelaskan di bawah):

_addGuidelineCommand.ParametersDescription = "<column>";

Status kueri. Fungsi status kueri AddColumnGuideBeforeQueryStatus dan RemoveColumnGuideBeforeQueryStatus memeriksa beberapa pengaturan (seperti jumlah maksimum panduan atau kolom maksimum) atau jika ada panduan kolom yang harus dihapus. Mereka mengaktifkan perintah jika kondisinya benar. Fungsi status kueri harus efisien karena dijalankan setiap kali Visual Studio menampilkan menu dan untuk setiap perintah pada menu.

fungsi AddColumnGuideExecuted. Bagian yang menarik dari menambahkan panduan adalah mencari tahu tampilan editor saat ini dan lokasi tanda sisipan. Pertama, fungsi ini memanggil GetApplicableColumn, yang memeriksa apakah ada argumen yang disediakan pengguna dalam argumen acara pengelola perintah, dan jika tidak ada, fungsi akan memeriksa tampilan 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 harus menggali sedikit untuk mendapatkan pandangan IWpfTextView terhadap kode tersebut. Jika Anda menelusuri GetActiveTextView, GetActiveView, dan GetTextViewFromVsTextView, Anda dapat melihat cara melakukannya. Kode berikut adalah kode yang relevan yang diabstraksi, dimulai dengan pilihan saat ini, lalu mendapatkan bingkai pilihan, lalu mendapatkan DocView bingkai sebagai IVsTextView, lalu mendapatkan IVsUserData dari IVsTextView, lalu mendapatkan host tampilan, dan akhirnya 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;

Setelah Anda memiliki IWpfTextView, Anda bisa mendapatkan kolom tempat tanda sisipan berada:

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

Dengan kolom saat ini yang dipilih yang diklik pengguna, kode hanya memanggil pengelola pengaturan untuk menambahkan atau menghapus kolom. Manajer pengaturan mengaktifkan peristiwa yang didengarkan semua objek ColumnGuideAdornment. Saat acara diaktifkan, objek ini memperbarui tampilan teks terkait dengan pengaturan panduan kolom baru.

Jalankan perintah dari Jendela Perintah

Sampel panduan kolom memungkinkan pengguna memanggil dua perintah dari Jendela Perintah sebagai bentuk ekstensibilitas. Jika Anda menggunakan perintah Lihat | Windows Lainnya | Jendela Perintah, Anda dapat melihat Jendela Perintah. Anda dapat berinteraksi dengan Jendela Perintah dengan memasukkan "edit.", dan dengan penyelesaian nama perintah dan menyediakan argumen 120, Anda memiliki hasil berikut:

> Edit.AddColumnGuide 120
>

Potongan sampel yang mengaktifkan perilaku ini ada di deklarasi file .vsct, konstruktor kelas ColumnGuideCommands saat menghubungkan handler perintah, dan implementasi handler perintah yang memeriksa argumen peristiwa.

Anda melihat "<CommandFlag>CommandWellOnly</CommandFlag>" dalam file .vsct, serta penempatannya di menu utama Edit , meskipun perintah tersebut tidak ditampilkan dalam antarmuka pengguna menu Edit . Memilikinya di menu utama Edit memberi mereka nama seperti Edit.AddColumnGuide. Deklarasi grup perintah yang menyimpan empat perintah menempatkan grup pada menu Edit secara langsung:

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

Bagian tombol kemudian mendeklarasikan perintah CommandWellOnly agar tidak terlihat di menu utama dan mendeklarasikannya dengan 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>

Kamu dapat melihat kode penyambungan handler perintah di dalam konstruktor kelas ColumnGuideCommands yang memberikan deskripsi tentang parameter yang diizinkan:

_addGuidelineCommand.ParametersDescription = "<column>";

Anda melihat fungsi GetApplicableColumn memeriksa nilai di OleMenuCmdEventArgs sebelum memeriksa tampilan editor untuk kolom yang sedang aktif.

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

Coba ekstensi Anda

Sekarang Anda dapat menekan F5 untuk menjalankan ekstensi Panduan Kolom Anda. Buka file teks dan gunakan menu konteks editor untuk menambahkan baris panduan, menghapusnya, dan mengubah warnanya. Klik teks (bukan spasi kosong yang melewati akhir baris) untuk menambahkan panduan kolom, atau editor menambahkannya ke kolom terakhir pada baris. Jika Anda menggunakan Jendela Perintah dan memanggil perintah dengan argumen, Anda dapat menambahkan panduan kolom di mana saja.

Jika Anda ingin mencoba penempatan perintah yang berbeda, mengubah nama, mengubah ikon, dan sebagainya, dan Anda mengalami masalah dengan Visual Studio yang menunjukkan kode terbaru di menu, Anda dapat mengatur ulang instance eksperimental tempat Anda melakukan debugging. Buka Menu Mulai Windows dan ketik "reset". Cari dan jalankan perintah , Reset Instans Eksperimental Visual Studio Berikutnya. Perintah ini membersihkan sarang registri eksperimental dari semua komponen ekstensi. Ini tidak menghapus pengaturan terkait komponen, jadi panduan apa pun yang Anda miliki saat Anda menutup versi eksperimental Visual Studio akan tetap ada saat kode Anda membaca penyimpanan pengaturan pada peluncuran berikutnya.

Proyek kode selesai

Akan segera ada proyek GitHub dari sampel Visual Studio Extensibility, dan proyek yang telah selesai akan ada di sana. Artikel ini akan diperbarui untuk mengarah ke sana ketika itu terjadi. Proyek sampel yang selesai mungkin memiliki guid yang berbeda dan akan memiliki strip bitmap yang berbeda untuk ikon perintah.

Anda dapat mencoba versi fitur panduan kolom dengan ekstensi di Visual Studio Galleryini.