Share via


Tutorial: Uso de un comando de shell con una extensión del editor

Desde un VSPackage, puede agregar características como comandos de menú al editor. En este tutorial se muestra cómo agregar un adorno a una vista de texto en el editor invocando un comando de menú.

En este tutorial se muestra el uso de un VSPackage junto con un elemento de componente managed Extensibility Framework (MEF). Debe usar un VSPackage para registrar el comando de menú con el shell de Visual Studio. Además, puede usar el comando para acceder al elemento del componente MEF.

Creación de una extensión con un comando de menú

Cree un VSPackage que coloque un comando de menú denominado Agregar adorno en el menú Herramientas .

  1. Cree un proyecto VSIX de C# denominado MenuCommandTesty agregue un nombre de plantilla de elemento de comando personalizado AddAdornment. Para obtener más información, vea Crear una extensión con un comando de menú.

  2. Se abre una solución denominada MenuCommandTest. El archivo MenuCommandTestPackage tiene el código que crea el comando de menú y lo coloca en el menú Herramientas . En este punto, el comando simplemente hace que aparezca un cuadro de mensaje. Los pasos posteriores mostrarán cómo cambiar esto para mostrar el adorno del comentario.

  3. Abra el archivo source.extension.vsixmanifest en el Editor de manifiestos vsIX. La Assets pestaña debe tener una fila para microsoft.VisualStudio.VsPackage denominada MenuCommandTest.

  4. Guarde y cierre el archivo source.extension.vsixmanifest .

Agregar una extensión MEF a la extensión de comando

  1. En Explorador de soluciones, haga clic con el botón derecho en el nodo de la solución, haga clic en Agregary, a continuación, haga clic en Nuevo proyecto. En el cuadro de diálogo Agregar nuevo proyecto , haga clic en Extensibilidad en Visual C# y, a continuación, en Proyecto VSIX. Dé un nombre al proyecto CommentAdornmentTest.

  2. Dado que este proyecto interactuará con el ensamblado VSPackage con nombre seguro, debe firmar el ensamblado. Puede reutilizar el archivo de clave ya creado para el ensamblado VSPackage.

    1. Abra las propiedades del proyecto y seleccione la pestaña Firma .

    2. Seleccione Firmar el ensamblado.

    3. En Elegir un archivo de clave de nombre seguro, seleccione el archivo Key.snk que se generó para el ensamblado MenuCommandTest.

Consulte la extensión MEF en el proyecto VSPackage.

Dado que va a agregar un componente MEF al VSPackage, debe especificar ambos tipos de recursos en el manifiesto.

Nota:

Para obtener más información sobre MEF, vea Managed Extensibility Framework (MEF).

Para hacer referencia al componente MEF en el proyecto VSPackage

  1. En el proyecto MenuCommandTest, abra el archivo source.extension.vsixmanifest en el Editor de manifiestos VSIX.

  2. En la pestaña Activos , haga clic en Nuevo.

  3. En la lista Tipo, elija Microsoft.VisualStudio.MefComponent.

  4. En la lista Origen, elija Un proyecto de la solución actual.

  5. En la lista Proyecto , elija CommentAdornmentTest.

  6. Guarde y cierre el archivo source.extension.vsixmanifest .

  7. Asegúrese de que el proyecto MenuCommandTest tiene una referencia al proyecto CommentAdornmentTest.

  8. En el proyecto CommentAdornmentTest, establezca el proyecto para generar un ensamblado. En el Explorador de soluciones, seleccione el proyecto y busque en la ventana Propiedades de la propiedad Copiar salida de compilación en OutputDirectory y establézcalo en true.

Definir un adorno de comentario

El adorno del comentario consta de un ITrackingSpan objeto que realiza un seguimiento del texto seleccionado y algunas cadenas que representan al autor y la descripción del texto.

Para definir un adorno de comentario

  1. En el proyecto CommentAdornmentTest, agregue un nuevo archivo de clase y asígnele CommentAdornmentel nombre .

  2. Agregue las siguientes referencias:

    1. Microsoft.VisualStudio.CoreUtility

    2. Microsoft.VisualStudio.Text.Data

    3. Microsoft.VisualStudio.Text.Logic

    4. Microsoft.VisualStudio.Text.UI

    5. Microsoft.VisualStudio.Text.UI.Wpf

    6. System.ComponentModel.Composition

    7. PresentationCore

    8. PresentationFramework

    9. WindowsBase

  3. Agregue la siguiente using directiva.

    using Microsoft.VisualStudio.Text;
    
  4. El archivo debe contener una clase denominada CommentAdornment.

    internal class CommentAdornment
    
  5. Agregue tres campos a la CommentAdornment clase para ITrackingSpan, el autor y la descripción.

    public readonly ITrackingSpan Span;
    public readonly string Author;
    public readonly string Text;
    
  6. Agregue un constructor que inicialice los campos.

    public CommentAdornment(SnapshotSpan span, string author, string text)
    {
        this.Span = span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeExclusive);
        this.Author = author;
        this.Text = text;
    }
    

Crear un elemento visual para el adorno

Defina un elemento visual para el adorno. Para este tutorial, defina un control que herede de la clase CanvasWindows Presentation Foundation (WPF).

  1. Cree una clase en el proyecto CommentAdornmentTest y asígnelo CommentBlockel nombre .

  2. Agregue las siguientes using directivas.

    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;
    
  3. Haga que la CommentBlock clase herede de Canvas.

    internal class CommentBlock : Canvas
    { }
    
  4. Agregue algunos campos privados para definir los aspectos visuales del adorno.

    private Geometry textGeometry;
    private Grid commentGrid;
    private static Brush brush;
    private static Pen solidPen;
    private static Pen dashPen;
    
  5. Agregue un constructor que defina el adorno del comentario y agregue el texto correspondiente.

    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);
    }
    
  6. Implemente también un OnRender controlador de eventos que dibuje el adorno.

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

Agregar un IWpfTextViewCreationListener

IWpfTextViewCreationListener es una parte del componente MEF que puede usar para escuchar los eventos de creación.

  1. Agregue un archivo de clase al proyecto CommentAdornmentTest y asígnele Connectorel nombre .

  2. Agregue las siguientes using directivas.

    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Utilities;
    
  3. Declare una clase que implemente IWpfTextViewCreationListenery expórtela con un ContentTypeAttribute de "texto" y un TextViewRoleAttribute de Document. El atributo de tipo de contenido especifica el tipo de contenido al que se aplica el componente. El tipo de texto es el tipo base para todos los tipos de archivo no binarios. Por lo tanto, casi todas las vistas de texto que se crean serán de este tipo. El atributo de rol de vista de texto especifica el tipo de vista de texto al que se aplica el componente. Por lo general, los roles de vista de texto de documento muestran texto compuesto de líneas y se almacenan en un archivo.

    [Export(typeof(IWpfTextViewCreationListener))]
    [ContentType("text")]
    [TextViewRole(PredefinedTextViewRoles.Document)]
    public sealed class Connector : IWpfTextViewCreationListener
    
  4. Implemente el TextViewCreated método para que llame al evento estático Create() de CommentAdornmentManager.

    public void TextViewCreated(IWpfTextView textView)
    {
        CommentAdornmentManager.Create(textView);
    }
    
  5. Agregue un método que puede usar para ejecutar el 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);
        }
    }
    

Definición de una capa de adorno

Para agregar un nuevo adorno, debe definir una capa de adorno.

Para definir una capa de adorno

  1. En la Connector clase , declare un campo público de tipo AdornmentLayerDefinitiony expórtelo con un NameAttribute que especifique un nombre único para la capa de adorno y un OrderAttribute que defina la relación de orden Z de esta capa de adorno con las demás capas de vista de texto (texto, intercalación y selección).

    [Export(typeof(AdornmentLayerDefinition))]
    [Name("CommentAdornmentLayer")]
    [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
    public AdornmentLayerDefinition commentLayerDefinition;
    
    

Proporcionar adornos de comentario

Al definir un adorno, implemente también un proveedor de adornos de comentarios y un administrador de adornos de comentarios. El proveedor de adornos de comentarios mantiene una lista de adornos de comentario, escucha Changed eventos en el búfer de texto subyacente y elimina adornos de comentario cuando se elimina el texto subyacente.

  1. Agregue un nuevo archivo de clase al proyecto CommentAdornmentTest y asígnele CommentAdornmentProviderel nombre .

  2. Agregue las siguientes using directivas.

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    
  3. Agregue una clase denominada CommentAdornmentProvider.

    internal class CommentAdornmentProvider
    {
    }
    
  4. Agregue campos privados para el búfer de texto y la lista de adornos de comentarios relacionados con el búfer.

    private ITextBuffer buffer;
    private IList<CommentAdornment> comments = new List<CommentAdornment>();
    
    
  5. Agregue un constructor para CommentAdornmentProvider. Este constructor debe tener acceso privado porque el método crea una instancia del Create() proveedor. El constructor agrega el OnBufferChanged controlador de eventos al Changed evento.

    private CommentAdornmentProvider(ITextBuffer buffer)
    {
        this.buffer = buffer;
        //listen to the Changed event so we can react to deletions. 
        this.buffer.Changed += OnBufferChanged;
    }
    
    
  6. Agregue el método Create().

    public static CommentAdornmentProvider Create(IWpfTextView view)
    {
        return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentProvider>(delegate { return new CommentAdornmentProvider(view.TextBuffer); });
    }
    
    
  7. Agregue el método Detach().

    public void Detach()
    {
        if (this.buffer != null)
        {
            //remove the Changed listener 
            this.buffer.Changed -= OnBufferChanged;
            this.buffer = null;
        }
    }
    
  8. Agregue el controlador de OnBufferChanged eventos.

    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;
    }
    
  9. Agregue una declaración para un CommentsChanged evento.

    public event EventHandler<CommentsChangedEventArgs> CommentsChanged;
    
  10. Cree un Add() método para agregar el adorno.

    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));
    }
    
    
  11. Agregue un RemoveComments() método .

    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;
    }
    
  12. Agregue un GetComments() método que devuelva todos los comentarios de un intervalo de instantáneas determinado.

    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);
    }
    
  13. Agregue una clase denominada CommentsChangedEventArgs, como se indica a continuación.

    internal class CommentsChangedEventArgs : EventArgs
    {
        public readonly CommentAdornment CommentAdded;
    
        public readonly CommentAdornment CommentRemoved;
    
        public CommentsChangedEventArgs(CommentAdornment added, CommentAdornment removed)
        {
            this.CommentAdded = added;
            this.CommentRemoved = removed;
        }
    }
    

Administrar adornos de comentarios

El administrador de adornos de comentarios crea el adorno y lo agrega a la capa de adorno. Escucha los LayoutChanged eventos y Closed para que pueda mover o eliminar el adorno. También escucha el CommentsChanged evento que desencadena el proveedor de adornos de comentarios cuando se agregan o quitan los comentarios.

  1. Agregue un archivo de clase al proyecto CommentAdornmentTest y asígnele CommentAdornmentManagerel nombre .

  2. Agregue las siguientes using directivas.

    using System;
    using System.Collections.Generic;
    using System.Windows.Media;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Formatting;
    
  3. Agregue una clase denominada CommentAdornmentManager.

    internal class CommentAdornmentManager
        {
        }
    
  4. Agregue algunos campos privados.

    private readonly IWpfTextView view;
    private readonly IAdornmentLayer layer;
    private readonly CommentAdornmentProvider provider;
    
  5. Agregue un constructor que suscriba el administrador a los LayoutChanged eventos y Closed , y también al CommentsChanged evento . El constructor es privado porque el método estático Create() crea una instancia del administrador.

    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;
    }
    
  6. Agregue el Create() método que obtiene un proveedor o crea uno si es necesario.

    public static CommentAdornmentManager Create(IWpfTextView view)
    {
        return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentManager>(delegate { return new CommentAdornmentManager(view); });
    }
    
  7. Agregue el CommentsChanged controlador.

    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);
    }
    
  8. Agregue el Closed controlador.

    private void OnClosed(object sender, EventArgs e)
    {
        this.provider.Detach();
        this.view.LayoutChanged -= OnLayoutChanged;
        this.view.Closed -= OnClosed;
    }
    
  9. Agregue el LayoutChanged controlador.

    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);
            }
        }
    }
    
  10. Agregue el método privado que dibuja el comentario.

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

Use el comando de menú para agregar el adorno del comentario.

Puede usar el comando de menú para crear un adorno de comentario mediante la implementación del MenuItemCallback método de VSPackage.

  1. Agregue las siguientes referencias al proyecto MenuCommandTest:

    • Microsoft.VisualStudio.TextManager.Interop

    • Microsoft.VisualStudio.Editor

    • Microsoft.VisualStudio.Text.UI.Wpf

  2. Abra el archivo AddAdornment.cs y agregue las siguientes using directivas.

    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Editor;
    using CommentAdornmentTest;
    
  3. Elimine el Execute() método y agregue el siguiente controlador de comandos.

    private async void AddAdornmentHandler(object sender, EventArgs e)
    {
    }
    
  4. Agregue código para obtener la vista activa. Debe obtener del SVsTextManager shell de Visual Studio para obtener el objeto activo IVsTextView.

    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);
    }
    
  5. Si esta vista de texto es una instancia de una vista de texto del editor, puede convertirla en la IVsUserData interfaz y, a continuación, obtener y IWpfTextViewHost su asociado IWpfTextView. IWpfTextViewHost Use para llamar al Connector.Execute() método , que obtiene el proveedor de adornos de comentario y agrega el adorno. El controlador de comandos debería tener ahora un aspecto similar al siguiente:

    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);
    }
    
  6. Establezca el método AddAdornmentHandler como controlador para el comando AddAdornment en el constructor 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);
    }
    

Compilación y prueba del código

  1. Compile la solución y comience la depuración. Debería aparecer la instancia experimental.

  2. Crear un archivo de texto Escriba algún texto y selecciónelo.

  3. En el menú Herramientas , haga clic en Invocar agregar adorno. Un globo debe mostrarse en el lado derecho de la ventana de texto y debe contener texto similar al texto siguiente.

    YourUserName

    Ochenta...