Пошаговое руководство. Использование команды оболочки с расширением редактора
В VSPackage можно добавить такие функции, как команды меню в редактор. В этом пошаговом руководстве показано, как добавить украшение в текстовое представление в редакторе, вызвав команду меню.
В этом пошаговом руководстве показано использование VSPackage вместе с частью компонента Managed Extensibility Framework (MEF). Чтобы зарегистрировать команду меню в оболочке Visual Studio, необходимо использовать VSPackage. И вы можете использовать команду для доступа к части компонента MEF.
Создание расширения с помощью команды меню
Создайте VSPackage, который помещает команду меню с именем "Добавить украшение " в меню "Сервис ".
Создайте проект VSIX C# с именем
MenuCommandTest
и добавьте имя шаблона пользовательского элемента команды AddAdornment. Дополнительные сведения см. в разделе "Создание расширения" с помощью команды меню.Откроется решение с именем MenuCommandTest. В файле MenuCommandTestPackage есть код, который создает команду меню и помещает его в меню "Сервис ". На этом этапе команда просто приводит к отображению поля сообщения. Далее показано, как изменить это, чтобы отобразить украшение комментария.
Откройте файл source.extension.vsixmanifest в редакторе манифестов VSIX. На
Assets
вкладке должна быть строка для Microsoft.VisualStudio.VsPackage с именем MenuCommandTest.Сохраните и закройте файл source.extension.vsixmanifest .
Добавление расширения MEF в расширение команды
В Обозреватель решений щелкните правой кнопкой мыши узел решения, нажмите кнопку "Добавить" и нажмите кнопку "Создать проект". В диалоговом окне "Добавить новый проект" щелкните расширяемость в visual C#, а затем проект VSIX. Присвойте проекту имя
CommentAdornmentTest
.Так как этот проект будет взаимодействовать со сборкой VSPackage с строгим именем, необходимо подписать сборку. Вы можете повторно использовать файл ключа, уже созданный для сборки VSPackage.
Откройте свойства проекта и перейдите на вкладку "Подписывание ".
Выберите " Подписать сборку".
В разделе "Выбор файла ключа строгого имени" выберите файл Key.snk, созданный для сборки MenuCommandTest.
См. расширение MEF в проекте VSPackage
Так как компонент MEF добавляется в VSPackage, необходимо указать оба типа ресурсов в манифесте.
Примечание.
Дополнительные сведения о MEF см. в статье Об управляемой платформе расширяемости (MEF).
Ссылка на компонент MEF в проекте VSPackage
В проекте MenuCommandTest откройте файл source.extension.vsixmanifest в редакторе манифеста VSIX.
На вкладке "Активы" нажмите кнопку "Создать".
В списке типов выберите Microsoft.VisualStudio.MefComponent.
В списке источников выберите проект в текущем решении.
В списке проектов выберите CommentAdornmentTest.
Сохраните и закройте файл source.extension.vsixmanifest .
Убедитесь, что проект MenuCommandTest имеет ссылку на проект CommentAdornmentTest.
В проекте CommentAdornmentTest задайте проект для создания сборки. В Обозреватель решений выберите проект и просмотрите окно "Свойства" для свойства Copy Build OutputDirectory и задайте для него значение true.
Определение украшения примечания
Украшение комментария состоит из ITrackingSpan того, что отслеживает выделенный текст, а также некоторые строки, представляющие автора и описание текста.
Определение украшения комментария
В проекте CommentAdornmentTest добавьте новый файл класса и назовите его
CommentAdornment
.Добавьте следующие ссылки:
Microsoft.VisualStudio.CoreUtility
Microsoft.VisualStudio.Text.Data
Microsoft.VisualStudio.Text.Logic
Microsoft.VisualStudio.Text.UI
Microsoft.VisualStudio.Text.UI.Wpf
System.ComponentModel.Composition
PresentationCore
PresentationFramework
WindowsBase
Добавьте следующую
using
директиву.using Microsoft.VisualStudio.Text;
Файл должен содержать класс с именем
CommentAdornment
.internal class CommentAdornment
Добавьте три поля в
CommentAdornment
класс для ITrackingSpanавтора и описания.public readonly ITrackingSpan Span; public readonly string Author; public readonly string Text;
Добавьте конструктор, который инициализирует поля.
public CommentAdornment(SnapshotSpan span, string author, string text) { this.Span = span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeExclusive); this.Author = author; this.Text = text; }
Создание визуального элемента для украшения
Определите визуальный элемент для украшения. В этом пошаговом руководстве определите элемент управления, наследующий от класса CanvasWindows Presentation Foundation (WPF).
Создайте класс в проекте CommentAdornmentTest и присвойте ему
CommentBlock
имя.Добавьте следующие
using
директивы.using Microsoft.VisualStudio.Text; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities;
CommentBlock
Сделайте класс наследуемым.Canvasinternal class CommentBlock : Canvas { }
Добавьте некоторые частные поля, чтобы определить визуальные аспекты украшения.
private Geometry textGeometry; private Grid commentGrid; private static Brush brush; private static Pen solidPen; private static Pen dashPen;
Добавьте конструктор, который определяет украшение комментария и добавляет соответствующий текст.
public CommentBlock(double textRightEdge, double viewRightEdge, Geometry newTextGeometry, string author, string body) { if (brush == null) { brush = new SolidColorBrush(Color.FromArgb(0x20, 0x00, 0xff, 0x00)); brush.Freeze(); Brush penBrush = new SolidColorBrush(Colors.Green); penBrush.Freeze(); solidPen = new Pen(penBrush, 0.5); solidPen.Freeze(); dashPen = new Pen(penBrush, 0.5); dashPen.DashStyle = DashStyles.Dash; dashPen.Freeze(); } this.textGeometry = newTextGeometry; TextBlock tb1 = new TextBlock(); tb1.Text = author; TextBlock tb2 = new TextBlock(); tb2.Text = body; const int MarginWidth = 8; this.commentGrid = new Grid(); this.commentGrid.RowDefinitions.Add(new RowDefinition()); this.commentGrid.RowDefinitions.Add(new RowDefinition()); ColumnDefinition cEdge = new ColumnDefinition(); cEdge.Width = new GridLength(MarginWidth); ColumnDefinition cEdge2 = new ColumnDefinition(); cEdge2.Width = new GridLength(MarginWidth); this.commentGrid.ColumnDefinitions.Add(cEdge); this.commentGrid.ColumnDefinitions.Add(new ColumnDefinition()); this.commentGrid.ColumnDefinitions.Add(cEdge2); System.Windows.Shapes.Rectangle rect = new System.Windows.Shapes.Rectangle(); rect.RadiusX = 6; rect.RadiusY = 3; rect.Fill = brush; rect.Stroke = Brushes.Green; Size inf = new Size(double.PositiveInfinity, double.PositiveInfinity); tb1.Measure(inf); tb2.Measure(inf); double middleWidth = Math.Max(tb1.DesiredSize.Width, tb2.DesiredSize.Width); this.commentGrid.Width = middleWidth + 2 * MarginWidth; Grid.SetColumn(rect, 0); Grid.SetRow(rect, 0); Grid.SetRowSpan(rect, 2); Grid.SetColumnSpan(rect, 3); Grid.SetRow(tb1, 0); Grid.SetColumn(tb1, 1); Grid.SetRow(tb2, 1); Grid.SetColumn(tb2, 1); this.commentGrid.Children.Add(rect); this.commentGrid.Children.Add(tb1); this.commentGrid.Children.Add(tb2); Canvas.SetLeft(this.commentGrid, Math.Max(viewRightEdge - this.commentGrid.Width - 20.0, textRightEdge + 20.0)); Canvas.SetTop(this.commentGrid, textGeometry.GetRenderBounds(solidPen).Top); this.Children.Add(this.commentGrid); }
Кроме того, реализуйте OnRender обработчик событий, который рисует украшение.
protected override void OnRender(DrawingContext dc) { base.OnRender(dc); if (this.textGeometry != null) { dc.DrawGeometry(brush, solidPen, this.textGeometry); Rect textBounds = this.textGeometry.GetRenderBounds(solidPen); Point p1 = new Point(textBounds.Right, textBounds.Bottom); Point p2 = new Point(Math.Max(Canvas.GetLeft(this.commentGrid) - 20.0, p1.X), p1.Y); Point p3 = new Point(Math.Max(Canvas.GetLeft(this.commentGrid), p1.X), (Canvas.GetTop(this.commentGrid) + p1.Y) * 0.5); dc.DrawLine(dashPen, p1, p2); dc.DrawLine(dashPen, p2, p3); } }
Добавление IWpfTextViewCreationListener
Это IWpfTextViewCreationListener часть компонента MEF, которую можно использовать для прослушивания событий создания.
Добавьте файл класса в проект CommentAdornmentTest и присвойте ему
Connector
имя.Добавьте следующие
using
директивы.using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities;
Объявите класс, реализующий IWpfTextViewCreationListenerи экспортируйте его с ContentTypeAttribute помощью текста и объекта TextViewRoleAttribute Document. Атрибут типа контента указывает тип контента, к которому применяется компонент. Текстовый тип является базовым типом для всех типов файлов, не являющихся двоичными. Таким образом, почти каждое созданное текстовое представление будет иметь этот тип. Атрибут роли представления текста указывает тип текстового представления, к которому применяется компонент. Роли представления текста документа обычно показывают текст, состоящий из строк и хранящийся в файле.
TextViewCreated Реализуйте метод таким образом, чтобы он вызывает статическое
Create()
событиеCommentAdornmentManager
объекта.public void TextViewCreated(IWpfTextView textView) { CommentAdornmentManager.Create(textView); }
Добавьте метод, который можно использовать для выполнения команды.
static public void Execute(IWpfTextViewHost host) { IWpfTextView view = host.TextView; //Add a comment on the selected text. if (!view.Selection.IsEmpty) { //Get the provider for the comment adornments in the property bag of the view. CommentAdornmentProvider provider = view.Properties.GetProperty<CommentAdornmentProvider>(typeof(CommentAdornmentProvider)); //Add some arbitrary author and comment text. string author = System.Security.Principal.WindowsIdentity.GetCurrent().Name; string comment = "Four score...."; //Add the comment adornment using the provider. provider.Add(view.Selection.SelectedSpans[0], author, comment); } }
Определение слоя украшений
Чтобы добавить новое украшение, необходимо определить слой украшения.
Определение слоя украшений
Connector
В классе объявите открытое поле типа AdornmentLayerDefinitionи экспортируйте его с NameAttribute указанием уникального имени слоя украшения и OrderAttribute отношения Z-порядка этого слоя украшения к другим слоям представления текста (текст, курсор и выделение).[Export(typeof(AdornmentLayerDefinition))] [Name("CommentAdornmentLayer")] [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] public AdornmentLayerDefinition commentLayerDefinition;
Предоставление украшений комментариев
При определении украшения также реализуйте поставщик украшений примечания и диспетчер украшений примечания. Поставщик украшений комментариев хранит список украшений комментариев, прослушивает Changed события в базовом текстовом буфере и удаляет украшения комментариев при удалении базового текста.
Добавьте новый файл класса в проект CommentAdornmentTest и назовите его
CommentAdornmentProvider
.Добавьте следующие
using
директивы.using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor;
Добавьте класс с именем
CommentAdornmentProvider
.internal class CommentAdornmentProvider { }
Добавьте частные поля для текстового буфера и список украшений комментариев, связанных с буфером.
private ITextBuffer buffer; private IList<CommentAdornment> comments = new List<CommentAdornment>();
Добавление конструктора для
CommentAdornmentProvider
. Этот конструктор должен иметь закрытый доступ, так как поставщик создает экземпляр методаCreate()
. Конструктор добавляетOnBufferChanged
обработчик событий в Changed событие.private CommentAdornmentProvider(ITextBuffer buffer) { this.buffer = buffer; //listen to the Changed event so we can react to deletions. this.buffer.Changed += OnBufferChanged; }
Добавьте метод
Create()
.public static CommentAdornmentProvider Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentProvider>(delegate { return new CommentAdornmentProvider(view.TextBuffer); }); }
Добавьте метод
Detach()
.public void Detach() { if (this.buffer != null) { //remove the Changed listener this.buffer.Changed -= OnBufferChanged; this.buffer = null; } }
OnBufferChanged
Добавьте обработчик событий.private void OnBufferChanged(object sender, TextContentChangedEventArgs e) { //Make a list of all comments that have a span of at least one character after applying the change. There is no need to raise a changed event for the deleted adornments. The adornments are deleted only if a text change would cause the view to reformat the line and discard the adornments. IList<CommentAdornment> keptComments = new List<CommentAdornment>(this.comments.Count); foreach (CommentAdornment comment in this.comments) { Span span = comment.Span.GetSpan(e.After); //if a comment does not span at least one character, its text was deleted. if (span.Length != 0) { keptComments.Add(comment); } } this.comments = keptComments; }
Добавьте объявление для
CommentsChanged
события.public event EventHandler<CommentsChangedEventArgs> CommentsChanged;
Создайте метод для
Add()
добавления украшения.public void Add(SnapshotSpan span, string author, string text) { if (span.Length == 0) throw new ArgumentOutOfRangeException("span"); if (author == null) throw new ArgumentNullException("author"); if (text == null) throw new ArgumentNullException("text"); //Create a comment adornment given the span, author and text. CommentAdornment comment = new CommentAdornment(span, author, text); //Add it to the list of comments. this.comments.Add(comment); //Raise the changed event. EventHandler<CommentsChangedEventArgs> commentsChanged = this.CommentsChanged; if (commentsChanged != null) commentsChanged(this, new CommentsChangedEventArgs(comment, null)); }
RemoveComments()
Добавьте метод.public void RemoveComments(SnapshotSpan span) { EventHandler<CommentsChangedEventArgs> commentsChanged = this.CommentsChanged; //Get a list of all the comments that are being kept IList<CommentAdornment> keptComments = new List<CommentAdornment>(this.comments.Count); foreach (CommentAdornment comment in this.comments) { //find out if the given span overlaps with the comment text span. If two spans are adjacent, they do not overlap. To consider adjacent spans, use IntersectsWith. if (comment.Span.GetSpan(span.Snapshot).OverlapsWith(span)) { //Raise the change event to delete this comment. if (commentsChanged != null) commentsChanged(this, new CommentsChangedEventArgs(null, comment)); } else keptComments.Add(comment); } this.comments = keptComments; }
GetComments()
Добавьте метод, который возвращает все примечания в заданном диапазоне моментальных снимков.public Collection<CommentAdornment> GetComments(SnapshotSpan span) { IList<CommentAdornment> overlappingComments = new List<CommentAdornment>(); foreach (CommentAdornment comment in this.comments) { if (comment.Span.GetSpan(span.Snapshot).OverlapsWith(span)) overlappingComments.Add(comment); } return new Collection<CommentAdornment>(overlappingComments); }
Добавьте класс с именем
CommentsChangedEventArgs
, как показано ниже.internal class CommentsChangedEventArgs : EventArgs { public readonly CommentAdornment CommentAdded; public readonly CommentAdornment CommentRemoved; public CommentsChangedEventArgs(CommentAdornment added, CommentAdornment removed) { this.CommentAdded = added; this.CommentRemoved = removed; } }
Управление украшениями комментариев
Диспетчер украшений примечания создает украшение и добавляет его в слой украшений. Он прослушивает LayoutChanged и Closed события, чтобы он смог переместить или удалить украшение. Он также прослушивает CommentsChanged
событие, которое запускается поставщиком украшений комментариев при добавлении или удалении комментариев.
Добавьте файл класса в проект CommentAdornmentTest и присвойте ему
CommentAdornmentManager
имя.Добавьте следующие
using
директивы.using System; using System.Collections.Generic; using System.Windows.Media; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting;
Добавьте класс с именем
CommentAdornmentManager
.internal class CommentAdornmentManager { }
Добавьте некоторые частные поля.
private readonly IWpfTextView view; private readonly IAdornmentLayer layer; private readonly CommentAdornmentProvider provider;
Добавьте конструктор, который подписывает диспетчера на LayoutChanged события и Closed события, а также на
CommentsChanged
событие. Конструктор является закрытым, так как диспетчер создается статическим методомCreate()
.private CommentAdornmentManager(IWpfTextView view) { this.view = view; this.view.LayoutChanged += OnLayoutChanged; this.view.Closed += OnClosed; this.layer = view.GetAdornmentLayer("CommentAdornmentLayer"); this.provider = CommentAdornmentProvider.Create(view); this.provider.CommentsChanged += OnCommentsChanged; }
Create()
Добавьте метод, который получает поставщика или создает его при необходимости.public static CommentAdornmentManager Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentManager>(delegate { return new CommentAdornmentManager(view); }); }
CommentsChanged
Добавьте обработчик.private void OnCommentsChanged(object sender, CommentsChangedEventArgs e) { //Remove the comment (when the adornment was added, the comment adornment was used as the tag). if (e.CommentRemoved != null) this.layer.RemoveAdornmentsByTag(e.CommentRemoved); //Draw the newly added comment (this will appear immediately: the view does not need to do a layout). if (e.CommentAdded != null) this.DrawComment(e.CommentAdded); }
Closed Добавьте обработчик.
private void OnClosed(object sender, EventArgs e) { this.provider.Detach(); this.view.LayoutChanged -= OnLayoutChanged; this.view.Closed -= OnClosed; }
LayoutChanged Добавьте обработчик.
private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) { //Get all of the comments that intersect any of the new or reformatted lines of text. List<CommentAdornment> newComments = new List<CommentAdornment>(); //The event args contain a list of modified lines and a NormalizedSpanCollection of the spans of the modified lines. //Use the latter to find the comments that intersect the new or reformatted lines of text. foreach (Span span in e.NewOrReformattedSpans) { newComments.AddRange(this.provider.GetComments(new SnapshotSpan(this.view.TextSnapshot, span))); } //It is possible to get duplicates in this list if a comment spanned 3 lines, and the first and last lines were modified but the middle line was not. //Sort the list and skip duplicates. newComments.Sort(delegate(CommentAdornment a, CommentAdornment b) { return a.GetHashCode().CompareTo(b.GetHashCode()); }); CommentAdornment lastComment = null; foreach (CommentAdornment comment in newComments) { if (comment != lastComment) { lastComment = comment; this.DrawComment(comment); } } }
Добавьте закрытый метод, который рисует комментарий.
private void DrawComment(CommentAdornment comment) { SnapshotSpan span = comment.Span.GetSpan(this.view.TextSnapshot); Geometry g = this.view.TextViewLines.GetMarkerGeometry(span); if (g != null) { //Find the rightmost coordinate of all the lines that intersect the adornment. double maxRight = 0.0; foreach (ITextViewLine line in this.view.TextViewLines.GetTextViewLinesIntersectingSpan(span)) maxRight = Math.Max(maxRight, line.Right); //Create the visualization. CommentBlock block = new CommentBlock(maxRight, this.view.ViewportRight, g, comment.Author, comment.Text); //Add it to the layer. this.layer.AddAdornment(span, comment, block); } }
Используйте команду меню, чтобы добавить украшение комментария
С помощью команды меню можно создать украшение комментария, реализуя MenuItemCallback
метод VSPackage.
Добавьте следующие ссылки в проект MenuCommandTest:
Microsoft.VisualStudio.TextManager.Interop
Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Text.UI.Wpf
Откройте файл AddAdornment.cs и добавьте следующие
using
директивы.using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Editor; using CommentAdornmentTest;
Execute()
Удалите метод и добавьте следующий обработчик команд.private async void AddAdornmentHandler(object sender, EventArgs e) { }
Добавьте код для получения активного представления. Чтобы получить активную
IVsTextView
среду, необходимо получитьSVsTextManager
оболочку Visual Studio.private async void AddAdornmentHandler(object sender, EventArgs e) { IVsTextManager txtMgr = (IVsTextManager) await ServiceProvider.GetServiceAsync(typeof(SVsTextManager)); IVsTextView vTextView = null; int mustHaveFocus = 1; txtMgr.GetActiveView(mustHaveFocus, null, out vTextView); }
Если это текстовое представление является экземпляром текстового представления редактора, его можно привести к IVsUserData интерфейсу, а затем получить IWpfTextViewHost и связанные IWpfTextViewс ним. IWpfTextViewHost
Connector.Execute()
Используйте метод, который получает поставщик украшений комментариев и добавляет украшение. Теперь обработчик команд должен выглядеть следующим образом:private async void AddAdornmentHandler(object sender, EventArgs e) { IVsTextManager txtMgr = (IVsTextManager) await ServiceProvider.GetServiceAsync(typeof(SVsTextManager)); IVsTextView vTextView = null; int mustHaveFocus = 1; txtMgr.GetActiveView(mustHaveFocus, null, out vTextView); IVsUserData userData = vTextView as IVsUserData; if (userData == null) { Console.WriteLine("No text view is currently open"); return; } IWpfTextViewHost viewHost; object holder; Guid guidViewHost = DefGuidList.guidIWpfTextViewHost; userData.GetData(ref guidViewHost, out holder); viewHost = (IWpfTextViewHost)holder; Connector.Execute(viewHost); }
Задайте метод AddAdornmentHandler в качестве обработчика команды AddAdornment в конструкторе AddAdornment.
private AddAdornment(AsyncPackage package, OleMenuCommandService commandService) { this.package = package ?? throw new ArgumentNullException(nameof(package)); commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); var menuCommandID = new CommandID(CommandSet, CommandId); var menuItem = new MenuCommand(this.AddAdornmentHandler, menuCommandID); commandService.AddCommand(menuItem); }
Сборка и проверка кода
Создайте решение и начните отладку. Должен появиться экспериментальный экземпляр.
Создание текстового файла. Введите текст и выберите его.
В меню "Сервис" нажмите кнопку "Вызвать добавление украшения". Воздушный шар должен отображаться справа от текстового окна и должен содержать текст, похожий на следующий текст.
YourUserName
Fourscore...