Visual Studio拦截F1帮助命令

Luk*_*ire 10 c# visual-studio-addins visual-studio visual-studio-extensions visual-studio-2012

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

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

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

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

Jus*_*ant 13

大约10年前,当我在微软工作时,我在Visual Studio 2005中编写了原始"在线F1"功能的规范.所以我的知识有点权威但也可能过时了.;-)

您无法更改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
Run Code Online (Sandbox Code Playgroud)

上面的"k"参数包含visual studio中的帮助上下文.帮助上下文包含"关键字"(文本字符串)和"属性"(名称/值对),Visual Studio中的各种窗口用于告诉IDE当前用户正在执行的操作.

CSS编辑器推送了两个关键字:我选择的"宽度",以及MSDN可以用作"后备"的"vs.csseditor",例如,如果在MSDN上找不到我的选择.

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

TargetFrameworkMoniker = NETFramework,Version=v4.0
DevLang=CSS
Run Code Online (Sandbox Code Playgroud)

这些确保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. 找到与该关键字匹配的所有页面
  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
{
    /// <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;
    }
}
Run Code Online (Sandbox Code Playgroud)