İzlenecek yol: Görünüm donatısı, komutlar ve ayarlar oluşturma (sütun kılavuzları)

Visual Studio metin/kod düzenleyicisini komutlarla genişletebilir ve efektleri görüntüleyebilirsiniz. Bu makalede popüler bir uzantı özelliği olan sütun kılavuzlarını kullanmaya nasıl başladığınız gösterilmektedir. Sütun kılavuzları, kodunuzu belirli sütun genişliklerine göre yönetmenize yardımcı olmak için metin düzenleyicisinin görünümünde çizilen görsel olarak açık çizgilerdir. Özellikle, biçimlendirilmiş kod belgelere, blog gönderilerine veya hata raporlarına eklediğiniz örnekler için önemli olabilir.

Bu kılavuzda şunları yapmak için:

  • VSIX projesi oluşturma

  • Düzenleyici görünümü süslemesi ekleme

  • Kaydetme ve ayarları alma desteği ekleme (sütun kılavuzlarının ve renklerinin çizileceği yer)

  • Komut ekleme (sütun kılavuzlarını ekleme/kaldırma, renklerini değiştirme)

  • Komutları Düzenle menüsüne ve metin belgesi bağlam menülerine yerleştirin

  • Visual Studio Komut Penceresi'nden komutları çağırma desteği ekleme

    Bu Visual Studio Galerisi uzantısıyla sütun kılavuzları özelliğinin bir sürümünü deneyebilirsiniz.

    Not

    Bu kılavuzda, Visual Studio uzantı şablonları tarafından oluşturulan birkaç dosyaya büyük miktarda kod yapıştıracaksınız. Ancak bu kılavuz yakında GitHub'da diğer uzantı örneklerini içeren tamamlanmış bir çözüme başvuracaktır. Tamamlanan kod, genel platform simgeleri yerine gerçek komut simgelerine sahip olduğundan biraz farklıdır.

Çözümü ayarlama

İlk olarak, bir VSIX projesi oluşturur, düzenleyici görünümü süslemesi ekler ve ardından bir komut eklersiniz (komutun sahibi olmak için VSPackage ekler). Temel mimari aşağıdaki gibidir:

  • Görünüm başına nesne oluşturan bir ColumnGuideAdornment metin görünümü oluşturma dinleyiciniz var. Bu nesne, görünümün değiştirilmesi veya ayarların değiştirilmesi, güncelleştirilmesi veya sütun kılavuzlarının gerektiği şekilde yeniden çizilmesiyle ilgili olayları dinler.

  • Visual Studio ayarları depolama alanından okuma ve yazma işlemlerini işleyen bir GuidesSettingsManager vardır. Ayarlar yöneticisi ayrıca kullanıcı komutlarını destekleyen ayarları güncelleştirme işlemlerine de sahiptir (sütun ekle, sütunu kaldır, rengi değiştir).

  • Kullanıcı komutlarınız varsa gerekli olan bir VSIP paketi vardır, ancak komut uygulama nesnesini başlatan ortak koddan ibarettir.

  • Kullanıcı komutlarını çalıştıran ve .vsct dosyasında bildirilen komutlar için komut işleyicilerini çalıştıran bir ColumnGuideCommands nesne vardır.

    VSIX. Dosya Kullan | Yeni... komutuyla proje oluşturun. Sol gezinti bölmesindeki C# altında Genişletilebilirlik düğümünü seçin ve sağ bölmede VSIX Projesi'ni seçin. Projeyi oluşturmak için ColumnGuides adını girin ve Tamam'ı seçin.

    Görünüm süslemesi. Çözüm Gezgini proje düğümünde sağ işaretçi düğmesine basın. Ekle | öğesini seçin Yeni Öğe ... komutuyla yeni bir görünüm süsleme öğesi ekleyin. Genişletilebilirlik Seç | Sol gezinti bölmesinde düzenleyici ve sağ bölmede Düzenleyici Görünüm Penceresi Donatısı'nı seçin. Öğe adı olarak ColumnGuideAdornment adını girin ve eklemek için Ekle'yi seçin.

    Bu öğe şablonunun projeye iki dosya (başvuruların yanı sıra vb.) eklendiğini görebilirsiniz: ColumnGuideAdornment.cs ve ColumnGuideAdornmentTextViewCreationListener.cs. Şablonlar görünümde mor bir dikdörtgen çizer. Aşağıdaki bölümde, görünüm oluşturma dinleyicisindeki birkaç satırı değiştirir ve ColumnGuideAdornment.cs içeriğini değiştirirsiniz.

    Komutlar. Çözüm Gezgini'da proje düğümünde sağ işaretçi düğmesine basın. Ekle | öğesini seçin Yeni Öğe ... komutuyla yeni bir görünüm süsleme öğesi ekleyin. Genişletilebilirlik Seç | Sol gezinti bölmesinde VSPackage ve sağ bölmede Özel Komut'u seçin. Öğe adı olarak ColumnGuideCommands adını girin ve Ekle'yi seçin. Çeşitli başvurulara ek olarak, komutları ve paketi ekleyerek ColumnGuideCommands.cs, ColumnGuideCommandsPackage.cs ve ColumnGuideCommandsPackage.vsct de eklendi. Aşağıdaki bölümde, komutları tanımlamak ve uygulamak için ilk ve son dosyaların içeriğini değiştirirsiniz.

Metin görünümü oluşturma dinleyicisini ayarlama

düzenleyicide ColumnGuideAdornmentTextViewCreationListener.cs açın. Bu kod, Visual Studio metin görünümleri oluşturduğunda için bir işleyici uygular. Görünümün özelliklerine bağlı olarak işleyicinin ne zaman çağrıldığını denetleyebilen öznitelikler vardır.

Kodun ayrıca bir donatma katmanı bildirmesi gerekir. Düzenleyici görünümleri güncelleştirdiğinde, görünümün süsleme katmanlarını alır ve bu katmandan da donatma öğelerini alır. Öznitelikleri olan diğer kullanıcılara göre katmanınızın sıralamasını bildirebilirsiniz. Aşağıdaki satırı değiştirin:

[Order(After = PredefinedAdornmentLayers.Caret)]

şu iki satırla:

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

Değiştirdiğiniz satır, bir donatma katmanı bildiren bir öznitelik grubunda yer alır. Değiştirdiğiniz ilk satır yalnızca sütun kılavuzu çizgilerinin göründüğü yerde değişir. Görünümdeki metni "önce" çizerken metnin arkasında veya altında görünürler. İkinci satır, sütun kılavuzu kenarlıklarının bir belgeyle ilgili bildiriminize uyan metin varlıkları için geçerli olduğunu bildirir, ancak örneğin, yalnızca düzenlenebilir metinler için çalışacak şekilde donatma bildirebilirsiniz. Dil hizmeti ve düzenleyici uzantı noktalarında daha fazla bilgi vardır

Ayarlar yöneticisini uygulama

GuidesSettingsManager.cs içeriğini aşağıdaki kodla değiştirin (aşağıda açıklanmıştır):

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;

    }
}

Bu kodun çoğu ayarlar biçimini oluşturur ve ayrıştırıyor: "RGB(<int,int,int><><>) <int>, <int>, ...". Sonundaki tamsayılar, sütun kılavuzlarının olmasını istediğiniz tek tabanlı sütunlardır. Sütun kılavuzları uzantısı tüm ayarlarını tek bir ayar değer dizesinde yakalar.

Kodun vurgulanması gereken bazı bölümleri vardır. Aşağıdaki kod satırı, ayarlar depolaması için Visual Studio yönetilen sarmalayıcısını alır. Çoğunlukla bu, Windows kayıt defteri üzerinde özetler, ancak bu API depolama mekanizmasından bağımsızdır.

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

Visual Studio ayarları depolama alanı, tüm ayarları benzersiz olarak tanımlamak için bir kategori tanımlayıcısı ve ayar tanımlayıcısı kullanır:

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

Kategori adı olarak kullanmanız "Text Editor" gerekmez. İstediğiniz her şeyi seçebilirsiniz.

İlk birkaç işlev, ayarları değiştirmek için giriş noktalarıdır. İzin verilen en fazla kılavuz sayısı gibi üst düzey kısıtlamaları denetler. Ardından, bir ayar dizesi oluşturan ve özelliğini GuideLinesConfigurationayarlayan öğesini çağırırlarWriteSettings. Bu özelliğin ayarlanması, ayarlar değerini Visual Studio ayarlar deposuna kaydeder ve olayı tetikler SettingsChanged ve her birinin bir metin görünümüyle ilişkili tüm ColumnGuideAdornment nesneleri güncelleştirir.

Ayarları değiştiren komutları uygulamak için kullanılan gibi CanAddGuidelinebirkaç giriş noktası işlevi vardır. Visual Studio menüleri gösterdiğinde komutun şu anda etkin olup olmadığını, adının ne olduğunu vb. görmek için komut uygulamalarını sorgular. Aşağıda, komut uygulamaları için bu giriş noktalarını nasıl bağlayabileceğinizi göreceksiniz. Komutlar hakkında daha fazla bilgi için bkz . Menüleri ve komutları genişletme.

ColumnGuideAdornment sınıfını uygulama

Sınıf ColumnGuideAdornment , kenarlıklara sahip olabilecek her metin görünümü için örneği oluşturulur. Bu sınıf görünümün değiştirilmesi veya ayarların değiştirilmesi ve sütun kılavuzlarının gerektiği şekilde güncelleştirilmesi veya yeniden çizilmesiyle ilgili olayları dinler.

ColumnGuideAdornment.cs içeriğini aşağıdaki kodla değiştirin (aşağıda açıklanmıştır):

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

}

Bu sınıfın örnekleri, görünümde çizilen ilişkili IWpfTextView ve nesnelerin listesini Line tutar.

Oluşturucu (Visual Studio yeni görünümler oluşturduğunda çağrılır ColumnGuideAdornmentTextViewCreationListener ) sütun kılavuzu Line nesnelerini oluşturur. Oluşturucu ayrıca olay (içinde GuidesSettingsManagertanımlanan) ve görünüm olayları LayoutChanged ile Closediçin SettingsChanged işleyiciler ekler.

Visual Studio görünümü LayoutChanged oluşturduğunda da dahil olmak üzere görünümdeki çeşitli değişikliklerden dolayı olay tetikler. İşleyici OnViewLayoutChanged yürütülmek için çağırır AddGuidelinesToAdornmentLayer . içindeki OnViewLayoutChanged kod, yazı tipi boyutu değişiklikleri, görünüm cilt payı, yatay kaydırma vb. gibi değişikliklere göre satır konumlarını güncelleştirmenin gerekip gerekmediğini belirler. içindeki UpdatePositions kod, kılavuz çizgilerinin karakterler arasında veya metin satırında belirtilen karakter uzaklığında yer alan metin sütunundan hemen sonra çizmesine neden olur.

Ayarlar işlevi değiştirildiğinde SettingsChanged , yeni ayarlar ne olursa olsun tüm Line nesneleri yeniden oluşturur. Satır konumlarını ayarladıktan sonra kod, önceki Line tüm nesneleri donatma katmanından ColumnGuideAdornment kaldırır ve yenilerini ekler.

Komutları, menüleri ve menü yerleşimlerini tanımlama

Komutları ve menüleri bildirmek, çeşitli diğer menülere komut veya menü grupları yerleştirmek ve komut işleyicileri bağlamak için çok fazla şey olabilir. Bu kılavuzda komutların bu uzantıda nasıl çalıştığı vurgulanır, ancak daha ayrıntılı bilgi için bkz . Menüleri ve komutları genişletme.

Koda giriş

Sütun Kılavuzları uzantısı, birbirine ait bir komut grubu bildirmeyi (sütun ekleme, sütunu kaldırma, çizgi rengini değiştirme) ve ardından bu grubu düzenleyicinin bağlam menüsünün alt menüsüne yerleştirmeyi gösterir. Sütun Kılavuzları uzantısı, komutları ana Düzenleme menüsüne de ekler, ancak aşağıda yaygın bir desen olarak ele alınıp görünmez kalmasını sağlar.

Komut uygulamasının üç bölümü vardır: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct ve ColumnGuideCommands.cs. Şablonlar tarafından oluşturulan kod, Araçlar menüsüne uygulama olarak bir iletişim kutusu gösteren bir komut yerleştirir. Basit olduğu için bunun .vsct ve ColumnGuideCommands.cs dosyalarında nasıl uygulandığına bakabilirsiniz. Aşağıdaki dosyalardaki kodu değiştirirsiniz.

Paket kodu, uzantının komutlar sunduğunu keşfetmek ve komutların nereye yerleştirildiğini bulmak için Visual Studio için gerekli ortak bildirimleri içerir. Paket başlatıldığında komut uygulama sınıfını başlatır. Komutlarla ilgili paketler hakkında daha fazla bilgi için bkz . Menüleri ve komutları genişletme.

Yaygın komut deseni

Sütun Kılavuzları uzantısındaki komutlar, Visual Studio'da çok yaygın bir desen örneğidir. İlgili komutları bir gruba koyarsınız ve bu grubu bir ana menüye koyarsınız; genellikle "<CommandFlag>CommandWellOnly</CommandFlag>" komutu görünmez hale getirmek için ayarlanmıştır. Komutları ana menülere (Düzenle gibi) yerleştirmek, araçlar seçeneklerinde anahtar bağlamalarını yeniden atarken komutları bulmak için yararlı olan güzel adlar (Edit.AddColumnGuide gibi) verir. Komut Penceresinden komutları çağırırken tamamlama almak için de kullanışlıdır.

Ardından komut grubunu, kullanıcının komutları kullanmasını beklediğiniz bağlam menülerine veya alt menülere eklersiniz. Visual Studio yalnızca ana menüler CommandWellOnly için görünmezlik bayrağı olarak davranır. Aynı komut grubunu bir bağlam menüsüne veya alt menüye yerleştirdiğinizde, komutlar görünür.

Ortak desenin bir parçası olarak, Sütun Kılavuzları uzantısı tek bir alt menü tutan ikinci bir grup oluşturur. Sırayla alt menü, dört sütunlu kılavuz komutlarını içeren ilk grubu içerir. Alt menüyü tutan ikinci grup, çeşitli bağlam menülerine yerleştirdiğiniz ve bu bağlam menülerine bir alt menü koyan yeniden kullanılabilir varlıktır.

.vsct dosyası

.vsct dosyası komutları ve bunların nereye gittiğini simgelerle birlikte bildirir. .vsct dosyasının içeriğini aşağıdaki kodla değiştirin (aşağıda açıklanmıştır):

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

GUID'LER. Visual Studio'nın komut işleyicilerinizi bulması ve çağırması için, ColumnGuideCommandsPackage.cs dosyasında bildirilen paket GUID'sinin (proje öğesi şablonundan oluşturulan) .vsct dosyasında bildirilen paket GUID'si ile (yukarıdan kopyalanır) eşleştiğinden emin olmanız gerekir. Bu örnek kodu yeniden kullanırsanız, bu kodu kopyalamış olabilecek başka biriyle çakışmaması için farklı bir GUID'niz olduğundan emin olmanız gerekir.

bu satırı ColumnGuideCommandsPackage.cs bulun ve guid değerini tırnak işaretleri arasında kopyalayın:

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

Ardından, bildirimlerinizde Symbols aşağıdaki satırın olması için GUID'yi .vsct dosyasına yapıştırın:

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

Komut kümesinin GUID'leri ve bit eşlem görüntü dosyası da uzantılarınız için benzersiz olmalıdır:

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

Ancak, kodun çalışmasını sağlamak için bu kılavuzdaki komut kümesini ve bit eşlem görüntüsü GUID'lerini değiştirmeniz gerekmez. Komut kümesi GUID'sinin ColumnGuideCommands.cs dosyasındaki bildirimle eşleşmesi gerekir, ancak bu dosyanın içeriğini de değiştirirsiniz; bu nedenle GUID'ler eşleşecektir.

.vsct dosyasındaki diğer GUID'ler, sütun kılavuzu komutlarının eklendiği önceden var olan menüleri tanımlar ve bu nedenle hiçbir zaman değişmez.

Dosya bölümleri. .vsct öğesinin üç dış bölümü vardır: komutlar, yerleştirmeler ve simgeler. Komutlar bölümü komut gruplarını, menüleri, düğmeleri veya menü öğelerini ve simgeler için bit eşlemleri tanımlar. Yerleşimler bölümü, grupların menülere veya ek yerleşimlere önceden var olan menülere nereye gittiğini bildirir. Simgeler bölümü, .vsct dosyasının başka bir yerinde kullanılan tanımlayıcıları bildirir ve bu da .vsct kodunun her yerde GUID'lere ve onaltılık sayılara sahip olmaktan daha okunabilir olmasını sağlar.

Komutlar bölümü, gruplar tanımları. Komutlar bölümü önce komut gruplarını tanımlar. Komut grupları, menülerde grupları ayıran hafif gri çizgili komutlardır. Bir grup, bu örnekte olduğu gibi bir alt menünün tamamını da doldurabilir ve bu örnekte gri ayrıştırma çizgilerini görmezsiniz. .vsct dosyaları, GuidesMenuItemsGroup üst öğesi IDM_VS_MENU_EDIT (ana Düzenleme menüsü) ve GuidesContextMenuGroup üst IDM_VS_CTXT_CODEWIN öğesi (kod düzenleyicisinin bağlam menüsü) olan iki grup bildirir.

İkinci grup bildiriminin önceliği vardır 0x0600 :

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

Amaç, alt menü grubunu eklediğiniz bağlam menüsünün sonuna sütun kılavuzları alt menüsünü yerleştirmektir. Ancak, en iyi bildiğinizi varsaymamalısınız ve alt menüyü önceliğini 0xFFFFkullanarak her zaman son olmaya zorlamamalısınız. Alt menünüzün yerleştirdiğiniz bağlam menülerinde nerede olduğunu görmek için sayıyla denemeler yapsanız iyi olur. Bu durumda, 0x0600 menülerin sonuna gördüğünüz kadar koyacak kadar yüksektir, ancak isterseniz başka birinin uzantıyı sütun kılavuzları uzantısından daha düşük olacak şekilde tasarlaması için yer bırakır.

Komutlar bölümü, menü tanımı. Ardından komut bölümü, öğesinin üst öğesinde yer alan alt menüyü GuidesSubMenuGuidesContextMenuGrouptanımlar. GuidesContextMenuGroup, tüm ilgili bağlam menülerine eklediğiniz grupdur. Yerleştirmeler bölümünde kod, grubu dört sütunlu kılavuz komutlarını bu alt menüye yerleştirir.

Komutlar bölümü, düğme tanımları. Komutlar bölümü daha sonra dört sütunlu kılavuz komutları olan menü öğelerini veya düğmeleri tanımlar. CommandWellOnly, komutların bir ana menüye yerleştirildiğinde görünmez olduğu anlamına gelir. Menü öğesi düğme bildirimlerinden ikisinin (kılavuz ekleme ve kaldırma) bir AllowParams bayrağı da vardır:

<CommandFlag>AllowParams</CommandFlag>

Bu bayrak, Visual Studio komut işleyicisini çağırdığında bağımsız değişkenleri almak için komutun ana menü yerleşimlerine sahip olmasını sağlar. Kullanıcı komutu Komut Penceresi'nden çalıştırırsa, bağımsız değişken olay bağımsız değişkenlerinde komut işleyicisine geçirilir.

Komut bölümleri, bit eşlem tanımları. Son olarak, komutlar bölümü komutlar için kullanılan bit eşlemleri veya simgeleri bildirir. Bu bölüm, proje kaynağını tanımlayan ve kullanılan simgelerin tek tabanlı dizinlerini listeleyen basit bir bildirimdir. .vsct dosyasının simgeler bölümü, dizin olarak kullanılan tanımlayıcıların değerlerini bildirir. Bu izlenecek yol, projeye eklenen özel komut öğesi şablonuyla birlikte sağlanan bit eşlem şeridini kullanır.

Yerleştirmeler bölümü. Komutlar bölümünden sonra yerleştirmeler bölümü bulunur. İlki, kodun yukarıda açıklanan ve dört sütunlu kılavuz komutlarını barındıran ilk grubu komutların göründüğü alt menüye eklediği yerdir:

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

Diğer tüm yerleşimler, diğer düzenleyici bağlam menülerine öğesini (öğesini içerirGuidesSubMenu) ekler GuidesContextMenuGroup . Kod bildirdiğinde GuidesContextMenuGroup, kod düzenleyicisinin bağlam menüsüne üst öğe olarak eklendi. Bu nedenle kod düzenleyicisinin bağlam menüsü için bir yerleşim görmezsiniz.

Simgeler bölümü. Yukarıda belirtildiği gibi, simgeler bölümü .vsct dosyasının başka bir yerinde kullanılan tanımlayıcıları bildirir ve bu da .vsct kodunu her yerde GUID'lere ve onaltılık sayılara sahip olmaktan daha okunabilir hale getirir. Bu bölümdeki önemli noktalar, paket GUID'sinin paket sınıfındaki bildirimi kabul etmesi gerektiğidir. Ayrıca, komut kümesi GUID'i komut uygulama sınıfındaki bildirimi kabul etmelidir.

Komutları uygulama

ColumnGuideCommands.cs dosyası komutları uygular ve işleyicileri bağlar. Visual Studio paketi yükleyip başlattığında, paket komut uygulama sınıfını çağırır Initialize . Komutları başlatma yalnızca sınıfını başlatır ve oluşturucu tüm komut işleyicilerini bağlar.

ColumnGuideCommands.cs dosyasının içeriğini aşağıdaki kodla değiştirin (aşağıda açıklanmıştır):

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

    }
}

Başvuruları düzeltin. Bu noktada bir başvuru eksik. Çözüm Gezgini Başvurular düğümünde sağ işaretçi düğmesine basın. Ekle ... komutunu seçin. Başvuru Ekle iletişim kutusunun sağ üst köşesinde bir arama kutusu vardır. "düzenleyici" (çift tırnak işareti olmadan) girin. Microsoft.VisualStudio.Editor öğesini seçin (yalnızca öğeyi seçmek yerine öğenin solundaki kutuyu işaretlemeniz gerekir) ve başvuruyu eklemek için Tamam'ı seçin.

Başlatma. Paket sınıfı başlatıldığında, komut uygulama sınıfını çağırır Initialize . Başlatma ColumnGuideCommands , sınıfın örneğini oluşturur ve sınıf örneğini ve paket başvuruyu sınıf üyelerine kaydeder.

Şimdi sınıf oluşturucusundan komut işleyicisi kancalarından birine bakalım:

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

Bir OleMenuCommandoluşturursunuz. Visual Studio, Microsoft Office komut sistemini kullanır. örneğini oluştururken OleMenuCommand temel bağımsız değişkenler komutu ()AddColumnGuideExecuted uygulayan işlev, Visual Studio komutu () ileAddColumnGuideBeforeQueryStatus bir menü gösterdiğinde çağrılacak işlev ve komut kimliğidir. Visual Studio, menüde bir komut göstermeden önce sorgu durumu işlevini çağırır; böylece komut, menünün belirli bir görüntüsü için kendisini görünmez veya gri yapabilir (örneğin, seçim yoksa Kopyala'yı devre dışı bırakma), simgesini değiştirebilir ve hatta adını değiştirebilir (örneğin, Bir Şeyi Kaldırmak için Bir Şey Ekle'den), ve benzeri. Komut kimliği, .vsct dosyasında bildirilen bir komut kimliğiyle eşleşmelidir. Komut kümesinin dizeleri ve sütun kılavuzları ekleme komutu .vsct dosyası ile ColumnGuideCommands.cs arasında eşleşmelidir.

Aşağıdaki satır, kullanıcıların Komut Penceresi aracılığıyla komutu çağırdığında yardım sağlar (aşağıda açıklanmıştır):

_addGuidelineCommand.ParametersDescription = "<column>";

Sorgu durumu. Sorgu durumu işlevi görür AddColumnGuideBeforeQueryStatus ve RemoveColumnGuideBeforeQueryStatus bazı ayarları (en fazla kılavuz sayısı veya maksimum sütun gibi) veya kaldırılacak bir sütun kılavuzu olup olmadığını denetler. Koşullar doğruysa komutları etkinleştirir. Visual Studio menüyü her gösterdiğinde ve menüdeki her komut için çalıştırıldığından sorgu durumu işlevlerinin verimli olması gerekir.

AddColumnGuideExecuted işlevi. Kılavuz eklemenin ilginç kısmı, geçerli düzenleyici görünümünü ve şapka işaretinin konumunu bulmaktır. İlk olarak, bu işlev komut işleyicisinin olay bağımsız değişkenlerinde kullanıcı tarafından sağlanan bir bağımsız değişken olup olmadığını denetleyen öğesini çağırır GetApplicableColumnve yoksa işlev düzenleyicinin görünümünü denetler:

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 kodun bir görünümünü elde IWpfTextView etmek için biraz araştırmak zorunda. , GetActiveViewve GetTextViewFromVsTextViewüzerinden GetActiveTextViewizlerseniz, bunu nasıl yapacağınızı görebilirsiniz. Aşağıdaki kod, geçerli seçimden başlayarak, sonra seçimin çerçevesini alarak, ardından çerçevenin DocView'unu bir IVsTextViewolarak alıp IVsTextView'dan bir görünüm IVsUserData konağından ve son olarak da IWpfTextView'dan alarak özetlenen ilgili koddur:

   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;

IWpfTextView'a sahip olduktan sonra, şapka işaretinin bulunduğu sütunu alabilirsiniz:

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

Kullanıcının tıkladığı geçerli sütun elinizin altında olduğunda, kod yalnızca sütunu eklemek veya kaldırmak için ayarlar yöneticisini çağırır. Ayarlar yöneticisi, tüm ColumnGuideAdornment nesnelerin dinlediği olayı başlatır. Olay tetiklendiğinde, bu nesneler ilişkili metin görünümlerini yeni sütun kılavuzu ayarlarıyla güncelleştirir.

Komut Penceresinden Komut Çağır

Sütun kılavuzları örneği, kullanıcıların Komut Penceresi'nden iki komutu genişletilebilirlik biçimi olarak çağırmasına olanak tanır. Görünüm | kullanıyorsanız Diğer Windows | Komut Penceresi komutu, Komut Penceresi'ni görebilirsiniz. Komut Penceresi ile "edit. " girerek etkileşim kurabilirsiniz ve komut adı tamamlandıktan sonra 120 bağımsız değişkenini sağlayarak aşağıdaki sonuca sahip olursunuz:

> Edit.AddColumnGuide 120
>

Bu davranışı etkinleştiren örneğin parçaları .vsct dosya bildirimlerinde, ColumnGuideCommands komut işleyicilerini bağladığında sınıf oluşturucusunda ve olay bağımsız değişkenlerini denetleen komut işleyici uygulamalarında bulunur.

Komutlar Düzenle menüsü kullanıcı arabiriminde gösterilmese bile .vsct dosyasında ve Ana menüyü düzenle menüsündeki yerleşimlerde "<CommandFlag>CommandWellOnly</CommandFlag>" ifadesini gördünüz. Bunları ana Düzenle menüsünde bulundurmak, onlara Edit.AddColumnGuide gibi adlar verir. Grubu doğrudan Düzenle menüsüne yerleştiren dört komutu tutan komut grubu bildirimi:

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

Düğmeler bölümü daha sonra ana menüde görünmelerini sağlamak için komutları CommandWellOnly bildirdi ve ile bildirdi 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>

Komut işleyicisinin sınıf oluşturucusunda kod bağlamasının ColumnGuideCommands izin verilen parametrenin açıklamasını sağladığını gördünüz:

_addGuidelineCommand.ParametersDescription = "<column>";

Düzenleyicinin GetApplicableColumn geçerli sütun görünümünü denetlemeden önce işlevin OleMenuCmdEventArgs bir değeri denetlediğini gördünüz:

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

Uzantınızı deneyin

Artık Sütun Kılavuzları uzantınızı yürütmek için F5 tuşuna basabilirsiniz. Bir metin dosyası açın ve düzenleyicinin bağlam menüsünü kullanarak kılavuz çizgileri ekleyin, bunları kaldırın ve renklerini değiştirin. Sütun kılavuzu eklemek için metne tıklayın (satırın sonuna boşluk geçmemiş) veya düzenleyici bunu satırdaki son sütuna ekler. Komut Penceresi'ni kullanır ve komutları bağımsız değişkenle çağırırsanız, her yere sütun kılavuzları ekleyebilirsiniz.

Farklı komut yerleşimlerini denemek, adları değiştirmek, simgeleri değiştirmek vb. istiyorsanız ve menülerde size en son kodu gösteren Visual Studio ile ilgili herhangi bir sorun yaşıyorsanız, hata ayıkladığınız deneysel kovanı sıfırlayabilirsiniz. Windows Başlat Menüsü'nü açın ve "reset" yazın. Sonraki Visual Studio Deneysel Örneğini Sıfırla komutunu arayın ve çalıştırın. Bu komut, tüm uzantı bileşenlerinin deneysel kayıt defteri kovanını temizler. Bileşenlerdeki ayarları temizlemez, bu nedenle Visual Studio'nun deneysel kovanını kapattığınızda sahip olduğunuz tüm kılavuzlar, kodunuz bir sonraki başlatmada ayarlar depoyu okuduğunda hala orada olur.

Tamamlanmış kod projesi

Yakında Visual Studio Genişletilebilirlik örneklerini içeren bir GitHub projesi olacak ve tamamlanan proje orada olacak. Bu makale, bu olduğunda bunu işaret edecek şekilde güncelleştirilecektir. Tamamlanan örnek proje farklı guid'lere sahip olabilir ve komut simgeleri için farklı bir bit eşlem şeridine sahip olacaktır.

Bu Visual Studio Galerisi uzantısıyla sütun kılavuzları özelliğinin bir sürümünü deneyebilirsiniz.