Visual Studio拦截F1帮助命令

我正在寻找一个可视化工作室插件,它可以拦截默认的在线帮助命令,并在类或类型上调用F1帮助时获取MSDN库URL。

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

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

如果以上可以做任何指针,从哪里开始?

大约10年前,当我在微软工作时,我在Visual Studio 2005中编写了原始“在线F1”function的规范。所以我的知识有点权威但也可能过时。 😉

您无法更改Visual Studio正在使用的URL(至少我不知道如何更改它),但您可以简单地编写另一个窃取F1键绑定的加载项,使用与默认值相同的帮助上下文F1处理程序可以将用户引导到您自己的URL或应用程序。

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

  1. Visual Studio IDE的组件将关键字推送到“F1帮助上下文”,这是一个关于用户正在做什么的信息包:例如代码编辑器中的当前选择,正在编辑的文件类型,正在编辑的项目类型等。

  2. 当用户按F1时,IDE将帮助上下文打包到URL中并打开指向MSDN的浏览器。

这是一个示例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编辑器推送了两个关键字:我选择的“宽度”,以及MSDN可以用作“后备”的“vs.csseditor”,例如,如果在MSDN上找不到我的选择。

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

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

这些确保F1加载页面以获得正确的语言或技术,在本例中为CSS。 (.NET 4.0的另一个filter就在那里,因为我加载的项目是针对.NET 4.0的)

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

MSDN上的实际帮助内容包含元数据(由编写文档的团队手动设置),其中包含与该页面关联的关键字和名称/值上下文属性。 例如,MSDN上的css width属性文档 ,当它存储在MSDN服务器上时,有一个与之关联的关键字列表(在本例中为“width”)和一个上下文属性列表(在这种情况下:“DevLang = CSS” “)。 页面可以有多个关键字(例如“System.String”,“String”)和多个上下文属性(例如“DevLang = C#”,“DevLang = VB”等)。

当关键字列表进入MSDN Online F1服务时,算法是这样的,但需要注意的是它在过去几年中可能已经改变:

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

以下是Visual Studio加载项的代码示例:

  1. 接管F1键绑定
  2. 按下F1时,获取帮助上下文并将其转换为一组名称=值对
  3. 将这组name = value对传递给某些外部代码,以便对F1请求执行某些操作。

我省略了所有Visual Studio插件样板代码 – 如果你也需要,那么谷歌应该有很多例子。

 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 { /// The object for implementing an Add-in. ///  public class Connect : IDTExtensibility2, IDTCommandTarget { /// Implements the constructor for the Add-in object. Place your initialization code within this method. public Connect() { } MsdnExplorer.MainWindow Explorer = new MsdnExplorer.MainWindow(); /// Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded. /// Root object of the host application. /// Describes how the Add-in is being loaded. /// Object representing this Add-in. ///  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. } } } /// Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded. /// Describes how the Add-in is being unloaded. /// Array of parameters that are host application specific. ///  public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) { } /// Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed. /// Array of parameters that are host application specific. ///  public void OnAddInsUpdate(ref Array custom) { } /// Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading. /// Array of parameters that are host application specific. ///  public void OnStartupComplete(ref Array custom) { } /// Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded. /// Array of parameters that are host application specific. ///  public void OnBeginShutdown(ref Array custom) { } /// Implements the QueryStatus method of the IDTCommandTarget interface. This is called when the command's availability is updated /// The name of the command to determine state for. /// Text that is needed for the command. /// The state of the command in the user interface. /// Text requested by the neededText parameter. ///  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; } } } /// Implements the Exec method of the IDTCommandTarget interface. This is called when the command is invoked. /// The name of the command to execute. /// Describes how the command should be run. /// Parameters passed from the caller to the command handler. /// Parameters passed from the command handler to the caller. /// Informs the caller if the command was handled or not. ///  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 attributes = new List(); try { ContextAttributes highPri = contextAttributes == null ? null : contextAttributes.HighPriorityAttributes; highPri.Refresh(); if (highPri != null) { foreach (ContextAttribute CA in highPri) { List values = new List(); 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 values = new List(); 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; } } 

所有非常令人兴奋但可能过度设计? 我有像大多数人那样的可编程鼠标。 我已经设置了一个按钮来搜索。 即点击单词和浏览器在喜爱的搜索引擎中打开该单词。 通常,MSDN帮助在该列表中。 AS是SO链接。 我喜欢有效而简单的灵魂:-)