Visual Studio 拦截 F1 帮助命令

问题描述 投票:0回答:1

我希望编写一个 Visual Studio 插件,当在类或类型上调用 F1 帮助时,它可以拦截默认的在线帮助命令并获取 MSDN 库 URL。

例如,假设我将光标放在关键字字符串上并按 F1,它通常会自动打开浏览器并导航到字符串引用类型的帮助文档。我想在传递给浏览器的 URL 到达浏览器之前获取它。

是否可以编写一个可以拦截默认 F1 帮助命令的 Visual Studio 插件/扩展 ??

如果上述可以完成,有任何关于从哪里开始的指示吗?

c# visual-studio visual-studio-2012 visual-studio-addins visual-studio-extensions
1个回答
17
投票

大约十年前,当我在微软工作时,我为Visual Studio 2005中最初的“Online F1”功能编写了规范。所以我的知识有些权威,但也可能已经过时了。 ;-)

您无法更改 Visual Studio 正在使用的 URL(至少我不知道如何更改它),但您可以简单地编写另一个加载项来窃取 F1 键绑定,使用与默认的 F1 处理程序会执行此操作,并将用户定向到您自己的 URL 或应用程序。

首先,一些有关在线 F1 如何运作的信息:

    Visual Studio IDE 的组件将关键字推送到“F1 帮助上下文”中,这是有关用户正在执行的操作的信息的属性包:例如代码编辑器中的当前选择、正在编辑的文件类型、正在编辑的项目类型等。
  1. 当用户按 F1 时,IDE 将帮助上下文打包到 URL 中,并打开指向 MSDN 的浏览器。
  2. 这是一个示例 URL,在本例中,当选择 CSS 属性“width”时,在 VS2012 HTML 编辑器中按 F1

msdn.microsoft.com/query/dev11.query? appId=Dev11IDEF1& l=EN-US& k=k(width); k(vs.csseditor); k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.0); k(DevLang-CSS)& rd=true

上面的“k”参数包含 Visual Studio 内部的帮助上下文。帮助上下文包含“关键字”(文本字符串)和“属性”(名称/值对),Visual Studio 中的各个窗口使用它们来告诉 IDE 用户现在正在做什么。

CSS 编辑器推送了两个关键字:我选择的“width”和“vs.csseditor”,MSDN 可以将其用作“后备”,例如,如果在 MSDN 上找不到我的选择。

还有一些上下文过滤属性:(

-

分隔符将名称/值对中的名称与值分开)

TargetFrameworkMoniker-NETFramework,Version=v4.0
DevLang-CSS

这些确保 F1 加载正确语言或技术的页面,在本例中为 CSS。 (.NET 4.0 的另一个过滤器存在,因为我加载的项目面向 .NET 4.0)

注意上下文是有序的。 “width”关键字比它下面的关键字更重要。

MSDN 上的实际帮助内容具有元数据(由编写文档的团队手动设置),其中包含与该页面关联的关键字和名称/值上下文属性。例如,MSDN 上的

css width 属性文档

,当它存储在 MSDN 服务器上时,具有与其关联的关键字列表(在本例中为:“width”)和上下文属性列表(在本例中为:“DevLang” =CSS”)。页面可以有多个关键字(例如“System.String”、“String”)和多个上下文属性(例如“DevLang=C#”、“DevLang=VB”等)。 当关键字列表到达 MSDN Online F1 服务时,算法是这样的,但需要注意的是,它在过去几年中可能已经发生了变化:

取第一个关键词
  1. 查找与该关键字匹配的所有页面
  2. 排除与上下文属性名称(例如“DevLang”)匹配但与值不匹配的所有页面。例如,这将排除
  3. Control.Width
  4. 页面,因为它将被标记为“DevLang=C#”、“DevLang=VB”。但它不会排除没有 DevLang 属性的页面。 如果没有留下任何结果,但还有更多关键字,请从 #1 开始使用下一个关键字(按顺序),除非您用完关键字。如果没有留下关键字,请执行“备份”操作,这可能会返回 MSDN 搜索结果列表,可能会显示“找不到页面”,或其他一些解决方案。
  5. 对剩余结果进行排名。我不记得确切的排名算法,从那时起它可能已经改变,但我相信总体思路是首先显示匹配更多属性的页面,然后首先显示更受欢迎的匹配。
  6. 在浏览器中显示最上面的结果
  7. 以下是 Visual Studio 加载项如何实现的代码示例:

接管F1键绑定
  1. 当按下F1时,获取帮助上下文并将其转换为一组名称=值对
  2. 将这组名称=值对传递到一些外部代码中以对 F1 请求执行某些操作。
  3. 我省略了所有 Visual Studio 插件样板代码 - 如果您也需要,Google 中应该有很多示例。

using System; using Extensibility; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.CommandBars; using System.Resources; using System.Reflection; using System.Globalization; using System.Collections; using System.Collections.Generic; using System.Text; namespace ReplaceF1 { /// <summary>The object for implementing an Add-in.</summary> /// <seealso class='IDTExtensibility2' /> public class Connect : IDTExtensibility2, IDTCommandTarget { /// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary> public Connect() { } MsdnExplorer.MainWindow Explorer = new MsdnExplorer.MainWindow(); /// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary> /// <param term='application'>Root object of the host application.</param> /// <param term='connectMode'>Describes how the Add-in is being loaded.</param> /// <param term='addInInst'>Object representing this Add-in.</param> /// <seealso class='IDTExtensibility2' /> public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) { _applicationObject = (DTE2)application; _addInInstance = (AddIn)addInInst; if(connectMode == ext_ConnectMode.ext_cm_UISetup) { object []contextGUIDS = new object[] { }; Commands2 commands = (Commands2)_applicationObject.Commands; string toolsMenuName; try { // If you would like to move the command to a different menu, change the word "Help" to the // English version of the menu. This code will take the culture, append on the name of the menu // then add the command to that menu. You can find a list of all the top-level menus in the file // CommandBar.resx. ResourceManager resourceManager = new ResourceManager("ReplaceF1.CommandBar", Assembly.GetExecutingAssembly()); CultureInfo cultureInfo = new System.Globalization.CultureInfo(_applicationObject.LocaleID); string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Help"); toolsMenuName = resourceManager.GetString(resourceName); } catch { //We tried to find a localized version of the word Tools, but one was not found. // Default to the en-US word, which may work for the current culture. toolsMenuName = "Help"; } //Place the command on the tools menu. //Find the MenuBar command bar, which is the top-level command bar holding all the main menu items: Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"]; //Find the Tools command bar on the MenuBar command bar: CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName]; CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl; //This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in, // just make sure you also update the QueryStatus/Exec method to include the new command names. try { //Add a command to the Commands collection: Command command = commands.AddNamedCommand2(_addInInstance, "ReplaceF1", "MSDN Advanced F1", "Brings up context-sensitive Help via the MSDN Add-in", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton); command.Bindings = new object[] { "Global::F1" }; } catch(System.ArgumentException) { //If we are here, then the exception is probably because a command with that name // already exists. If so there is no need to recreate the command and we can // safely ignore the exception. } } } /// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary> /// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param> /// <param term='custom'>Array of parameters that are host application specific.</param> /// <seealso class='IDTExtensibility2' /> public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) { } /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary> /// <param term='custom'>Array of parameters that are host application specific.</param> /// <seealso class='IDTExtensibility2' /> public void OnAddInsUpdate(ref Array custom) { } /// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary> /// <param term='custom'>Array of parameters that are host application specific.</param> /// <seealso class='IDTExtensibility2' /> public void OnStartupComplete(ref Array custom) { } /// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary> /// <param term='custom'>Array of parameters that are host application specific.</param> /// <seealso class='IDTExtensibility2' /> public void OnBeginShutdown(ref Array custom) { } /// <summary>Implements the QueryStatus method of the IDTCommandTarget interface. This is called when the command's availability is updated</summary> /// <param term='commandName'>The name of the command to determine state for.</param> /// <param term='neededText'>Text that is needed for the command.</param> /// <param term='status'>The state of the command in the user interface.</param> /// <param term='commandText'>Text requested by the neededText parameter.</param> /// <seealso class='Exec' /> public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText) { if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone) { if(commandName == "ReplaceF1.Connect.ReplaceF1") { status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled; return; } } } /// <summary>Implements the Exec method of the IDTCommandTarget interface. This is called when the command is invoked.</summary> /// <param term='commandName'>The name of the command to execute.</param> /// <param term='executeOption'>Describes how the command should be run.</param> /// <param term='varIn'>Parameters passed from the caller to the command handler.</param> /// <param term='varOut'>Parameters passed from the command handler to the caller.</param> /// <param term='handled'>Informs the caller if the command was handled or not.</param> /// <seealso class='Exec' /> public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled) { if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault) { if (commandName == "ReplaceF1.Connect.ReplaceF1") { // Get a reference to Solution Explorer. Window activeWindow = _applicationObject.ActiveWindow; ContextAttributes contextAttributes = activeWindow.DTE.ContextAttributes; contextAttributes.Refresh(); List<string> attributes = new List<string>(); try { ContextAttributes highPri = contextAttributes == null ? null : contextAttributes.HighPriorityAttributes; highPri.Refresh(); if (highPri != null) { foreach (ContextAttribute CA in highPri) { List<string> values = new List<string>(); foreach (string value in (ICollection)CA.Values) { values.Add(value); } string attribute = CA.Name + "=" + String.Join(";", values.ToArray()); attributes.Add(CA.Name + "="); } } } catch (System.Runtime.InteropServices.COMException e) { // ignore this exception-- means there's no High Pri values here string x = e.Message; } catch (System.Reflection.TargetInvocationException e) { // ignore this exception-- means there's no High Pri values here string x = e.Message; } catch (System.Exception e) { System.Windows.Forms.MessageBox.Show(e.Message); // ignore this exception-- means there's no High Pri values here string x = e.Message; } // fetch context attributes that are not high-priority foreach (ContextAttribute CA in contextAttributes) { List<string> values = new List<string>(); foreach (string value in (ICollection)CA.Values) { values.Add (value); } string attribute = CA.Name + "=" + String.Join(";", values.ToArray()); attributes.Add (attribute); } // Replace this call with whatever you want to do with the help context info HelpHandler.HandleF1 (attributes); } } } private DTE2 _applicationObject; private AddIn _addInInstance; } }

© www.soinside.com 2019 - 2024. All rights reserved.