Overzicht: Een weergaveversiering, opdrachten en instellingen (kolomhulplijnen) maken
U kunt de Tekst-/code-editor van Visual Studio uitbreiden met opdrachten en effecten weergeven. In dit artikel leest u hoe u aan de slag gaat met een populaire extensiefunctie, kolomhandleidingen. Kolomhulplijnen zijn visueel lichte lijnen die zijn getekend in de weergave van de teksteditor om u te helpen bij het beheren van uw code voor specifieke kolombreedten. Met name opgemaakte code kan belangrijk zijn voor voorbeelden die u opneemt in documenten, blogberichten of foutrapporten.
In deze procedure gaat u het volgende doen:
Een VSIX-project maken
Een versiering van de editorweergave toevoegen
Ondersteuning toevoegen voor het opslaan en verkrijgen van instellingen (waar kolomhulplijnen en de bijbehorende kleur moeten worden getekend)
Opdrachten toevoegen (kolomhulplijnen toevoegen/verwijderen, de kleur ervan wijzigen)
Plaats de opdrachten in het menu Bewerken en contextmenu's voor tekstdocument
Ondersteuning toevoegen voor het aanroepen van de opdrachten vanuit het Visual Studio-opdrachtvenster
U kunt een versie van de functie voor kolomhulplijnen uitproberen met deze Visual Studio Gallery extensie.
Notitie
In dit scenario plakt u een grote hoeveelheid code in een aantal bestanden die zijn gegenereerd door Visual Studio-extensiesjablonen. Maar binnenkort zal deze handleiding verwijzen naar een voltooide oplossing op GitHub met andere extensievoorbeelden. De voltooide code is enigszins anders omdat deze echte opdrachtpictogrammen bevat in plaats van generictemplate-pictogrammen te gebruiken.
De oplossing instellen
Eerst maakt u een VSIX-project, voegt u een versiering voor de editorweergave toe, en voegt u vervolgens een opdracht toe (waarmee een VSPackage wordt toegevoegd om de opdracht te bevatten). De basisarchitectuur is als volgt:
U hebt een listener voor het aanmaken van een tekstweergave die per weergave een
ColumnGuideAdornment
object aanmaakt. Dit object luistert naar gebeurtenissen over het wijzigen van de weergave of het wijzigen van instellingen, het bijwerken of opnieuw tekenen van kolomhulplijnen, indien nodig.Er is een
GuidesSettingsManager
die het lezen en schrijven van de Visual Studio-instellingenopslag afhandelt. De instellingenbeheerder heeft ook bewerkingen voor het bijwerken van de instellingen die ondersteuning bieden voor de gebruikersopdrachten (kolom toevoegen, kolom verwijderen, kleur wijzigen).Er is een VSIP-pakket dat nodig is als u gebruikersopdrachten hebt, maar het is gewoon standaardcode waarmee het implementatieobject voor opdrachten wordt geïnitialiseerd.
Er is een
ColumnGuideCommands
-object dat de gebruikersopdrachten uitvoert en de opdrachthandlers koppelt voor opdrachten die zijn gedeclareerd in het bestand .vsct.VSIX. -bestand gebruiken | Nieuwe opdracht ... om een project te maken. Kies het knooppunt Uitbreidbaarheid onder C# in het linkernavigatiedeelvenster en kies VSIX Project in het rechterdeelvenster. Voer de naam ColumnGuides in en kies OK om het project te maken.
weergeven. Druk op de rechterknop op het projectknooppunt in Solution Explorer. Kies de Toevoegen | Nieuw item ... opdracht om een nieuw weergave sieritem toe te voegen. Kies Uitbreidbaarheid | Editor in het linkernavigatiedeelvenster en kies editorweergave in het rechterdeelvenster. Voer de naam ColumnGuideAdornment in als itemnaam en kies Toevoegen om deze toe te voegen.
U ziet dat deze itemsjabloon twee bestanden heeft toegevoegd aan het project (evenals verwijzingen, enzovoort): ColumnGuideAdornment.cs en ColumnGuideAdornmentTextViewCreationListener.cs. De sjablonen tekenen een paarse rechthoek in de weergave. In de volgende sectie wijzigt u een aantal regels in de listener voor het maken van de weergave en vervangt u de inhoud van ColumnGuideAdornment.cs.
opdrachten. Druk in Solution Explorerop de rechterknop op het projectknooppunt. Kies de Toevoegen | Nieuw item ... opdracht om een nieuw weergave sieritem toe te voegen. Kies Uitbreidbaarheid | VSPackage in het linkernavigatiedeelvenster en kies Aangepaste opdracht in het rechterdeelvenster. Voer de naam in ColumnGuideCommands als de naam van het item en kies Toevoegen. Naast verschillende verwijzingen zijn ook de opdrachten en het pakket, namelijk (ColumnGuideCommands.cs), (ColumnGuideCommandsPackage.cs) en (ColumnGuideCommandsPackage.vsct), toegevoegd. In de volgende sectie vervangt u de inhoud van de eerste en laatste bestanden om de opdrachten te definiëren en te implementeren.
De listener voor het maken van de tekstweergave instellen
Open ColumnGuideAdornmentTextViewCreationListener.cs in de editor. Met deze code wordt een handler geïmplementeerd voor wanneer Visual Studio tekstweergaven maakt. Er zijn kenmerken die bepalen wanneer de handler wordt aangeroepen, afhankelijk van de kenmerken van de weergave.
De code moet ook een sierlaag declareren. Wanneer de editor weergaven bijwerkt, worden de sierlagen en vervolgens de sierelementen voor de weergave opgehaald. U kunt de volgorde van uw laag ten opzichte van anderen declareren met kenmerken. Vervang de volgende regel:
[Order(After = PredefinedAdornmentLayers.Caret)]
met deze twee regels:
[Order(Before = PredefinedAdornmentLayers.Text)]
[TextViewRole(PredefinedTextViewRoles.Document)]
De regel die u hebt vervangen, bevindt zich in een groep kenmerken die een sierlaag declareren. De eerste regel die u hebt gewijzigd, verandert alleen de positie van de kolomhulplijnen. Als u de regels 'vóór' de tekst in de weergave tekent, worden ze achter of onder de tekst weergegeven. Met de tweede regel wordt aangegeven dat kolomhulplijnversieringen van toepassing zijn op tekstentiteiten die passen binnen uw idee van een document. U zou bijvoorbeeld kunnen instellen dat de versiering uitsluitend werkt voor bewerkbare tekst. Meer informatie vindt u in Language-service en editor-extensiepunten
Het instellingenbeheer implementeren
Vervang de inhoud van de GuidesSettingsManager.cs door de volgende code (zie hieronder):
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 meeste van deze code maakt en parseert de instellingenindeling: "RGB(<int>,<int>,<int>) <int>, <int>, ...". De gehele getallen aan het einde zijn de kolommen op basis van één kolom waarin u kolomhulplijnen wilt gebruiken. De extensie kolomhulplijnen legt alle instellingen vast in één instellingswaardetekenreeks.
Er zijn enkele onderdelen van de code die u moet markeren. De volgende coderegel haalt de beheerde Visual Studio-wrapper op voor de instellingenopslag. Voor het grootste deel abstracteert dit over het Windows-register, maar deze API is onafhankelijk van het opslagmechanisme.
internal static SettingsManager VsManagedSettingsManager =
new ShellSettingsManager(ServiceProvider.GlobalProvider);
De opslag van Visual Studio-instellingen maakt gebruik van een categorie-id en een instellings-id om alle instellingen uniek te identificeren:
private const string _collectionSettingsName = "Text Editor";
private const string _settingName = "Guides";
U hoeft "Text Editor"
niet te gebruiken als categorienaam. U kunt alles kiezen wat u wilt.
De eerste paar functies zijn de toegangspunten om instellingen te wijzigen. Ze controleren beperkingen op hoog niveau, zoals het maximum aantal toegestane gidsen. Vervolgens roepen ze WriteSettings
aan, die een instellingentekenreeks opstelt en de eigenschap GuideLinesConfiguration
instelt. Als u deze eigenschap instelt, wordt de waarde van de instellingen opgeslagen in het Visual Studio-instellingenarchief en wordt de SettingsChanged
gebeurtenis geactiveerd om alle ColumnGuideAdornment
objecten bij te werken, die elk zijn gekoppeld aan een tekstweergave.
Er zijn een aantal invoerpuntfuncties, zoals CanAddGuideline
, die worden gebruikt om opdrachten te implementeren die instellingen wijzigen. Wanneer In Visual Studio menu's worden weergegeven, worden opdracht-implementaties opgevraagd om te zien of de opdracht momenteel is ingeschakeld, wat de naam ervan is, enzovoort. Hieronder ziet u hoe u deze toegangspunten koppelt voor de opdracht-implementaties. Voor meer informatie over opdrachten, zie Menu's en opdrachten uitbreiden.
De klasse ColumnGuideAdornment implementeren
De ColumnGuideAdornment
-klasse wordt geïnstantieerd voor elke tekstweergave die versieringen kan hebben. Deze klasse luistert naar gebeurtenissen over het wijzigen van de weergave of instellingen, en het bijwerken of opnieuw tekenen van kolomhulplijnen indien nodig.
Vervang de inhoud van de ColumnGuideAdornment.cs door de volgende code (zie hieronder):
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);
}
}
}
Exemplaren van deze klasse houden de bijbehorende IWpfTextView en een lijst met Line
-objecten bij die op de weergave zijn getekend.
De constructor (aangeroepen vanuit ColumnGuideAdornmentTextViewCreationListener
wanneer Visual Studio nieuwe weergaven maakt) creëert de kolomgidsobjecten Line
. De constructor voegt ook handlers toe voor de SettingsChanged
gebeurtenis (gedefinieerd in GuidesSettingsManager
) en de weergave-gebeurtenissen LayoutChanged
en Closed
.
De LayoutChanged
gebeurtenis wordt geactiveerd door verschillende wijzigingen in de weergave, zoals wanneer Visual Studio de weergave creëert. De OnViewLayoutChanged
handler roept AddGuidelinesToAdornmentLayer
aan om uit te voeren. De code in OnViewLayoutChanged
bepaalt of de regelposities moeten worden bijgewerkt op basis van wijzigingen zoals wijzigingen in de tekengrootte, weergavewanden, horizontaal schuiven, enzovoort. De code in UpdatePositions
zorgt ervoor dat hulplijnen worden getekend tussen tekens of net na de kolom met tekst die zich in de opgegeven karakteroffset in de tekstregel bevindt.
Wanneer instellingen de SettingsChanged
functie wijzigen, worden alle Line
objecten opnieuw gemaakt met wat de nieuwe instellingen zijn. Na het instellen van de lijnposities verwijdert de code alle vorige Line
objecten uit de ColumnGuideAdornment
sierlaag en worden de nieuwe objecten toegevoegd.
De opdrachten, menu's en menuplaatsingen definiëren
Er komt veel kijken bij het opstellen van opdrachten en menu's, het plaatsen van groepen opdrachten of menu's in andere menu's, en het koppelen van opdrachthandlers. In dit overzicht ziet u hoe opdrachten in deze extensie werken, maar zie Menu's en opdrachten uitbreidenvoor meer informatie.
Inleiding tot de code
De extensie Kolomhulplijnen toont het declareren van een groep opdrachten die bij elkaar horen (kolom toevoegen, kolom verwijderen, lijnkleur wijzigen) en die groep vervolgens in een submenu van het contextmenu van de editor plaatsen. De extensie Kolomhulplijnen voegt de opdrachten ook toe aan het hoofdmenu Bewerken, maar houdt ze onzichtbaar, zoals hieronder als een veelvoorkomend patroon besproken.
Er zijn drie onderdelen voor de implementatie van opdrachten: ColumnGuideCommandsPackage.cs, ColumnGuideCommandsPackage.vsct en ColumnGuideCommands.cs. De code die door de sjablonen wordt gegenereerd, plaatst een opdracht in het menu Extra waarin een dialoogvenster wordt weergegeven als de implementatie. U kunt bekijken hoe dat wordt geïmplementeerd in de .vsct-- en ColumnGuideCommands.cs-bestanden, omdat dit eenvoudig is. U vervangt de code in deze bestanden hieronder.
De pakketcode bevat standaarddeclaraties die vereist zijn voor Visual Studio om te ontdekken dat de extensie opdrachten biedt en waar u de opdrachten kunt plaatsen. Wanneer het pakket wordt geïnitialiseerd, wordt de implementatieklasse van de opdrachten geïnstitueert. Voor meer informatie over pakketten met betrekking tot opdrachten, zie Menu's en opdrachten uitbreiden.
Een algemeen patroon voor opdrachten
De opdrachten in de extensie Kolomhulplijnen zijn een voorbeeld van een veelvoorkomend patroon in Visual Studio. U plaatst gerelateerde opdrachten in een groep en u plaatst die groep in een hoofdmenu, vaak met '<CommandFlag>CommandWellOnly</CommandFlag>
' ingesteld om de opdracht onzichtbaar te maken. Het plaatsen van opdrachten in de hoofdmenu's (zoals Bewerken) geeft ze mooie namen (zoals Edit.AddColumnGuide), die handig zijn voor het zoeken van opdrachten bij het opnieuw toewijzen van sleutelbindingen in Extra Opties. Het is ook handig om bij het aanroepen van opdrachten uit het opdrachtvenstervoltooiing te krijgen.
Vervolgens voegt u de groep opdrachten toe aan contextmenu's of submenu's waarvan u verwacht dat de gebruiker de opdrachten gebruikt. Visual Studio behandelt CommandWellOnly
alleen als een onzichtbaarheidsvlag voor hoofdmenu's. Wanneer u dezelfde groep opdrachten in een contextmenu of submenu plaatst, zijn de opdrachten zichtbaar.
Als onderdeel van het gebruikelijke patroon maakt de extensie Kolomhulplijnen een tweede groep die één submenu bevat. Het submenu bevat vervolgens de eerste groep met de vierkolomgidscommando's. De tweede groep met het submenu is de herbruikbare asset die u in verschillende contextmenu's plaatst, waardoor een submenu in deze contextmenu's wordt geplaatst.
Het VSCT-bestand
Het bestand .vsct declareert de opdrachten en waar ze heengaan, samen met pictogrammen enzovoort. Vervang de inhoud van het bestand .vsct door de volgende code (zie hieronder):
<?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-. Opdat Visual Studio uw opdrachthandlers kan vinden en deze kan invoeren, moet u ervoor zorgen dat de pakket-GUID die is gedeclareerd in het ColumnGuideCommandsPackage.cs-bestand (gegenereerd op basis van de projectitemsjabloon) overeenkomt met de pakket-GUID die is gedeclareerd in het .vsct--bestand dat hierboven is gekopieerd. Als u deze voorbeeldcode opnieuw gebruikt, moet u ervoor zorgen dat u een andere GUID hebt, zodat u geen conflict veroorzaakt met iemand anders die deze code mogelijk heeft gekopieerd.
Zoek deze regel in ColumnGuideCommandsPackage.cs en kopieer de GUID tussen de aanhalingstekens:
public const string PackageGuidString = "ef726849-5447-4f73-8de5-01b9e930f7cd";
Plak vervolgens de GUID in het bestand .vsct zodat u de volgende regel in uw Symbols
declaraties hebt:
<GuidSymbol name="guidColumnGuideCommandsPkg"
value="{ef726849-5447-4f73-8de5-01b9e930f7cd}" />
De GUID's voor de opdrachtenset en het bitmapafbeeldingsbestand moeten ook uniek zijn voor uw extensies:
<GuidSymbol name="guidColumnGuidesCommandSet"
value="{c2bc0047-8bfa-4e5a-b5dc-45af8c274d8e}">
<GuidSymbol name="guidImages" value="{2C99F852-587C-43AF-AA2D-F605DE2E46EF}">
U hoeft in deze stapsgewijze handleiding echter niet de GUID's voor de opdrachtset en bitmapafbeelding te wijzigen om de code te laten functioneren. De GUID van de opdrachtset moet overeenkomen met de declaratie in het ColumnGuideCommands.cs bestand, maar u vervangt ook de inhoud van dat bestand; daarom komen de GUID's overeen.
Andere GUID's in het bestand .vsct identificeren vooraf bestaande menu's waaraan de kolomgidsopdrachten worden toegevoegd, zodat ze nooit worden gewijzigd.
bestandsecties. De .vsct bevat drie uitwendige secties: opdrachten, plaatsingen en symbolen. In de sectie Opdrachten worden opdrachtgroepen, menu's, knoppen of menu-items en bitmaps voor pictogrammen gedefinieerd. In de sectie plaatsingen wordt aangegeven waar groepen op menu's of extra plaatsingen op bestaande menu's worden toegevoegd. De sectie symbolen declareert identificatoren die elders in het bestand .vsct worden gebruikt, waardoor de .vsct code beter leesbaar is dan overal GUID's en hexadecimale nummers te hebben.
Opdrachten sectie, definities van groepen. In de sectie Opdrachten worden eerst opdrachtgroepen gedefinieerd. Groepen opdrachten zijn opdrachten die u ziet in menu's met lichte grijze lijnen die de groepen scheiden. Een groep kan ook een volledig submenu vullen, zoals in dit voorbeeld, en u ziet in dit geval niet de grijze scheidingslijnen. De .vsct--bestanden declareren twee groepen, de GuidesMenuItemsGroup
die boven de IDM_VS_MENU_EDIT
staat (het hoofdmenu Bewerken) en de GuidesContextMenuGroup
die boven de IDM_VS_CTXT_CODEWIN
(het contextmenu van de code-editor) staat.
De tweede groepsdeclaratie heeft een 0x0600
prioriteit:
<Group guid="guidColumnGuidesCommandSet" id="GuidesContextMenuGroup"
priority="0x0600">
Het idee is om het submenu kolomhulplijnen aan het einde van een contextmenu te plaatsen waaraan u de submenugroep toevoegt. Maar u moet er niet van uitgaan dat u het beste weet en afdwingen dat het submenu altijd de laatste is met behulp van een prioriteit van 0xFFFF
. U moet experimenteren met het nummer om te zien waar uw submenu zich bevindt in de contextmenu's waar u het plaatst. In dit geval is 0x0600
hoog genoeg om het aan het einde van de menu's te plaatsen, voor zover u kunt zien, maar het laat ruimte voor iemand anders om de extensie zo te ontwerpen dat deze lager is dan de kolomhulplijnextensie als dat wenselijk is.
Opdrachtensectie, menudefinitie. Vervolgens definieert het opdrachtenmenu het submenu GuidesSubMenu
, dat ondergeschikt is aan GuidesContextMenuGroup
. De GuidesContextMenuGroup
is de groep die u toevoegt aan alle relevante contextmenu's. In de plaatsingen-sectie plaatst de code de groep met de opdrachten voor vier kolommen op dit submenu.
sectie Opdrachten, knoppendefinities. De opdrachtensectie definieert vervolgens de menu-items of knoppen die de vierkoloms-gidsopdrachten zijn.
CommandWellOnly
, hierboven besproken, betekent dat de opdrachten onzichtbaar zijn wanneer ze in een hoofdmenu worden geplaatst. Twee van de declaraties van de menuopdrachtknop (hulplijn toevoegen en handleiding verwijderen) hebben ook een AllowParams
vlag:
<CommandFlag>AllowParams</CommandFlag>
Met deze vlag kunt u, naast het hebben van hoofdmenu-plaatsingen, de opdracht inschakelen om argumenten te ontvangen wanneer Visual Studio de opdrachthandler aanroept. Als de gebruiker de opdracht uitvoert vanuit het opdrachtvenster, wordt het argument doorgegeven aan de opdrachthandler in de gebeurtenisargumenten.
Opdrachtsecties, bitmap-definities. Ten slotte declareert de sectie opdrachten de bitmaps of pictogrammen die voor de opdrachten worden gebruikt. Deze sectie is een eenvoudige declaratie die de projectresource identificeert en één index van gebruikte pictogrammen weergeeft. De symbolensectie van het bestand .vsct declareert de waarden van de id's die worden gebruikt als indexen. In deze walkthrough wordt de bitmapstrook gebruikt die is geleverd met de aangepaste opdrachtitem-sjabloon die aan het project is toegevoegd.
Plaatsingen sectie. Na de sectie opdrachten volgt de sectie plaatsingen. De eerste is de plaats waar de code de hierboven besproken eerste groep toevoegt, die de opdrachten van de vierkolomgids bevat, aan het submenu waarin de opdrachten verschijnen.
<CommandPlacement guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0x0100">
<Parent guid="guidColumnGuidesCommandSet" id="GuidesSubMenu" />
</CommandPlacement>
Alle andere plaatsingen voegen de GuidesContextMenuGroup
(die de GuidesSubMenu
bevat) toe aan andere contextmenu's van de editor. Toen de code GuidesContextMenuGroup
declareerde, werd het gekoppeld aan het contextmenu van de code-editor. Daarom ziet u geen plaatsing voor het contextmenu van de code-editor.
Symbolensectie. Zoals hierboven vermeld, declareert de sectie symbolen identificaties die elders in het .vsct--bestand worden gebruikt, waardoor de .vsct--code beter leesbaar is dan overal GUID's en hexnummers hebben. De belangrijkste punten in deze sectie zijn dat de pakket-GUID akkoord moet gaan met de declaratie in de pakketklasse. En de GUID van de opdrachtenset moet akkoord gaan met de declaratie in de opdracht-implementatieklasse.
De opdrachten implementeren
Het ColumnGuideCommands.cs-bestand implementeert de opdrachten en koppelt de handlers aan. Wanneer Visual Studio het pakket laadt en het initialiseert, roept het pakket op zijn beurt Initialize
aan op de implementatieklasse van opdrachten. De initialisatie van de opdrachten instantieert eenvoudig de klasse en de constructor haakt alle opdrachthandlers aan.
Vervang de inhoud van het bestand ColumnGuideCommands.cs door de volgende code (zie hieronder):
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);
}
}
}
}
}
Verwijzingen herstellen. Er ontbreekt op dit moment een verwijzing. Druk op de rechterknop op het knooppunt Verwijzingen in Solution Explorer. Kies de opdracht Toevoegen.... Het dialoogvenster Verwijzing toevoegen bevat een zoekvak in de rechterbovenhoek. Voer 'editor' in (zonder de dubbele aanhalingstekens). Kies het Microsoft.VisualStudio.Editor item (u moet het selectievakje links van het item inschakelen, niet alleen het item selecteren) en kies OK- om de verwijzing toe te voegen.
initialisatie. Wanneer de pakketklasse wordt geïnitialiseerd, roept deze Initialize
aan op de implementatieklasse van opdrachten. De ColumnGuideCommands
initialisatie instantieert de klasse en slaat het klasse-exemplaar en de pakketreferentie in klasseleden op.
Laten we eens kijken naar een van de koppelingen van de opdrachthandler in de klasseconstructor:
_addGuidelineCommand =
new OleMenuCommand(AddColumnGuideExecuted, null,
AddColumnGuideBeforeQueryStatus,
new CommandID(ColumnGuideCommands.CommandSet,
cmdidAddColumnGuide));
U maakt een OleMenuCommand
. Visual Studio maakt gebruik van het Microsoft Office-opdrachtsysteem. De belangrijkste argumenten bij het instantiëren van een OleMenuCommand
is de functie waarmee de opdracht (AddColumnGuideExecuted
) wordt geïmplementeerd, de functie die moet worden aangeroepen wanneer Visual Studio een menu met de opdracht (AddColumnGuideBeforeQueryStatus
) en de opdracht-id weergeeft. Visual Studio roept de querystatusfunctie aan voordat een opdracht in een menu wordt weergegeven, zodat de opdracht zichzelf onzichtbaar of grijs kan maken voor een bepaalde weergave van het menu (bijvoorbeeld het uitschakelen van Kopiëren als er geen selectie is), het pictogram ervan kan wijzigen of zelfs de naam ervan wijzigen (bijvoorbeeld van Iets toevoegen om iets te verwijderen), enzovoort. De opdracht-id moet overeenkomen met een opdracht-id die is gedeclareerd in het bestand .vsct. De tekenreeksen voor de opdrachtenset en het kolomhulplijnen toevoegen commando moeten overeenkomen tussen het .vsct bestand en de ColumnGuideCommands.cs.
De volgende regel biedt hulp bij het aanroepen van de opdracht via het opdrachtvenster (zie hieronder):
_addGuidelineCommand.ParametersDescription = "<column>";
querystatus. De querystatusfuncties AddColumnGuideBeforeQueryStatus
en RemoveColumnGuideBeforeQueryStatus
controleren enkele instellingen (zoals het maximum aantal hulplijnen of het maximaal aantal kolommen) of er een kolomhulplijn is om te verwijderen. Ze schakelen de opdrachten in als de voorwaarden juist zijn. Querystatusfuncties moeten efficiënt zijn omdat ze worden uitgevoerd telkens wanneer Visual Studio een menu weergeeft en voor elke opdracht in het menu.
functie AddColumnGuideExecuted uitgevoerd. Het interessante aspect van het toevoegen van een gids is het bepalen van de weergave van de editor en de cursorpositie. Eerst roept deze functie GetApplicableColumn
aan, waarmee wordt gecontroleerd of er een door de gebruiker opgegeven argument is in de gebeurtenisargumenten van de opdrachthandler en als er geen is, controleert de functie de weergave van de editor:
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
moet een beetje graven om een IWpfTextView weergave van de code te krijgen. Als u tracering uitvoert via GetActiveTextView
, GetActiveView
en GetTextViewFromVsTextView
, kunt u zien hoe u dat doet. De volgende code is de relevante geabstraheerde code, te beginnen met de huidige selectie, vervolgens het frame van de selectie op te halen, daarna de DocView van het frame op te halen als een IVsTextView, vervolgens een IVsUserData op te halen uit de IVsTextView, daarna een weergavehost op te halen, en ten slotte de IWpfTextView te verkrijgen.
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;
Zodra u een IWpfTextView hebt, kunt u de kolom ophalen waarin de caret zich bevindt:
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));
}
Met de huidige kolom waarop de gebruiker heeft geklikt, roept de code alleen de instellingenbeheerder aan om de kolom toe te voegen of te verwijderen. De instellingenbeheerder activeert de gebeurtenis waarop alle ColumnGuideAdornment
objecten luisteren. Wanneer de gebeurtenis wordt geactiveerd, werken deze objecten hun bijbehorende tekstweergaven bij met nieuwe instellingen voor kolomrichtlijnen.
Opdracht aanroepen vanuit het opdrachtvenster
Met het voorbeeld van kolomhulplijnen kunnen gebruikers twee opdrachten vanuit het opdrachtvenster aanroepen als een vorm van uitbreidbaarheid. Als u de opdracht Weergave | Andere Windows | Opdrachtvenster gebruikt, ziet u het opdrachtvenster. U kunt interageren met het opdrachtvenster door "bewerken" in te voeren. Met autocompletie van opdrachtnamen en het argument 120 krijgt u het volgende resultaat:
> Edit.AddColumnGuide 120
>
De onderdelen van het voorbeeld die dit gedrag mogelijk maken, bevinden zich in de .vsct bestandsdeclaraties, de ColumnGuideCommands
klasseconstructor wanneer deze opdrachthandlers koppelt en de implementaties van de opdrachthandler die gebeurtenisargumenten controleren.
U hebt '<CommandFlag>CommandWellOnly</CommandFlag>
' gezien in het .vsct-bestand en plaatsingen in het hoofdmenu Bewerken, ook al worden de opdrachten niet weergegeven in de gebruikersinterface van het Bewerken-menu. Als ze zich in het hoofdmenu Bewerken bevinden, krijgen ze namen zoals Edit.AddColumnGuide. De opdrachtgroepdeclaratie die de vier opdrachten bevat, plaatst de groep rechtstreeks in het menu Bewerken:
<Group guid="guidColumnGuidesCommandSet" id="GuidesMenuItemsGroup"
priority="0xB801">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
</Group>
De knoppensectie heeft later de opdracht CommandWellOnly
gedeclareerd om deze onzichtbaar te houden in het hoofdmenu en ze met AllowParams
verklaard.
<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>
U merkte op dat de commandohandlercode in de constructor van de ColumnGuideCommands
-klasse een beschrijving van de toegestane parameter gaf.
_addGuidelineCommand.ParametersDescription = "<column>";
U zag dat de functie GetApplicableColumn
controleert of OleMenuCmdEventArgs
een waarde heeft voordat deze de weergave van de editor controleert voor een huidige kolom.
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;
}
Probeer uw extensie
U kunt nu op F5 drukken om de extensie kolomhulplijnen uit te voeren. Open een tekstbestand en gebruik het contextmenu van de editor om hulplijnen toe te voegen, ze te verwijderen en hun kleur te wijzigen. Klik in de tekst (niet in lege ruimte voorbij het einde van de regel) om een kolomhulplijn toe te voegen, of anders voegt de editor deze toe aan de laatste kolom van deze regel. Als u het opdrachtvenster gebruikt en de opdrachten aanroept met een argument, kunt u overal kolomhulplijnen toevoegen.
Als u verschillende opdrachtplaatsingen wilt proberen, namen wilt wijzigen, pictogrammen wilt wijzigen, enzovoort, en u problemen ondervindt met Visual Studio met de meest recente code in menu's, kunt u de experimentele hive opnieuw instellen waarin u fouten opspoort. Open het startmenu van Windows en typ opnieuw instellen. Zoek de opdracht en voer deze uit: Stel de volgende Visual Studio Experimentele Instantie opnieuw in. Met deze opdracht wordt het experimentele registerbestand van alle extensieonderdelen opgeschoond. Er worden geen instellingen van onderdelen opgeschoond, dus alle handleidingen die u had toen u visual Studio's experimentele hive afsluit, zijn er nog steeds wanneer uw code het instellingenarchief leest bij de volgende lancering.
Het afgeronde codeproject
Er is binnenkort een GitHub-project van Visual Studio Extensibility-voorbeelden en het voltooide project is er. Dit artikel wordt bijgewerkt om daar te verwijzen wanneer dat gebeurt. Het voltooide voorbeeldproject kan verschillende guid's hebben en heeft een andere bitmapstrook voor de opdrachtpictogrammen.
U kunt een versie van de functie voor kolomhulplijnen uitproberen met deze Visual Studio Gallery extensie.