Procedura dettagliata: Usare un comando shell con un'estensione dell'editor
Da un pacchetto VSPackage è possibile aggiungere funzionalità come i comandi di menu all'editor. Questa procedura dettagliata illustra come aggiungere uno strumento decorativo a una visualizzazione di testo nell'editor richiamando un comando di menu.
Questa procedura dettagliata illustra l'uso di un VSPackage insieme a una parte del componente MEF (Managed Extensibility Framework). Per registrare il comando di menu con la shell di Visual Studio, è necessario usare un pacchetto VSPackage. È anche possibile usare il comando per accedere alla parte del componente MEF.
Creare un'estensione con un comando di menu
Creare un PACCHETTO VSPackage che inserisce un comando di menu denominato Aggiungi strumento decorativo nel menu Strumenti .
Creare un progetto VSIX C# denominato
MenuCommandTest
e aggiungere un nome di modello di elemento comando personalizzato AddAdornment. Per altre informazioni, vedere Creare un'estensione con un comando di menu.Verrà visualizzata una soluzione denominata MenuCommandTest. Il file MenuCommandTestPackage include il codice che crea il comando di menu e lo inserisce nel menu Strumenti . A questo punto, il comando fa sì che venga visualizzata una finestra di messaggio. I passaggi successivi illustrano come modificare questa impostazione per visualizzare la decorazione del commento.
Aprire il file source.extension.vsixmanifest nell'editor del manifesto VSIX. La
Assets
scheda deve contenere una riga per un oggetto Microsoft.VisualStudio.VsPackage denominato MenuCommandTest.Salvare e chiudere il file source.extension.vsixmanifest .
Aggiungere un'estensione MEF all'estensione del comando
In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo della soluzione, scegliere Aggiungi e quindi fare clic su Nuovo progetto. Nella finestra di dialogo Aggiungi nuovo progetto fare clic su Estendibilità in Visual C#, quindi su Progetto VSIX. Assegnare al progetto il nome
CommentAdornmentTest
.Poiché questo progetto interagirà con l'assembly VSPackage con nome sicuro, è necessario firmare l'assembly. È possibile riutilizzare il file di chiave già creato per l'assembly VSPackage.
Aprire le proprietà del progetto e selezionare la scheda Firma .
Selezionare Firma l'assembly.
In Scegliere un file di chiave con nome sicuro selezionare il file Key.snk generato per l'assembly MenuCommandTest.
Fare riferimento all'estensione MEF nel progetto VSPackage
Poiché si aggiunge un componente MEF al pacchetto VSPackage, è necessario specificare entrambi i tipi di asset nel manifesto.
Nota
Per altre informazioni su MEF, vedere Managed Extensibility Framework (MEF).
Per fare riferimento al componente MEF nel progetto VSPackage
Nel progetto MenuCommandTest aprire il file source.extension.vsixmanifest nell'editor del manifesto VSIX.
Nella scheda Asset fare clic su Nuovo.
Nell'elenco Tipo scegliere Microsoft.VisualStudio.MefComponent.
Nell'elenco Origine scegliere Un progetto nella soluzione corrente.
Nell'elenco Progetto scegliere CommentAdornmentTest.
Salvare e chiudere il file source.extension.vsixmanifest .
Assicurarsi che il progetto MenuCommandTest abbia un riferimento al progetto CommentAdornmentTest.
Nel progetto CommentAdornmentTest impostare il progetto per produrre un assembly. Nella Esplora soluzioni selezionare il progetto e cercare nella finestra Proprietà la proprietà Copia output compilazione in OutputDirectory e impostarla su true.
Definire un elemento decorativo per i commenti
La struttura del commento è costituita da un oggetto ITrackingSpan che tiene traccia del testo selezionato e di alcune stringhe che rappresentano l'autore e la descrizione del testo.
Per definire una struttura di commento
Nel progetto CommentAdornmentTest aggiungere un nuovo file di classe e denominarlo
CommentAdornment
.Aggiungere i riferimenti seguenti:
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
Aggiungere la direttiva seguente
using
.using Microsoft.VisualStudio.Text;
Il file deve contenere una classe denominata
CommentAdornment
.internal class CommentAdornment
Aggiungere tre campi alla
CommentAdornment
classe per , ITrackingSpanl'autore e la descrizione.public readonly ITrackingSpan Span; public readonly string Author; public readonly string Text;
Aggiungere un costruttore che inizializza i campi.
public CommentAdornment(SnapshotSpan span, string author, string text) { this.Span = span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeExclusive); this.Author = author; this.Text = text; }
Creare un elemento visivo per la decorazione
Definire un elemento visivo per la struttura. Per questa procedura dettagliata, definire un controllo che eredita dalla classe CanvasWindows Presentation Foundation (WPF).
Creare una classe nel progetto CommentAdornmentTest e denominarla
CommentBlock
.Aggiungere le direttive seguenti
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;
Fare in modo che la
CommentBlock
classe erediti da Canvas.internal class CommentBlock : Canvas { }
Aggiungere alcuni campi privati per definire gli aspetti visivi della struttura.
private Geometry textGeometry; private Grid commentGrid; private static Brush brush; private static Pen solidPen; private static Pen dashPen;
Aggiungere un costruttore che definisce la struttura del commento e aggiunge il testo pertinente.
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); }
Implementare anche un OnRender gestore eventi che disegna la decorazione.
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); } }
Aggiungere un IWpfTextViewCreationListener
IWpfTextViewCreationListener è una parte del componente MEF che è possibile usare per ascoltare gli eventi di creazione.
Aggiungere un file di classe al progetto CommentAdornmentTest e denominarlo
Connector
.Aggiungere le direttive seguenti
using
.using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities;
Dichiarare una classe che implementa IWpfTextViewCreationListenered esportarla con un ContentTypeAttribute oggetto "text" e un TextViewRoleAttribute di Document. L'attributo tipo di contenuto specifica il tipo di contenuto a cui si applica il componente. Il tipo di testo è il tipo di base per tutti i tipi di file non binari. Pertanto, quasi tutte le visualizzazioni di testo create saranno di questo tipo. L'attributo ruolo visualizzazione testo specifica il tipo di visualizzazione testo a cui si applica il componente. I ruoli di visualizzazione testo del documento in genere mostrano testo composto da righe e archiviato in un file.
Implementare il TextViewCreated metodo in modo che chiami l'evento statico
Create()
diCommentAdornmentManager
.public void TextViewCreated(IWpfTextView textView) { CommentAdornmentManager.Create(textView); }
Aggiungere un metodo che è possibile usare per eseguire il comando.
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); } }
Definire un livello di adornazione
Per aggiungere un nuovo strumento decorativo, è necessario definire un livello decorativo.
Per definire un livello di adornazione
Connector
Nella classe dichiarare un campo pubblico di tipo AdornmentLayerDefinitioned esportarlo con un NameAttribute oggetto che specifica un nome univoco per il livello di adornazione e un oggetto OrderAttribute che definisce la relazione dell'ordine Z di questo livello decorativo con gli altri livelli di visualizzazione testo (testo, cursore e selezione).[Export(typeof(AdornmentLayerDefinition))] [Name("CommentAdornmentLayer")] [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] public AdornmentLayerDefinition commentLayerDefinition;
Specificare gli elementi decorativi dei commenti
Quando si definisce un oggetto decorativo, implementare anche un provider di oggetti decorativi di commento e un gestore di oggetti decorativi dei commenti. Il provider di oggetti decorativi dei commenti mantiene un elenco di oggetti decorativi dei commenti, rimane in ascolto degli Changed eventi nel buffer di testo sottostante ed elimina le decorazioni dei commenti quando il testo sottostante viene eliminato.
Aggiungere un nuovo file di classe al progetto CommentAdornmentTest e denominarlo
CommentAdornmentProvider
.Aggiungere le direttive seguenti
using
.using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor;
Aggiungere una classe denominata
CommentAdornmentProvider
.internal class CommentAdornmentProvider { }
Aggiungere campi privati per il buffer di testo e l'elenco delle aree di commento correlate al buffer.
private ITextBuffer buffer; private IList<CommentAdornment> comments = new List<CommentAdornment>();
Aggiungere un costruttore per
CommentAdornmentProvider
. Questo costruttore deve avere accesso privato perché il provider viene creato un'istanza dalCreate()
metodo . Il costruttore aggiunge ilOnBufferChanged
gestore eventi all'evento Changed .private CommentAdornmentProvider(ITextBuffer buffer) { this.buffer = buffer; //listen to the Changed event so we can react to deletions. this.buffer.Changed += OnBufferChanged; }
Aggiungere il metodo
Create()
.public static CommentAdornmentProvider Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentProvider>(delegate { return new CommentAdornmentProvider(view.TextBuffer); }); }
Aggiungere il metodo
Detach()
.public void Detach() { if (this.buffer != null) { //remove the Changed listener this.buffer.Changed -= OnBufferChanged; this.buffer = null; } }
Aggiungere il
OnBufferChanged
gestore eventi.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; }
Aggiungere una dichiarazione per un
CommentsChanged
evento.public event EventHandler<CommentsChangedEventArgs> CommentsChanged;
Creare un
Add()
metodo per aggiungere la decorazione.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)); }
Aggiungere un
RemoveComments()
metodo.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; }
Aggiungere un
GetComments()
metodo che restituisce tutti i commenti in un intervallo di snapshot specificato.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); }
Aggiungere una classe denominata
CommentsChangedEventArgs
, come indicato di seguito.internal class CommentsChangedEventArgs : EventArgs { public readonly CommentAdornment CommentAdded; public readonly CommentAdornment CommentRemoved; public CommentsChangedEventArgs(CommentAdornment added, CommentAdornment removed) { this.CommentAdded = added; this.CommentRemoved = removed; } }
Gestire gli elementi decorativi dei commenti
Il gestore degli oggetti decorativi del commento crea l'elemento decorativo e lo aggiunge al livello di adornamento. È in ascolto degli LayoutChanged eventi e Closed in modo che possa spostare o eliminare la decorazione. Ascolta anche l'evento CommentsChanged
generato dal provider di commenti quando i commenti vengono aggiunti o rimossi.
Aggiungere un file di classe al progetto CommentAdornmentTest e denominarlo
CommentAdornmentManager
.Aggiungere le direttive seguenti
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;
Aggiungere una classe denominata
CommentAdornmentManager
.internal class CommentAdornmentManager { }
Aggiungere alcuni campi privati.
private readonly IWpfTextView view; private readonly IAdornmentLayer layer; private readonly CommentAdornmentProvider provider;
Aggiungere un costruttore che sottoscrive il gestore agli LayoutChanged eventi e Closed e anche all'evento
CommentsChanged
. Il costruttore è privato perché la gestione viene creata un'istanza dal metodo staticoCreate()
.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; }
Aggiungere il
Create()
metodo che ottiene un provider o ne crea uno, se necessario.public static CommentAdornmentManager Create(IWpfTextView view) { return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentManager>(delegate { return new CommentAdornmentManager(view); }); }
Aggiungere il
CommentsChanged
gestore.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); }
Aggiungere il Closed gestore.
private void OnClosed(object sender, EventArgs e) { this.provider.Detach(); this.view.LayoutChanged -= OnLayoutChanged; this.view.Closed -= OnClosed; }
Aggiungere il LayoutChanged gestore.
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); } } }
Aggiungere il metodo privato che disegna il commento.
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); } }
Usare il comando di menu per aggiungere la decorazione del commento
È possibile usare il comando di menu per creare una struttura di commento implementando il MenuItemCallback
metodo del pacchetto VSPackage.
Aggiungere i riferimenti seguenti al progetto MenuCommandTest:
Microsoft.VisualStudio.TextManager.Interop
Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Text.UI.Wpf
Aprire il file AddAdornment.cs e aggiungere le direttive seguenti
using
.using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Editor; using CommentAdornmentTest;
Eliminare il
Execute()
metodo e aggiungere il gestore dei comandi seguente.private async void AddAdornmentHandler(object sender, EventArgs e) { }
Aggiungere il codice per ottenere la visualizzazione attiva. Per ottenere l'oggetto attivo
IVsTextView
, è necessario ottenere l'oggettoSVsTextManager
della shell di 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); }
Se questa visualizzazione testo è un'istanza di una visualizzazione di testo dell'editor, è possibile eseguirne il cast all'interfaccia IVsUserData e quindi ottenere IWpfTextViewHost e il relativo oggetto associato IWpfTextView. IWpfTextViewHost Utilizzare per chiamare il
Connector.Execute()
metodo , che ottiene il provider di oggetti decorativi del commento e aggiunge la decorazione. Il gestore dei comandi dovrebbe ora essere simile al codice seguente: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); }
Impostare il metodo AddAdornmentHandler come gestore per il comando AddAdornment nel costruttore 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); }
Compilare e testare il codice
Creare la soluzione e avviare il debug. Verrà visualizzata l'istanza sperimentale.
Creare un file di testo. Digitare un testo e quindi selezionarlo.
Nel menu Strumenti fare clic su Richiama aggiungi strumento decorativo. Un fumetto deve essere visualizzato sul lato destro della finestra di testo e deve contenere testo simile al testo seguente.
NomeUtente
Fourscore...