Genomgång: Skapa en visningsutsmyckning, kommandon och inställningar (kolumnguider)
Du kan utöka Text/kodredigeraren i Visual Studio med kommandon och visningseffekter. Den här artikeln visar hur du kommer igång med en populär tilläggsfunktion, kolumnguider. Kolumnstödlinjer är visuellt ljusa linjer som ritas i textredigerarens vy för att hjälpa dig att hantera din kod till specifika kolumnbredder. Mer specifikt kan formaterad kod vara viktig för exempel som du tar med i dokument, blogginlägg eller felrapporter.
I den här genomgången gör du:
Skapa ett VSIX-projekt
Lägg till en utsmyckning i redigeringsvyn
Lägg till stöd för att spara och hämta inställningar (var du ritar kolumnstödlinjer och deras färg)
Lägg till kommandon (lägg till/ta bort kolumnstödlinjer, ändra deras färg)
Placera kommandona på snabbmenyerna Redigera meny och textdokument
Lägg till stöd för att anropa kommandona från Visual Studio-kommandofönstret
Du kan prova en version av funktionen för kolumnguider med det här Visual Studio-galleriet -tillägget.
Not
I den här genomgången klistrar du in en stor mängd kod i några filer som genereras av Visual Studio-tilläggsmallar. Men snart refererar den här genomgången till en slutförd lösning på GitHub med andra tilläggsexempel. Den färdiga koden skiljer sig något eftersom den har verkliga kommandoikoner i stället för att använda generictemplate-ikoner.
Konfigurera lösningen
Först skapar du ett VSIX-projekt, lägger till en utsmyckning i redigeringsvyn och lägger sedan till ett kommando (som lägger till en VSPackage för att äga kommandot). Den grundläggande arkitekturen är följande:
Du har en lyssnare för att skapa textvyer som skapar ett
ColumnGuideAdornment
objekt per vy. Det här objektet lyssnar efter händelser om att vyn ändras eller inställningar ändras, uppdaterar eller ritar om kolumnguider efter behov.Det finns en
GuidesSettingsManager
som hanterar läsning och skrivning från Visual Studio-inställningslagringen. Inställningshanteraren har också åtgärder för att uppdatera de inställningar som stöder användarkommandona (lägg till kolumn, ta bort kolumn, ändra färg).Det finns ett VSIP-paket som behövs om du har användarkommandon, men det är bara exempelkod som initierar kommandoimplementeringsobjektet.
Det finns ett
ColumnGuideCommands
objekt som kör användarkommandona och kopplar in kommandohanterare för kommandon som deklarerats i filen .vsct.VSIX. Använd -fil | Nytt ... kommando för att skapa ett projekt. Välj noden Utökningsbarhet under C# i det vänstra navigeringsfönstret och välj VSIX Project i det högra fönstret. Ange namnet ColumnGuides och välj OK för att skapa projektet.
Visa utsmyckning. Tryck på höger pekare på projektnoden i Solution Explorer. Välj Lägg till | Nytt objekt ... kommando för att lägga till ett nytt visningsobjekt. Välj Utökningsbarhet | Redigeraren i det vänstra navigeringsfönstret och välj Editor Viewport Adornment i det högra fönstret. Ange namnet ColumnGuideAdornment som objektnamn och välj Lägg till för att lägga till det.
Du kan se att den här objektmallen har lagt till två filer i projektet (samt referenser och så vidare): ColumnGuideAdornment.cs och ColumnGuideAdornmentTextViewCreationListener.cs. Mallarna ritar en lila rektangel i visningen. I följande avsnitt ändrar du ett par rader i lyssnaren för att skapa vyn och ersätter innehållet i ColumnGuideAdornment.cs.
kommandon. I Solution Explorertrycker du på höger pekare på projektnoden. Välj Lägg till | Nytt objekt ... kommando för att lägga till ett nytt visningsobjekt. Välj Utökningsbarhet | VSPackage i det vänstra navigeringsfönstret och välj anpassat kommando i det högra fönstret. Ange namnet ColumnGuideCommands som objektnamn och välj Lägg till. Förutom flera referenser har du även lagt till kommandona och paketet ColumnGuideCommands.cs, ColumnGuideCommandsPackage.csoch ColumnGuideCommandsPackage.vsct. I följande avsnitt ersätter du innehållet i de första och sista filerna för att definiera och implementera kommandona.
Konfigurera lyssnaren för att skapa textvyn
Öppna ColumnGuideAdornmentTextViewCreationListener.cs i redigeraren. Den här koden implementerar en hanterare för varje gång Visual Studio skapar textvyer. Det finns attribut som styr när hanteraren anropas beroende på egenskaperna hos vyn.
Koden måste också deklarera ett utsmyckningslager. När redigeraren uppdaterar vyerna hämtas utsmyckningsskikten för vyn och därifrån hämtas utsmyckningselementen. Du kan deklarera ordningen på lagret i förhållande till andra med attribut. Ersätt följande rad:
[Order(After = PredefinedAdornmentLayers.Caret)]
med dessa två rader:
[Order(Before = PredefinedAdornmentLayers.Text)]
[TextViewRole(PredefinedTextViewRoles.Document)]
Raden som du ersatte finns i en grupp med attribut som deklarerar ett utsmyckningslager. Den första raden som du ändrade ändrar bara var kolumnguideraderna visas. Om du ritar raderna före texten i vyn visas de bakom eller under texten. Den andra raden deklarerar att kolumnguidens utsmyckningar gäller för textentiteter som passar din uppfattning om ett dokument, men du kan deklarera utsmyckningen, till exempel för att endast fungera för redigerbar text. Det finns mer information i språktjänst- och redigeringstilläggspunkter
Implementera inställningshanteraren
Ersätt innehållet i GuidesSettingsManager.cs med följande kod (förklaras nedan):
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace ColumnGuides
{
internal static class GuidesSettingsManager
{
// Because my code is always called from the UI thred, this succeeds.
internal static SettingsManager VsManagedSettingsManager =
new ShellSettingsManager(ServiceProvider.GlobalProvider);
private const int _maxGuides = 5;
private const string _collectionSettingsName = "Text Editor";
private const string _settingName = "Guides";
// 1000 seems reasonable since primary scenario is long lines of code
private const int _maxColumn = 1000;
static internal bool AddGuideline(int column)
{
if (! IsValidColumn(column))
throw new ArgumentOutOfRangeException(
"column",
"The parameter must be between 1 and " + _maxGuides.ToString());
var offsets = GuidesSettingsManager.GetColumnOffsets();
if (offsets.Count() >= _maxGuides)
return false;
// Check for duplicates
if (offsets.Contains(column))
return false;
offsets.Add(column);
WriteSettings(GuidesSettingsManager.GuidelinesColor, offsets);
return true;
}
static internal bool RemoveGuideline(int column)
{
if (!IsValidColumn(column))
throw new ArgumentOutOfRangeException(
"column", "The parameter must be between 1 and 10,000");
var columns = GuidesSettingsManager.GetColumnOffsets();
if (! columns.Remove(column))
{
// Not present. Allow user to remove the last column
// even if they're not on the right column.
if (columns.Count != 1)
return false;
columns.Clear();
}
WriteSettings(GuidesSettingsManager.GuidelinesColor, columns);
return true;
}
static internal bool CanAddGuideline(int column)
{
if (!IsValidColumn(column))
return false;
var offsets = GetColumnOffsets();
if (offsets.Count >= _maxGuides)
return false;
return ! offsets.Contains(column);
}
static internal bool CanRemoveGuideline(int column)
{
if (! IsValidColumn(column))
return false;
// Allow user to remove the last guideline regardless of the column.
// Okay to call count, we limit the number of guides.
var offsets = GuidesSettingsManager.GetColumnOffsets();
return offsets.Contains(column) || offsets.Count() == 1;
}
static internal void RemoveAllGuidelines()
{
WriteSettings(GuidesSettingsManager.GuidelinesColor, new int[0]);
}
private static bool IsValidColumn(int column)
{
// zero is allowed (per user request)
return 0 <= column && column <= _maxColumn;
}
// This has format "RGB(<int>, <int>, <int>) <int> <int>...".
// There can be any number of ints following the RGB part,
// and each int is a column (char offset into line) where to draw.
static private string _guidelinesConfiguration;
static private string GuidelinesConfiguration
{
get
{
if (_guidelinesConfiguration == null)
{
_guidelinesConfiguration =
GetUserSettingsString(
GuidesSettingsManager._collectionSettingsName,
GuidesSettingsManager._settingName)
.Trim();
}
return _guidelinesConfiguration;
}
set
{
if (value != _guidelinesConfiguration)
{
_guidelinesConfiguration = value;
WriteUserSettingsString(
GuidesSettingsManager._collectionSettingsName,
GuidesSettingsManager._settingName, value);
// Notify ColumnGuideAdornments to update adornments in views.
var handler = GuidesSettingsManager.SettingsChanged;
if (handler != null)
handler();
}
}
}
internal static string GetUserSettingsString(string collection, string setting)
{
var store = GuidesSettingsManager
.VsManagedSettingsManager
.GetReadOnlySettingsStore(SettingsScope.UserSettings);
return store.GetString(collection, setting, "RGB(255,0,0) 80");
}
internal static void WriteUserSettingsString(string key, string propertyName,
string value)
{
var store = GuidesSettingsManager
.VsManagedSettingsManager
.GetWritableSettingsStore(SettingsScope.UserSettings);
store.CreateCollection(key);
store.SetString(key, propertyName, value);
}
// Persists settings and sets property with side effect of signaling
// ColumnGuideAdornments to update.
static private void WriteSettings(Color color, IEnumerable<int> columns)
{
string value = ComposeSettingsString(color, columns);
GuidelinesConfiguration = value;
}
private static string ComposeSettingsString(Color color,
IEnumerable<int> columns)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("RGB({0},{1},{2})", color.R, color.G, color.B);
IEnumerator<int> columnsEnumerator = columns.GetEnumerator();
if (columnsEnumerator.MoveNext())
{
sb.AppendFormat(" {0}", columnsEnumerator.Current);
while (columnsEnumerator.MoveNext())
{
sb.AppendFormat(", {0}", columnsEnumerator.Current);
}
}
return sb.ToString();
}
// Parse a color out of a string that begins like "RGB(255,0,0)"
static internal Color GuidelinesColor
{
get
{
string config = GuidelinesConfiguration;
if (!String.IsNullOrEmpty(config) && config.StartsWith("RGB("))
{
int lastParen = config.IndexOf(')');
if (lastParen > 4)
{
string[] rgbs = config.Substring(4, lastParen - 4).Split(',');
if (rgbs.Length >= 3)
{
byte r, g, b;
if (byte.TryParse(rgbs[0], out r) &&
byte.TryParse(rgbs[1], out g) &&
byte.TryParse(rgbs[2], out b))
{
return Color.FromRgb(r, g, b);
}
}
}
}
return Colors.DarkRed;
}
set
{
WriteSettings(value, GetColumnOffsets());
}
}
// Parse a list of integer values out of a string that looks like
// "RGB(255,0,0) 1, 5, 10, 80"
static internal List<int> GetColumnOffsets()
{
var result = new List<int>();
string settings = GuidesSettingsManager.GuidelinesConfiguration;
if (String.IsNullOrEmpty(settings))
return new List<int>();
if (!settings.StartsWith("RGB("))
return new List<int>();
int lastParen = settings.IndexOf(')');
if (lastParen <= 4)
return new List<int>();
string[] columns = settings.Substring(lastParen + 1).Split(',');
int columnCount = 0;
foreach (string columnText in columns)
{
int column = -1;
// VS 2008 gallery extension didn't allow zero, so per user request ...
if (int.TryParse(columnText, out column) && column >= 0)
{
columnCount++;
result.Add(column);
if (columnCount >= _maxGuides)
break;
}
}
return result;
}
// Delegate and Event to fire when settings change so that ColumnGuideAdornments
// can update. We need nothing special in this event since the settings manager
// is statically available.
//
internal delegate void SettingsChangedHandler();
static internal event SettingsChangedHandler SettingsChanged;
}
}
De flesta av den här koden skapar och parsar inställningsformatet: "RGB(<int>,<int>,<int>) <int>, <int>, ...". Heltalen i slutet är de enbaserade kolumner där du vill ha kolumnstödlinjer. Kolumnstödlinjernas tillägg samlar in alla inställningar i en enda inställningsvärdesträng.
Det finns vissa delar av koden som är värda att markera. Följande kodrad hämtar den av Visual Studio hanterade wrappern för inställningslagringen. För det mesta abstraheras detta över Windows-registret, men det här API:et är oberoende av lagringsmekanismen.
internal static SettingsManager VsManagedSettingsManager =
new ShellSettingsManager(ServiceProvider.GlobalProvider);
Visual Studio-inställningslagringen använder en kategoriidentifierare och en inställningsidentifierare för att unikt identifiera alla inställningar:
private const string _collectionSettingsName = "Text Editor";
private const string _settingName = "Guides";
Du behöver inte använda "Text Editor"
som kategorinamn. Du kan välja vad du vill.
De första funktionerna är startpunkterna för att ändra inställningarna. De kontrollerar begränsningar på hög nivå, till exempel maximalt antal tillåtna guider. Sedan anropar de WriteSettings
, som skapar en inställningssträng och anger egenskapen GuideLinesConfiguration
. Om du anger den här egenskapen sparas inställningsvärdet i Visual Studio-inställningsarkivet och händelsen SettingsChanged
utlöses för att uppdatera alla ColumnGuideAdornment
objekt som var och en är associerade med en textvy.
Det finns ett par startpunktsfunktioner, till exempel CanAddGuideline
, som används för att implementera kommandon som ändrar inställningarna. När Visual Studio visar menyer frågar den kommandoimplementeringar för att se om kommandot för närvarande är aktiverat, vad dess namn är och så vidare. Nedan ser du hur du ansluter dessa startpunkter för kommandoimplementeringarna. Mer information om kommandon finns i Utöka menyer och kommandon.
Implementera klassen ColumnGuideAdornment
Klassen ColumnGuideAdornment
instansieras för varje textvy som kan ha utsmyckningar. Den här klassen lyssnar efter händelser om att vyn ändras eller om inställningarna ändras och att kolumnguiderna uppdateras eller ritas om efter behov.
Ersätt innehållet i ColumnGuideAdornment.cs med följande kod (förklaras nedan):
using System;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Editor;
using System.Collections.Generic;
using System.Windows.Shapes;
using Microsoft.VisualStudio.Text.Formatting;
using System.Windows;
namespace ColumnGuides
{
/// <summary>
/// Adornment class, one instance per text view that draws a guides on the viewport
/// </summary>
internal sealed class ColumnGuideAdornment
{
private const double _lineThickness = 1.0;
private IList<Line> _guidelines;
private IWpfTextView _view;
private double _baseIndentation;
private double _columnWidth;
/// <summary>
/// Creates editor column guidelines
/// </summary>
/// <param name="view">The <see cref="IWpfTextView"/> upon
/// which the adornment will be drawn</param>
public ColumnGuideAdornment(IWpfTextView view)
{
_view = view;
_guidelines = CreateGuidelines();
GuidesSettingsManager.SettingsChanged +=
new GuidesSettingsManager.SettingsChangedHandler(SettingsChanged);
view.LayoutChanged +=
new EventHandler<TextViewLayoutChangedEventArgs>(OnViewLayoutChanged);
_view.Closed += new EventHandler(OnViewClosed);
}
void SettingsChanged()
{
_guidelines = CreateGuidelines();
UpdatePositions();
AddGuidelinesToAdornmentLayer();
}
void OnViewClosed(object sender, EventArgs e)
{
_view.LayoutChanged -= OnViewLayoutChanged;
_view.Closed -= OnViewClosed;
GuidesSettingsManager.SettingsChanged -= SettingsChanged;
}
private bool _firstLayoutDone;
void OnViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
bool fUpdatePositions = false;
IFormattedLineSource lineSource = _view.FormattedLineSource;
if (lineSource == null)
{
return;
}
if (_columnWidth != lineSource.ColumnWidth)
{
_columnWidth = lineSource.ColumnWidth;
fUpdatePositions = true;
}
if (_baseIndentation != lineSource.BaseIndentation)
{
_baseIndentation = lineSource.BaseIndentation;
fUpdatePositions = true;
}
if (fUpdatePositions ||
e.VerticalTranslation ||
e.NewViewState.ViewportTop != e.OldViewState.ViewportTop ||
e.NewViewState.ViewportBottom != e.OldViewState.ViewportBottom)
{
UpdatePositions();
}
if (!_firstLayoutDone)
{
AddGuidelinesToAdornmentLayer();
_firstLayoutDone = true;
}
}
private static IList<Line> CreateGuidelines()
{
Brush lineBrush = new SolidColorBrush(GuidesSettingsManager.GuidelinesColor);
DoubleCollection dashArray = new DoubleCollection(new double[] { 1.0, 3.0 });
IList<Line> result = new List<Line>();
foreach (int column in GuidesSettingsManager.GetColumnOffsets())
{
Line line = new Line()
{
// Use the DataContext slot as a cookie to hold the column
DataContext = column,
Stroke = lineBrush,
StrokeThickness = _lineThickness,
StrokeDashArray = dashArray
};
result.Add(line);
}
return result;
}
void UpdatePositions()
{
foreach (Line line in _guidelines)
{
int column = (int)line.DataContext;
line.X2 = _baseIndentation + 0.5 + column * _columnWidth;
line.X1 = line.X2;
line.Y1 = _view.ViewportTop;
line.Y2 = _view.ViewportBottom;
}
}
void AddGuidelinesToAdornmentLayer()
{
// Grab a reference to the adornment layer that this adornment
// should be added to
// Must match exported name in ColumnGuideAdornmentTextViewCreationListener
IAdornmentLayer adornmentLayer =
_view.GetAdornmentLayer("ColumnGuideAdornment");
if (adornmentLayer == null)
return;
adornmentLayer.RemoveAllAdornments();
// Add the guidelines to the adornment layer and make them relative
// to the viewport
foreach (UIElement element in _guidelines)
adornmentLayer.AddAdornment(AdornmentPositioningBehavior.OwnerControlled,
null, null, element, null);
}
}
}
Instanser av den här klassen behåller den associerade IWpfTextView och en lista över Line
-objekt som ritats på vyn.
Konstruktorn (anropas från ColumnGuideAdornmentTextViewCreationListener
när Visual Studio skapar nya vyer) skapar kolumnguiden Line
objekt. Konstruktorn lägger också till hanterare för händelsen SettingsChanged
(definieras i GuidesSettingsManager
) och vyhändelserna LayoutChanged
och Closed
.
Händelsen LayoutChanged
utlöses på grund av flera typer av ändringar i vyn, inklusive när Visual Studio skapar vyn.
OnViewLayoutChanged
-hanteraren anropar AddGuidelinesToAdornmentLayer
för att köra. Koden i OnViewLayoutChanged
avgör om den behöver uppdatera radpositioner baserat på ändringar som ändring av teckenstorlek, visningsrännor, vågrät rullning och så vidare. Koden i UpdatePositions
gör att guiderader ritas mellan tecken eller strax efter den textkolumn som finns i den angivna teckenförskjutningen i textraden.
När inställningarna ändras återskapar funktionen SettingsChanged
bara alla Line
-objekt med de nya inställningarna. När du har angett radpositionerna tar koden bort alla tidigare Line
objekt från ColumnGuideAdornment
utsmyckningsskiktet och lägger till de nya.
Definiera kommandon, menyer och menyplaceringar
Det kan vara mycket att deklarera kommandon och menyer, placera grupper av kommandon eller menyer i olika andra menyer och knyta samman kommandohanterare. Den här genomgången visar hur kommandon fungerar i det här tillägget, men mer information finns i Utöka menyer och kommandon.
Introduktion till koden
Tillägget Kolumnstödlinjer visar hur du deklarerar en grupp kommandon som hör ihop (lägg till kolumn, ta bort kolumn, ändra linjefärg) och sedan placera gruppen på en undermeny i redigerarens snabbmeny. Tillägget Kolumnguider lägger också till kommandona i huvudmenyn Redigera men håller dem osynliga, vilket beskrivs som ett vanligt mönster nedan.
Det finns tre delar i kommandoimplementeringen: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct och ColumnGuideCommands.cs. Koden som genereras av mallarna placerar ett kommando på menyn Tools som visar en dialogruta som implementering. Du kan titta på hur det implementeras i .vsct- och ColumnGuideCommands.cs filer eftersom det är enkelt. Du ersätter koden i dessa filer nedan.
Paketkoden innehåller de pannplåtsdeklarationer som krävs för Visual Studio för att identifiera att tillägget erbjuder kommandon och för att hitta var kommandona ska placeras. När paketet initieras instansierar det kommandoimplementeringsklassen. Mer information om paket som rör kommandon finns i Utöka menyer och kommandon.
Ett vanligt kommandomönster
Kommandona i tillägget Kolumnguider är ett exempel på ett mycket vanligt mönster i Visual Studio. Du placerar relaterade kommandon i en grupp och placerar gruppen på en huvudmeny, ofta med "<CommandFlag>CommandWellOnly</CommandFlag>
" inställd för att göra kommandot osynligt. Genom att placera kommandon på huvudmenyerna (till exempel Redigera) får de fina namn (till exempel Edit.AddColumnGuide), som är användbara för att hitta kommandon när du tilldelar nyckelbindningar igen i Verktygsalternativ. Det är också användbart för att få slutförande när du anropar kommandon från -kommandofönstret.
Sedan lägger du till gruppen med kommandon i snabbmenyer eller undermenyer där du förväntar dig att användaren ska använda kommandona. Visual Studio behandlar CommandWellOnly
som en osynlighetsflagga endast för huvudmenyer. När du placerar samma grupp med kommandon på en snabbmeny eller undermeny visas kommandona.
Som en del av det gemensamma mönstret skapar tillägget Kolumnguider en andra grupp som innehåller en enda undermeny. Undermenyn innehåller i sin tur den första gruppen med guidekommandona med fyra kolumner. Den andra gruppen som innehåller undermenyn är den återanvändbara tillgång som du placerar på olika snabbmenyer, vilket placerar en undermeny på dessa snabbmenyer.
.vsct-filen
Filen .vsct deklarerar kommandona och vart de går, tillsammans med ikoner och så vidare. Ersätt innehållet i filen .vsct med följande kod (förklaras nedan):
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This is the file that defines the actual layout and type of the commands.
It is divided in different sections (e.g. command definition, command
placement, ...), with each defining a specific set of properties.
See the comment before each section for more details about how to
use it. -->
<!-- The VSCT compiler (the tool that translates this file into the binary
format that VisualStudio will consume) has the ability to run a preprocessor
on the vsct file; this preprocessor is (usually) the C++ preprocessor, so
it is possible to define includes and macros with the same syntax used
in C++ files. Using this ability of the compiler here, we include some files
defining some of the constants that we will use inside the file. -->
<!--This is the file that defines the IDs for all the commands exposed by
VisualStudio. -->
<Extern href="stdidcmd.h"/>
<!--This header contains the command ids for the menus provided by the shell. -->
<Extern href="vsshlids.h"/>
<!--The Commands section is where commands, menus, and menu groups are defined.
This section uses a Guid to identify the package that provides the command
defined inside it. -->
<Commands package="guidColumnGuideCommandsPkg">
<!-- Inside this section we have different sub-sections: one for the menus, another
for the menu groups, one for the buttons (the actual commands), one for the combos
and the last one for the bitmaps used. Each element is identified by a command id
that is a unique pair of guid and numeric identifier; the guid part of the identifier
is usually called "command set" and is used to group different command inside a
logically related group; your package should define its own command set in order to
avoid collisions with command ids defined by other packages. -->
<!-- In this section you can define new menu groups. A menu group is a container for
other menus or buttons (commands); from a visual point of view you can see the
group as the part of a menu contained between two lines. The parent of a group
must be a menu. -->
<Groups>
<!-- The main group is parented to the edit menu. All the buttons within the group
have the "CommandWellOnly" flag, so they're actually invisible, but it means
they get canonical names that begin with "Edit". Using placements, the group
is also placed in the GuidesSubMenu group. -->
<!-- The priority 0xB801 is chosen so it goes just after
IDG_VS_EDIT_COMMANDWELL -->
<Group guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0xB801">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
</Group>
<!-- Group for holding the "Guidelines" sub-menu anchor (the item on the menu that
drops the sub menu). The group is parented to
the context menu for code windows. That takes care of most editors, but it's
also placed in a couple of other windows using Placements -->
<Group guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN" />
</Group>
</Groups>
<Menus>
<Menu guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" priority="0x1000"
type="Menu">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup" />
<Strings>
<ButtonText>&Column Guides</ButtonText>
</Strings>
</Menu>
</Menus>
<!--Buttons section. -->
<!--This section defines the elements the user can interact with, like a menu command or a button
or combo box in a toolbar. -->
<Buttons>
<!--To define a menu group you have to specify its ID, the parent menu and its
display priority.
The command is visible and enabled by default. If you need to change the
visibility, status, etc, you can use the CommandFlag node.
You can add more than one CommandFlag node e.g.:
<CommandFlag>DefaultInvisible</CommandFlag>
<CommandFlag>DynamicVisibility</CommandFlag>
If you do not want an image next to your command, remove the Icon node or
set it to <Icon guid="guidOfficeIcon" id="msotcidNoIcon" /> -->
<Button guid="guidColumnGuidesCommandSet" id="cmdidAddColumnGuide"
priority="0x0100" type="Button">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
<Icon guid="guidImages" id="bmpPicAddGuide" />
<CommandFlag>CommandWellOnly</CommandFlag>
<CommandFlag>AllowParams</CommandFlag>
<Strings>
<ButtonText>&Add Column Guide</ButtonText>
</Strings>
</Button>
<Button guid="guidColumnGuidesCommandSet" id="cmdidRemoveColumnGuide"
priority="0x0101" type="Button">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
<Icon guid="guidImages" id="bmpPicRemoveGuide" />
<CommandFlag>CommandWellOnly</CommandFlag>
<CommandFlag>AllowParams</CommandFlag>
<Strings>
<ButtonText>&Remove Column Guide</ButtonText>
</Strings>
</Button>
<Button guid="guidColumnGuidesCommandSet" id="cmdidChooseGuideColor"
priority="0x0103" type="Button">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
<Icon guid="guidImages" id="bmpPicChooseColor" />
<CommandFlag>CommandWellOnly</CommandFlag>
<Strings>
<ButtonText>Column Guide &Color...</ButtonText>
</Strings>
</Button>
<Button guid="guidColumnGuidesCommandSet" id="cmdidRemoveAllColumnGuides"
priority="0x0102" type="Button">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
<CommandFlag>CommandWellOnly</CommandFlag>
<Strings>
<ButtonText>Remove A&ll Columns</ButtonText>
</Strings>
</Button>
</Buttons>
<!--The bitmaps section is used to define the bitmaps that are used for the
commands.-->
<Bitmaps>
<!-- The bitmap id is defined in a way that is a little bit different from the
others:
the declaration starts with a guid for the bitmap strip, then there is the
resource id of the bitmap strip containing the bitmaps and then there are
the numeric ids of the elements used inside a button definition. An important
aspect of this declaration is that the element id
must be the actual index (1-based) of the bitmap inside the bitmap strip. -->
<Bitmap guid="guidImages" href="Resources\ColumnGuideCommands.png"
usedList="bmpPicAddGuide, bmpPicRemoveGuide, bmpPicChooseColor" />
</Bitmaps>
</Commands>
<CommandPlacements>
<!-- Define secondary placements for our groups -->
<!-- Place the group containing the three commands in the sub-menu -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0x0100">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" />
</CommandPlacement>
<!-- The HTML editor context menu, for some reason, redefines its own groups
so we need to place a copy of our context menu there too. -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x1001">
<Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_HTML" />
</CommandPlacement>
<!-- The HTML context menu in Dev12 changed. -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x1001">
<Parent guid="CMDSETID_HtmEdGrp_Dev12" id="IDMX_HTM_SOURCE_HTML_Dev12" />
</CommandPlacement>
<!-- Similarly for Script -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x1001">
<Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_SCRIPT" />
</CommandPlacement>
<!-- Similarly for ASPX -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x1001">
<Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_ASPX" />
</CommandPlacement>
<!-- Similarly for the XAML editor context menu -->
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x0600">
<Parent guid="guidXamlUiCmds" id="IDM_XAML_EDITOR" />
</CommandPlacement>
</CommandPlacements>
<!-- This defines the identifiers and their values used above to index resources
and specify commands. -->
<Symbols>
<!-- This is the package guid. -->
<GuidSymbol name="guidColumnGuideCommandsPkg"
value="{e914e5de-0851-4904-b361-1a3a9d449704}" />
<!-- This is the guid used to group the menu commands together -->
<GuidSymbol name="guidColumnGuidesCommandSet"
value="{c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e}">
<IDSymbol name="GuidesContextMenuGroup" value="0x1020" />
<IDSymbol name="GuidesMenuItemsGroup" value="0x1021" />
<IDSymbol name="GuidesSubMenu" value="0x1022" />
<IDSymbol name="cmdidAddColumnGuide" value="0x0100" />
<IDSymbol name="cmdidRemoveColumnGuide" value="0x0101" />
<IDSymbol name="cmdidChooseGuideColor" value="0x0102" />
<IDSymbol name="cmdidRemoveAllColumnGuides" value="0x0103" />
</GuidSymbol>
<GuidSymbol name="guidImages" value="{2C99F852-587C-43AF-AA2D-F605DE2E46EF}">
<IDSymbol name="bmpPicAddGuide" value="1" />
<IDSymbol name="bmpPicRemoveGuide" value="2" />
<IDSymbol name="bmpPicChooseColor" value="3" />
</GuidSymbol>
<GuidSymbol name="CMDSETID_HtmEdGrp_Dev12"
value="{78F03954-2FB8-4087-8CE7-59D71710B3BB}">
<IDSymbol name="IDMX_HTM_SOURCE_HTML_Dev12" value="0x1" />
</GuidSymbol>
<GuidSymbol name="CMDSETID_HtmEdGrp" value="{d7e8c5e1-bdb8-11d0-9c88-0000f8040a53}">
<IDSymbol name="IDMX_HTM_SOURCE_HTML" value="0x33" />
<IDSymbol name="IDMX_HTM_SOURCE_SCRIPT" value="0x34" />
<IDSymbol name="IDMX_HTM_SOURCE_ASPX" value="0x35" />
</GuidSymbol>
<GuidSymbol name="guidXamlUiCmds" value="{4c87b692-1202-46aa-b64c-ef01faec53da}">
<IDSymbol name="IDM_XAML_EDITOR" value="0x103" />
</GuidSymbol>
</Symbols>
</CommandTable>
GUIDS. För att Visual Studio ska kunna hitta dina kommandohanterare och anropa dem måste du se till att det paket-GUID som deklareras i ColumnGuideCommandsPackage.cs -filen (genereras från projektobjektmallen) matchar det paket-GUID som deklarerats i .vsct- fil (kopierad ovan). Om du återanvänder den här exempelkoden bör du se till att du har ett annat GUID så att du inte står i konflikt med någon annan som kan ha kopierat den här koden.
Hitta den här raden i ColumnGuideCommandsPackage.cs och kopiera GUID mellan citattecknen:
public const string PackageGuidString = "ef726849-5447-4f73-8de5-01b9e930f7cd";
Klistra sedan in GUID i filen .vsct så att du har följande rad i dina Symbols
-deklarationer:
<GuidSymbol name="guidColumnGuideCommandsPkg"
value="{ef726849-5447-4f73-8de5-01b9e930f7cd}" />
GUID:erna för kommandouppsättningen och bitmappsavbildningsfilen bör också vara unika för dina tillägg:
<GuidSymbol name="guidColumnGuidesCommandSet"
value="{c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e}">
<GuidSymbol name="guidImages" value="{2C99F852-587C-43AF-AA2D-F605DE2E46EF}">
Men du behöver inte ändra kommandouppsättningen och bitmappsbildens GUID i den här genomgången för att få koden att fungera. Kommandouppsättningens GUID måste matcha deklarationen i ColumnGuideCommands.cs-filen, men du ersätter även innehållet i filen. Därför matchar GUID:erna.
Andra GUID:er i .vsct- fil identifierar befintliga menyer som kolumnguidekommandona läggs till i, så att de aldrig ändras.
Filavsnitt. .vsct har tre yttre avsnitt: kommandon, placeringar och symboler. Avsnittet kommandon definierar kommandogrupper, menyer, knappar eller menyalternativ och bitmappar för ikoner. Avsnittet om placeringar deklarerar var grupper placeras på menyer eller ytterligare placeringar på befintliga menyer. I symbolavsnittet deklareras identifierare som används någon annanstans i filen .vsct, vilket gör .vsct kod mer läsbar än att ha GUID och hexnummer överallt.
avsnittet Kommandon grupperar definitioner. Avsnittet kommandon definierar först kommandogrupper. Grupper av kommandon är kommandon som visas i menyer med små grå linjer som avgränsar grupperna. En grupp kan också fylla en hel undermeny, som i det här exemplet, och du ser inte de grå avgränsande raderna i det här fallet.
.vsct--filer deklarerar två grupper, GuidesMenuItemsGroup
som är överordnad till IDM_VS_MENU_EDIT
(huvudmenyn Redigera) och GuidesContextMenuGroup
som är överordnad till IDM_VS_CTXT_CODEWIN
(kodredigerarens snabbmeny).
Den andra gruppdeklarationen har prioritet 0x0600
:
<Group guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x0600">
Tanken är att placera undermenyn för kolumnguider i slutet av en snabbmeny som du lägger till undermenygruppen till. Men du bör inte anta att du vet bäst och tvinga undermenyn att alltid vara sist med hjälp av en prioritet på 0xFFFF
. Du måste experimentera med talet för att se var undermenyn finns på snabbmenyerna där du placerar den. I det här fallet är 0x0600
tillräckligt högt för att placera det i slutet av menyerna så långt du kan se, men det lämnar utrymme för någon annan att utforma sitt tillägg så att det är lägre än kolumnstödlinjernas tillägg om det är önskvärt.
avsnittet Kommandon, menydefinition. Därefter definierar kommandoavsnittet undermenyn GuidesSubMenu
, överordnad till GuidesContextMenuGroup
.
GuidesContextMenuGroup
är den grupp som du lägger till i alla relevanta snabbmenyer. I avsnittet placeringar placerar koden gruppen med guidekommandona med fyra kolumner på den här undermenyn.
Kommandonavsnitt, knappdefinitioner. Kommandoavsnittet definierar sedan menyalternativen eller knapparna som är kommandona för guider med fyra kolumner.
CommandWellOnly
, som beskrivs ovan, innebär att kommandona är osynliga när de placeras på en huvudmeny. Två av knappdeklarationerna för menyalternativ (lägg till guide och ta bort guide) har också en AllowParams
flagga:
<CommandFlag>AllowParams</CommandFlag>
Med den här flaggan aktiveras, tillsammans med placeringar i huvudmenyn, möjligheten att ta emot argument när Visual Studio anropar kommandohanteraren. Om användaren kör kommandot från kommandofönstret skickas argumentet till kommandohanteraren i händelseargumenten.
kommandoavsnitt, bitmappsdefinitioner. Slutligen deklarerar avsnittet kommandon de bitmappar eller ikoner som används för kommandona. Det här avsnittet är en enkel deklaration som identifierar projektresursen och visar en lista över en-baserade index för använda ikoner. Symbolavsnittet i filen .vsct deklarerar värdena för de identifierare som används som index. Den här genomgången använder sig av den bitmappsremsa som medföljer med mallen för anpassade kommandoposter som har lagts till i projektet.
Avsnittet Placeringar. Efter kommandoavsnittet kommer avsnittet för placeringar. Den första är när koden lägger till den första gruppen som beskrivs ovan som innehåller guidekommandona med fyra kolumner i undermenyn där kommandona visas:
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0x0100">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" />
</CommandPlacement>
Alla andra placeringar lägger till GuidesContextMenuGroup
(som innehåller GuidesSubMenu
) i andra snabbmenyer för redigeraren. När koden deklarerade GuidesContextMenuGroup
blev den kopplad till kodredigerarens kontextmeny. Det är därför du inte ser någon placering för kodredigerarens snabbmeny.
avsnittet Symboler. Som nämnts ovan deklarerar symbolavsnittet identifierare som används någon annanstans i filen .vsct, vilket gör .vsct- kod mer läsbar än att ha GUID och hexnummer överallt. De viktiga punkterna i det här avsnittet är att paket-GUID måste överensstämma med deklarationen i paketklassen. Och kommandouppsättningens GUID måste överensstämma med deklarationen i kommandoimplementeringsklassen.
Implementera kommandona
Filen ColumnGuideCommands.cs implementerar kommandona och ansluter hanterarna. När Visual Studio läser in paketet och initierar det anropar paketet i sin tur Initialize
på kommandoimplementeringsklassen. Initieringen av kommandon instansierar helt enkelt klassen, och konstruktorn ansluter alla kommandohanterare.
Ersätt innehållet i ColumnGuideCommands.cs-filen med följande kod (förklaras nedan):
using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio;
namespace ColumnGuides
{
/// <summary>
/// Command handler
/// </summary>
internal sealed class ColumnGuideCommands
{
const int cmdidAddColumnGuide = 0x0100;
const int cmdidRemoveColumnGuide = 0x0101;
const int cmdidChooseGuideColor = 0x0102;
const int cmdidRemoveAllColumnGuides = 0x0103;
/// <summary>
/// Command menu group (command set GUID).
/// </summary>
static readonly Guid CommandSet =
new Guid("c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e");
/// <summary>
/// VS Package that provides this command, not null.
/// </summary>
private readonly Package package;
OleMenuCommand _addGuidelineCommand;
OleMenuCommand _removeGuidelineCommand;
/// <summary>
/// Initializes the singleton instance of the command.
/// </summary>
/// <param name="package">Owner package, not null.</param>
public static void Initialize(Package package)
{
Instance = new ColumnGuideCommands(package);
}
/// <summary>
/// Gets the instance of the command.
/// </summary>
public static ColumnGuideCommands Instance
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="ColumnGuideCommands"/> class.
/// Adds our command handlers for menu (commands must exist in the command
/// table file)
/// </summary>
/// <param name="package">Owner package, not null.</param>
private ColumnGuideCommands(Package package)
{
if (package == null)
{
throw new ArgumentNullException("package");
}
this.package = package;
// Add our command handlers for menu (commands must exist in the .vsct file)
OleMenuCommandService commandService =
this.ServiceProvider.GetService(typeof(IMenuCommandService))
as OleMenuCommandService;
if (commandService != null)
{
// Add guide
_addGuidelineCommand =
new OleMenuCommand(AddColumnGuideExecuted, null,
AddColumnGuideBeforeQueryStatus,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidAddColumnGuide));
_addGuidelineCommand.ParametersDescription = "<column>";
commandService.AddCommand(_addGuidelineCommand);
// Remove guide
_removeGuidelineCommand =
new OleMenuCommand(RemoveColumnGuideExecuted, null,
RemoveColumnGuideBeforeQueryStatus,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidRemoveColumnGuide));
_removeGuidelineCommand.ParametersDescription = "<column>";
commandService.AddCommand(_removeGuidelineCommand);
// Choose color
commandService.AddCommand(
new MenuCommand(ChooseGuideColorExecuted,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidChooseGuideColor)));
// Remove all
commandService.AddCommand(
new MenuCommand(RemoveAllGuidelinesExecuted,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidRemoveAllColumnGuides)));
}
}
/// <summary>
/// Gets the service provider from the owner package.
/// </summary>
private IServiceProvider ServiceProvider
{
get
{
return this.package;
}
}
private void AddColumnGuideBeforeQueryStatus(object sender, EventArgs e)
{
int currentColumn = GetCurrentEditorColumn();
_addGuidelineCommand.Enabled =
GuidesSettingsManager.CanAddGuideline(currentColumn);
}
private void RemoveColumnGuideBeforeQueryStatus(object sender, EventArgs e)
{
int currentColumn = GetCurrentEditorColumn();
_removeGuidelineCommand.Enabled =
GuidesSettingsManager.CanRemoveGuideline(currentColumn);
}
private int GetCurrentEditorColumn()
{
IVsTextView view = GetActiveTextView();
if (view == null)
{
return -1;
}
try
{
IWpfTextView textView = GetTextViewFromVsTextView(view);
int column = GetCaretColumn(textView);
// Note: GetCaretColumn returns 0-based positions. Guidelines are 1-based
// positions.
// However, do not subtract one here since the caret is positioned to the
// left of
// the given column and the guidelines are positioned to the right. We
// want the
// guideline to line up with the current caret position. e.g. When the
// caret is
// at position 1 (zero-based), the status bar says column 2. We want to
// add a
// guideline for column 1 since that will place the guideline where the
// caret is.
return column;
}
catch (InvalidOperationException)
{
return -1;
}
}
/// <summary>
/// Find the active text view (if any) in the active document.
/// </summary>
/// <returns>The IVsTextView of the active view, or null if there is no active
/// document or the
/// active view in the active document is not a text view.</returns>
private IVsTextView GetActiveTextView()
{
IVsMonitorSelection selection =
this.ServiceProvider.GetService(typeof(IVsMonitorSelection))
as IVsMonitorSelection;
object frameObj = null;
ErrorHandler.ThrowOnFailure(
selection.GetCurrentElementValue(
(uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out frameObj));
IVsWindowFrame frame = frameObj as IVsWindowFrame;
if (frame == null)
{
return null;
}
return GetActiveView(frame);
}
private static IVsTextView GetActiveView(IVsWindowFrame windowFrame)
{
if (windowFrame == null)
{
throw new ArgumentException("windowFrame");
}
object pvar;
ErrorHandler.ThrowOnFailure(
windowFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out pvar));
IVsTextView textView = pvar as IVsTextView;
if (textView == null)
{
IVsCodeWindow codeWin = pvar as IVsCodeWindow;
if (codeWin != null)
{
ErrorHandler.ThrowOnFailure(codeWin.GetLastActiveView(out textView));
}
}
return textView;
}
private static IWpfTextView GetTextViewFromVsTextView(IVsTextView view)
{
if (view == null)
{
throw new ArgumentNullException("view");
}
IVsUserData userData = view as IVsUserData;
if (userData == null)
{
throw new InvalidOperationException();
}
object objTextViewHost;
if (VSConstants.S_OK
!= userData.GetData(Microsoft.VisualStudio
.Editor
.DefGuidList.guidIWpfTextViewHost,
out objTextViewHost))
{
throw new InvalidOperationException();
}
IWpfTextViewHost textViewHost = objTextViewHost as IWpfTextViewHost;
if (textViewHost == null)
{
throw new InvalidOperationException();
}
return textViewHost.TextView;
}
/// <summary>
/// Given an IWpfTextView, find the position of the caret and report its column
/// number. The column number is 0-based
/// </summary>
/// <param name="textView">The text view containing the caret</param>
/// <returns>The column number of the caret's position. When the caret is at the
/// leftmost column, the return value is zero.</returns>
private static int GetCaretColumn(IWpfTextView textView)
{
// This is the code the editor uses to populate the status bar.
Microsoft.VisualStudio.Text.Formatting.ITextViewLine caretViewLine =
textView.Caret.ContainingTextViewLine;
double columnWidth = textView.FormattedLineSource.ColumnWidth;
return (int)(Math.Round((textView.Caret.Left - caretViewLine.Left)
/ columnWidth));
}
/// <summary>
/// Determine the applicable column number for an add or remove command.
/// The column is parsed from command arguments, if present. Otherwise
/// the current position of the caret is used to determine the column.
/// </summary>
/// <param name="e">Event args passed to the command handler.</param>
/// <returns>The column number. May be negative to indicate the column number is
/// unavailable.</returns>
/// <exception cref="ArgumentException">The column number parsed from event args
/// was not a valid integer.</exception>
private int GetApplicableColumn(EventArgs e)
{
var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
if (!string.IsNullOrEmpty(inValue))
{
int column;
if (!int.TryParse(inValue, out column) || column < 0)
throw new ArgumentException("Invalid column");
return column;
}
return GetCurrentEditorColumn();
}
/// <summary>
/// This function is the callback used to execute a command when a menu item
/// is clicked. See the Initialize method to see how the menu item is associated
/// to this function using the OleMenuCommandService service and the MenuCommand
/// class.
/// </summary>
private void AddColumnGuideExecuted(object sender, EventArgs e)
{
int column = GetApplicableColumn(e);
if (column >= 0)
{
GuidesSettingsManager.AddGuideline(column);
}
}
private void RemoveColumnGuideExecuted(object sender, EventArgs e)
{
int column = GetApplicableColumn(e);
if (column >= 0)
{
GuidesSettingsManager.RemoveGuideline(column);
}
}
private void RemoveAllGuidelinesExecuted(object sender, EventArgs e)
{
GuidesSettingsManager.RemoveAllGuidelines();
}
private void ChooseGuideColorExecuted(object sender, EventArgs e)
{
System.Windows.Media.Color color = GuidesSettingsManager.GuidelinesColor;
using (System.Windows.Forms.ColorDialog picker =
new System.Windows.Forms.ColorDialog())
{
picker.Color = System.Drawing.Color.FromArgb(255, color.R, color.G,
color.B);
if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
GuidesSettingsManager.GuidelinesColor =
System.Windows.Media.Color.FromRgb(picker.Color.R,
picker.Color.G,
picker.Color.B);
}
}
}
}
}
Åtgärda referenser. Du saknar en referens just nu. Tryck på höger pekare på noden Referenser i Solution Explorer. Välj kommandot Lägg till .... Dialogrutan Lägg till referens har en sökruta i det övre högra hörnet. Ange "editor" (utan dubbla citattecken). Välj objektet Microsoft.VisualStudio.Editor (du måste markera kryssrutan till vänster om objektet, inte bara markera objektet) och välj OK för att lägga till referensen.
Initiering. När paketklassen initieras anropas Initialize
på kommandoimplementeringsklassen.
ColumnGuideCommands
-initieringen instansierar klassen och sparar klassens instans och paketreferensen som klassmedlemmar.
Nu ska vi titta på en av kommandohanterarkopplingarna från klasskonstruktorn:
_addGuidelineCommand =
new OleMenuCommand(AddColumnGuideExecuted, null,
AddColumnGuideBeforeQueryStatus,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidAddColumnGuide));
Du skapar en OleMenuCommand
. Visual Studio använder Microsoft Office-kommandosystemet. De viktigaste argumenten när du instansierar en OleMenuCommand
är funktionen som implementerar kommandot (AddColumnGuideExecuted
), funktionen som ska anropas när Visual Studio visar en meny med kommandot (AddColumnGuideBeforeQueryStatus
) och kommando-ID. Visual Studio anropar frågestatusfunktionen innan du visar ett kommando på en meny så att kommandot kan göra sig osynligt eller nedtonat för en viss visning av menyn (till exempel inaktivera Kopiera om det inte finns någon markering), ändra dess ikon eller till och med ändra dess namn (till exempel från Lägg till något för att ta bort något), och så vidare. Kommando-ID:t måste matcha ett kommando-ID som deklarerats i filen .vsct. Strängarna för kommandouppsättningen och kommandot för att lägga till kolumnguider måste matcha mellan filen .vsct och ColumnGuideCommands.cs.
Följande rad ger hjälp när användare anropar kommandot via kommandofönstret (förklaras nedan):
_addGuidelineCommand.ParametersDescription = "<column>";
Frågestatus. Frågestatusfunktionerna AddColumnGuideBeforeQueryStatus
och RemoveColumnGuideBeforeQueryStatus
kontrollera vissa inställningar (till exempel maximalt antal guider eller maxkolumn) eller om det finns en kolumnguide att ta bort. De aktiverar kommandona om villkoren är rätt. Frågestatusfunktioner måste vara effektiva eftersom de körs varje gång Visual Studio visar en meny och för varje kommando på menyn.
AddColumnGuideExecuted-funktionen. Den intressanta delen av att lägga till en guide är att räkna ut den nuvarande redigeringsvyn och markörens plats. Först anropar den här funktionen GetApplicableColumn
, som kontrollerar om det finns ett argument från användaren i kommandohanterarens händelseargument, och om det inte finns något kontrollerar funktionen redigerarens vy:
private int GetApplicableColumn(EventArgs e)
{
var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
if (!string.IsNullOrEmpty(inValue))
{
int column;
if (!int.TryParse(inValue, out column) || column < 0)
throw new ArgumentException("Invalid column");
return column;
}
return GetCurrentEditorColumn();
}
GetCurrentEditorColumn
måste gräva lite för att få en IWpfTextView vy över koden. Om du spårar genom GetActiveTextView
, GetActiveView
och GetTextViewFromVsTextView
kan du se hur du gör det. Följande kod är den relevanta koden abstraherad: först börjar den med den aktuella markeringen, sedan hämtas markeringens ram för att därefter hämta ramens DocView som en IVsTextView, härefter erhålls en IVsUserData från IVsTextView, och därefter fås en vyvärd, och till sist erhålls IWpfTextView.
IVsMonitorSelection selection =
this.ServiceProvider.GetService(typeof(IVsMonitorSelection))
as IVsMonitorSelection;
object frameObj = null;
ErrorHandler.ThrowOnFailure(selection.GetCurrentElementValue(
(uint)VSConstants.VSSELELEMID.SEID_DocumentFrame,
out frameObj));
IVsWindowFrame frame = frameObj as IVsWindowFrame;
if (frame == null)
<<do nothing>>;
...
object pvar;
ErrorHandler.ThrowOnFailure(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView,
out pvar));
IVsTextView textView = pvar as IVsTextView;
if (textView == null)
{
IVsCodeWindow codeWin = pvar as IVsCodeWindow;
if (codeWin != null)
{
ErrorHandler.ThrowOnFailure(codeWin.GetLastActiveView(out textView));
}
}
...
if (textView == null)
<<do nothing>>
IVsUserData userData = textView as IVsUserData;
if (userData == null)
<<do nothing>>
object objTextViewHost;
if (VSConstants.S_OK
!= userData.GetData(Microsoft.VisualStudio.Editor.DefGuidList
.guidIWpfTextViewHost,
out objTextViewHost))
{
<<do nothing>>
}
IWpfTextViewHost textViewHost = objTextViewHost as IWpfTextViewHost;
if (textViewHost == null)
<<do nothing>>
IWpfTextView textView = textViewHost.TextView;
När du har en IWpfTextView kan du hämta kolumnen där markören är:
private static int GetCaretColumn(IWpfTextView textView)
{
// This is the code the editor uses to populate the status bar.
Microsoft.VisualStudio.Text.Formatting.ITextViewLine caretViewLine =
textView.Caret.ContainingTextViewLine;
double columnWidth = textView.FormattedLineSource.ColumnWidth;
return (int)(Math.Round((textView.Caret.Left - caretViewLine.Left)
/ columnWidth));
}
Med den aktuella kolumnen i handen där användaren klickade anropar koden bara inställningshanteraren för att lägga till eller ta bort kolumnen. Inställningshanteraren utlöser händelsen som alla ColumnGuideAdornment
objekt lyssnar på. När händelsen utlöses uppdaterar dessa objekt sina associerade textvyer med nya kolumnguideinställningar.
Anropa kommandot från kommandofönstret
Exemplet med kolumnguider gör det möjligt för användare att anropa två kommandon från kommandofönstret som en form av utökningsbarhet. Om du använder kommandot View | Other Windows | Command Window kan du se Kommandofönstret. Du kan interagera med kommandofönstret genom att ange "edit.", och med kommandonamnet slutfört och anger argumentet 120 får du följande resultat:
> Edit.AddColumnGuide 120
>
De delar av exemplet som aktiverar det här beteendet finns i .vsct fildeklarationer, ColumnGuideCommands
-klasskonstruktorn när den ansluter kommandohanterare och de kommandohanterarimplementeringar som kontrollerar händelseargument.
Du såg "<CommandFlag>CommandWellOnly</CommandFlag>
" i filen .vsct samt placeringar i Redigera huvudmenyn trots att kommandona inte visas i Redigera-menygränssnittet. Att ha dem på huvudmenyn Redigera ger dem namn såsom Edit.AddColumnGuide. Kommandogruppens deklaration som innehåller de fyra kommandona placerade gruppen på menyn Redigera direkt:
<Group guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0xB801">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
</Group>
Knapparnas avsnitt deklarerade senare kommandona CommandWellOnly
för att hålla dem osynliga på huvudmenyn och markerade dem med AllowParams
.
<Button guid="guidColumnGuidesCommandSet" id="cmdidAddColumnGuide"
priority="0x0100" type="Button">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup" />
<Icon guid="guidImages" id="bmpPicAddGuide" />
<CommandFlag>CommandWellOnly</CommandFlag>
<CommandFlag>AllowParams</CommandFlag>
Du såg att kommandohanterarens kod i ColumnGuideCommands
-klasskonstruktorn gav en beskrivning av den tillåtna parametern:
_addGuidelineCommand.ParametersDescription = "<column>";
Du såg att GetApplicableColumn
-funktionen kontrollerar OleMenuCmdEventArgs
för ett värde innan den kontrollerar redigerarens vy för en aktuell kolumn.
private int GetApplicableColumn(EventArgs e)
{
var inValue = ((OleMenuCmdEventArgs)e).InValue as string;
if (!string.IsNullOrEmpty(inValue))
{
int column;
if (!int.TryParse(inValue, out column) || column < 0)
throw new ArgumentException("Invalid column");
return column;
}
Prova ditt tillägg
Du kan nu trycka på F5- för att köra tillägget Kolumnguider. Öppna en textfil och använd redigerarens snabbmeny för att lägga till guiderader, ta bort dem och ändra deras färg. Klicka i texten (inte utrymmet efter slutet av raden) för att lägga till en kolumnguide, eller så lägger redigeraren till den i den sista kolumnen på raden. Om du använder kommandofönstret och anropar kommandona med ett argument kan du lägga till kolumnguider var som helst.
Om du vill prova olika kommandoplaceringar, ändra namn, ändra ikoner och så vidare, och du har problem med att Visual Studio visar den senaste koden i menyerna, kan du återställa den experimentella grenen där du felsöker. Ta upp Windows Start-menyn och skriv "reset". Leta efter och kör kommandot Återställ den nästa experimentella Visual Studio-instansen. Det här kommandot rensar den experimentella registerdatafilen för alla tilläggskomponenter. Det rensar inte inställningarna från komponenter, så alla guider som du hade när du stängde av Visual Studio experimentella hive finns fortfarande kvar när koden läser inställningsarkivet vid nästa start.
Färdigt kodprojekt
Det kommer snart att finnas ett GitHub-projekt med Visual Studio Extensibility-exempel, och det slutförda projektet kommer att finnas där. Den här artikeln kommer att uppdateras så att den pekar där när det händer. Det färdiga exempelprojektet kan ha olika GUID:er och kommer att ha en annan bitmappsremsa för kommandoikonerna.
Du kan prova en version av funktionen för kolumnguider med det här Visual Studio-galleriet -tillägget.