Procédure pas à pas : utiliser une commande shell avec une extension d’éditeur
À partir d’un VSPackage, vous pouvez ajouter des fonctionnalités telles que des commandes de menu à l’éditeur. Cette procédure pas à pas montre comment ajouter une ornement à un affichage texte dans l’éditeur en appelant une commande de menu.
Cette procédure pas à pas illustre l’utilisation d’un VSPackage avec une partie de composant MEF (Managed Extensibility Framework). Vous devez utiliser un VSPackage pour inscrire la commande de menu avec l’interpréteur de commandes Visual Studio. Vous pouvez également utiliser la commande pour accéder à la partie du composant MEF.
Créer une extension avec une commande de menu
Créez un VSPackage qui place une commande de menu nommée Ajouter un ornement dans le menu Outils .
Créez un projet VSIX C# nommé
MenuCommandTest
et ajoutez un nom de modèle d’élément de commande personnalisé AddAdornment. Pour plus d’informations, consultez Créer une extension avec une commande de menu.Une solution nommée MenuCommandTest s’ouvre. Le fichier MenuCommandTestPackage contient le code qui crée la commande de menu et le place dans le menu Outils . À ce stade, la commande provoque simplement l’affichage d’une boîte de message. Les étapes ultérieures montrent comment modifier cette procédure pour afficher l’ornement du commentaire.
Ouvrez le fichier source.extension.vsixmanifest dans l’éditeur de manifeste VSIX. L’onglet
Assets
doit avoir une ligne pour un Microsoft.VisualStudio.VsPackage nommé MenuCommandTest.Enregistrez et fermez le fichier source.extension.vsixmanifest .
Ajouter une extension MEF à l’extension de commande
Dans Explorateur de solutions, cliquez avec le bouton droit sur le nœud de solution, cliquez sur Ajouter, puis sur Nouveau projet. Dans la boîte de dialogue Ajouter un nouveau projet , cliquez sur Extensibilité sous Visual C#, puis sur Projet VSIX. Nommez le projet
CommentAdornmentTest
.Étant donné que ce projet interagit avec l’assembly VSPackage nommé fort, vous devez signer l’assembly. Vous pouvez réutiliser le fichier de clé déjà créé pour l’assembly VSPackage.
Ouvrez les propriétés du projet et sélectionnez l’onglet Signature .
Sélectionnez Signer l’assembly.
Sous Choisir un fichier de clé de nom fort, sélectionnez le fichier Key.snk généré pour l’assembly MenuCommandTest.
Reportez-vous à l’extension MEF dans le projet VSPackage
Étant donné que vous ajoutez un composant MEF au VSPackage, vous devez spécifier les deux types de ressources dans le manifeste.
Remarque
Pour plus d’informations sur MEF, consultez Managed Extensibility Framework (MEF).
Pour faire référence au composant MEF dans le projet VSPackage
Dans le projet MenuCommandTest, ouvrez le fichier source.extension.vsixmanifest dans l’éditeur de manifeste VSIX.
Sous l’onglet Ressources , cliquez sur Nouveau.
Dans la liste Type, choisissez Microsoft.VisualStudio.MefComponent.
Dans la liste Source, choisissez Un projet dans la solution actuelle.
Dans la liste projet , choisissez CommentAdornmentTest.
Enregistrez et fermez le fichier source.extension.vsixmanifest .
Vérifiez que le projet MenuCommandTest a une référence au projet CommentAdornmentTest.
Dans le projet CommentAdornmentTest, définissez le projet pour produire un assembly. Dans le Explorateur de solutions, sélectionnez le projet et recherchez la valeur true dans la fenêtre Propriétés de la propriété Copier la sortie de build dans OutputDirectory.
Définir un ornement de commentaire
L’ornement de commentaire lui-même se compose d’un ITrackingSpan suivi du texte sélectionné et de certaines chaînes qui représentent l’auteur et la description du texte.
Pour définir une ornement de commentaire
Dans le projet CommentAdornmentTest, ajoutez un nouveau fichier de classe et nommez-le
CommentAdornment
.Ajoutez les références suivantes :
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
Ajoutez la directive suivante
using
.using Microsoft.VisualStudio.Text;
Le fichier doit contenir une classe nommée
CommentAdornment
.internal class CommentAdornment
Ajoutez trois champs à la
CommentAdornment
classe pour l’auteur ITrackingSpanet la description.public readonly ITrackingSpan Span; public readonly string Author; public readonly string Text;
Ajoutez un constructeur qui initialise les champs.
public CommentAdornment(SnapshotSpan span, string author, string text) { this.Span = span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeExclusive); this.Author = author; this.Text = text; }
Créer un élément visuel pour l’ornement
Définissez un élément visuel pour votre ornement. Pour cette procédure pas à pas, définissez un contrôle qui hérite de la classe CanvasWPF (Windows Presentation Foundation).
Créez une classe dans le projet CommentAdornmentTest et nommez-la
CommentBlock
.Ajoutez les directives suivantes
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;
Faire hériter la
CommentBlock
classe de Canvas.internal class CommentBlock : Canvas { }
Ajoutez des champs privés pour définir les aspects visuels de l’ornement.
private Geometry textGeometry; private Grid commentGrid; private static Brush brush; private static Pen solidPen; private static Pen dashPen;
Ajoutez un constructeur qui définit l’ornement de commentaire et ajoute le texte approprié.
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); }
Implémentez également un gestionnaire d’événements OnRender qui dessine l’ornement.
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); } }
Ajouter un IWpfTextViewCreationListener
Il IWpfTextViewCreationListener s’agit d’une partie de composant MEF que vous pouvez utiliser pour écouter les événements de création.
Ajoutez un fichier de classe au projet CommentAdornmentTest et nommez-le
Connector
.Ajoutez les directives suivantes
using
.using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities;
Déclarez une classe qui implémente IWpfTextViewCreationListener, puis exportez-la avec un ContentTypeAttribute « text » et un TextViewRoleAttribute de Document. L’attribut de type de contenu spécifie le type de contenu auquel le composant s’applique. Le type de texte est le type de base pour tous les types de fichiers non binaires. Par conséquent, presque toutes les vues de texte créées seront de ce type. L’attribut de rôle d’affichage de texte spécifie le type d’affichage de texte auquel le composant s’applique. Les rôles d’affichage texte de document affichent généralement le texte composé de lignes et sont stockés dans un fichier.
Implémentez la TextViewCreated méthode pour qu’elle appelle l’événement statique
Create()
duCommentAdornmentManager
.public void TextViewCreated(IWpfTextView textView) { CommentAdornmentManager.Create(textView); }
Ajoutez une méthode que vous pouvez utiliser pour exécuter la commande.
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); } }
Définir une couche d’ornement
Pour ajouter un nouvel ornement, vous devez définir une couche d’ornement.
Pour définir une couche d’ornement
Dans la
Connector
classe, déclarez un champ public de type AdornmentLayerDefinition, puis exportez-le avec un NameAttribute nom unique pour la couche d’ornements et un OrderAttribute qui définit la relation d’ordre Z de cette couche d’ornement vers les autres couches d’affichage de texte (texte, caret et sélection).[Export(typeof(AdornmentLayerDefinition))] [Name("CommentAdornmentLayer")] [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] public AdornmentLayerDefinition commentLayerDefinition;
Fournir des ornements de commentaires
Lorsque vous définissez un ornement, implémentez également un fournisseur d’ornements de commentaire et un gestionnaire d’ornements de commentaires. Le fournisseur d’ornements de commentaire conserve une liste d’ornements de commentaires, écoute les Changed événements sur la mémoire tampon de texte sous-jacente et supprime les ornements de commentaire lorsque le texte sous-jacent est supprimé.
Ajoutez un nouveau fichier de classe au projet CommentAdornmentTest et nommez-le
CommentAdornmentProvider
.Ajoutez les directives suivantes
using
.using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor;
Ajoutez une classe nommée
CommentAdornmentProvider
.internal class CommentAdornmentProvider { }
Ajoutez des champs privés pour la mémoire tampon de texte et la liste des ornements de commentaires liés à la mémoire tampon.
private ITextBuffer buffer; private IList<CommentAdornment> comments = new List<CommentAdornment>();
Ajoutez un constructeur pour
CommentAdornmentProvider
. Ce constructeur doit avoir un accès privé, car le fournisseur est instancié par laCreate()
méthode. Le constructeur ajoute leOnBufferChanged
gestionnaire d’événements à l’événement Changed .private CommentAdornmentProvider(ITextBuffer buffer) { this.buffer = buffer; //listen to the Changed event so we can react to deletions. this.buffer.Changed += OnBufferChanged; }
Ajoutez la méthode
Create()
.public static CommentAdornmentProvider Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentProvider>(delegate { return new CommentAdornmentProvider(view.TextBuffer); }); }
Ajoutez la méthode
Detach()
.public void Detach() { if (this.buffer != null) { //remove the Changed listener this.buffer.Changed -= OnBufferChanged; this.buffer = null; } }
Ajoutez le gestionnaire d’événements
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; }
Ajoutez une déclaration pour un
CommentsChanged
événement.public event EventHandler<CommentsChangedEventArgs> CommentsChanged;
Créez une
Add()
méthode pour ajouter l’ornement.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)); }
Ajoutez une
RemoveComments()
méthode.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; }
Ajoutez une
GetComments()
méthode qui retourne tous les commentaires dans une étendue de instantané donnée.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); }
Ajoutez une classe nommée
CommentsChangedEventArgs
, comme suit.internal class CommentsChangedEventArgs : EventArgs { public readonly CommentAdornment CommentAdded; public readonly CommentAdornment CommentRemoved; public CommentsChangedEventArgs(CommentAdornment added, CommentAdornment removed) { this.CommentAdded = added; this.CommentRemoved = removed; } }
Gérer les ornements de commentaires
Le gestionnaire d’ornements de commentaire crée l’ornement et l’ajoute à la couche d’ornement. Il écoute les événements et Closed les LayoutChanged événements afin qu’il puisse déplacer ou supprimer l’ornement. Il écoute également l’événement CommentsChanged
déclenché par le fournisseur d’ornement de commentaire lorsque des commentaires sont ajoutés ou supprimés.
Ajoutez un fichier de classe au projet CommentAdornmentTest et nommez-le
CommentAdornmentManager
.Ajoutez les directives suivantes
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;
Ajoutez une classe nommée
CommentAdornmentManager
.internal class CommentAdornmentManager { }
Ajoutez des champs privés.
private readonly IWpfTextView view; private readonly IAdornmentLayer layer; private readonly CommentAdornmentProvider provider;
Ajoutez un constructeur qui abonne le gestionnaire aux LayoutChanged événements et Closed aux événements, ainsi qu’à l’événement
CommentsChanged
. Le constructeur est privé, car le gestionnaire est instancié par la méthode statiqueCreate()
.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; }
Ajoutez la
Create()
méthode qui obtient un fournisseur ou en crée un si nécessaire.public static CommentAdornmentManager Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentManager>(delegate { return new CommentAdornmentManager(view); }); }
Ajoutez le
CommentsChanged
gestionnaire.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); }
Ajoutez le Closed gestionnaire.
private void OnClosed(object sender, EventArgs e) { this.provider.Detach(); this.view.LayoutChanged -= OnLayoutChanged; this.view.Closed -= OnClosed; }
Ajoutez le LayoutChanged gestionnaire.
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); } } }
Ajoutez la méthode privée qui dessine le commentaire.
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); } }
Utilisez la commande de menu pour ajouter l’ornement des commentaires
Vous pouvez utiliser la commande de menu pour créer un ornement de commentaire en implémentant la MenuItemCallback
méthode de VSPackage.
Ajoutez les références suivantes au projet MenuCommandTest :
Microsoft.VisualStudio.TextManager.Interop
Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Text.UI.Wpf
Ouvrez le fichier AddAdornment.cs et ajoutez les directives suivantes
using
.using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Editor; using CommentAdornmentTest;
Supprimez la
Execute()
méthode et ajoutez le gestionnaire de commandes suivant.private async void AddAdornmentHandler(object sender, EventArgs e) { }
Ajoutez du code pour obtenir l’affichage actif. Vous devez obtenir l’interpréteur
SVsTextManager
de commandes Visual Studio pour obtenir l’élément actifIVsTextView
.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); }
Si cette vue de texte est une instance d’une vue de texte de l’éditeur IVsUserData , vous pouvez la convertir en interface, puis obtenir le IWpfTextViewHost et son associé IWpfTextView. Utilisez la IWpfTextViewHost méthode pour appeler la
Connector.Execute()
méthode, qui obtient le fournisseur d’ornements de commentaire et ajoute l’ornement. Le gestionnaire de commandes doit maintenant ressembler à ce code :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); }
Définissez la méthode AddAdornmentHandler comme gestionnaire pour la commande AddAdornment dans le constructeur 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); }
Générer et tester le code
Générez la solution et commencez le débogage. L’instance expérimentale doit apparaître.
Créer un fichier texte. Tapez du texte, puis sélectionnez-le.
Dans le menu Outils , cliquez sur Appeler l’ornement. Une bulle doit s’afficher sur le côté droit de la fenêtre de texte et contenir du texte semblable au texte suivant.
YourUserName
Quatrescore...