11

F1クラスまたは型でヘルプが呼び出されたときに、デフォルトのオンライン ヘルプ コマンドをインターセプトし、MSDN ライブラリの URL を取得できるビジュアル スタジオ アドインを作成しようとしています。

たとえば、カーソルをキーワード文字列に置いて押すF1と、通常は自動的にブラウザが開き、文字列参照タイプのヘルプ ドキュメントに移動します。ブラウザーに渡される URL を、ブラウザーに到達する前に取得したいと考えています。

デフォルトの F1 ヘルプ コマンドをインターセプトできるビジュアル スタジオ アドイン/拡張機能を作成することは可能ですか??

上記が実行できる場合、どこから始めるべきかについての指針はありますか?

4

2 に答える 2

16

約 10 年前、Microsoft で働いていたとき、Visual Studio 2005 のオリジナルの「オンライン F1」機能の仕様を書きました。;-)

Visual Studio が使用している URL を変更することはできませんが (少なくとも変更方法はわかりません)、F1 キー バインドを盗み、デフォルトと同じヘルプ コンテキストを使用する別のアドインを簡単に作成できます。 F1 ハンドラーが実行し、ユーザーを独自の URL またはアプリに誘導します。

まず、オンライン F1 の仕組みに関する情報:

  1. Visual Studio IDE のコンポーネントは、ユーザーが行っていることに関する情報のプロパティ バッグである "F1 ヘルプ コンテキスト" にキーワードをプッシュします。たとえば、コード エディターでの現在の選択、編集中のファイルの種類、編集中のプロジェクトの種類などです。 .

  2. ユーザーが F1 キーを押すと、コンテキストを URL に変換し、MSDN を指すブラウザーを開く IDE パッケージが表示されます。

これはサンプル URL です。この場合、VS2012 HTML エディターで CSS プロパティ「幅」が選択されているときに 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 内のさまざまなウィンドウが、ユーザーが現在行っていることを IDE に伝えるために使用します。

CSS エディターは 2 つのキーワードをプッシュしました: 私が選択した "width" と、たとえば私の選択が MSDN で見つからない場合に MSDN が "フォールバック" として使用できる "vs.csseditor" です。

コンテキスト フィルタリング属性もいくつかあります。

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

これらにより、F1 が適切な言語またはテクノロジー (この場合は CSS) のページをロードすることが保証されます。(ロードしたプロジェクトが .NET 4.0 をターゲットにしているため、.NET 4.0 用のもう 1 つのフィルターがあります)

コンテキストが順序付けられていることに注意してください。「幅」キーワードは、その下のキーワードよりも重要です。

MSDN の実際のヘルプ コンテンツには、そのページに関連付けられたキーワードと名前/値のコンテキスト プロパティを含むメタデータ (ドキュメントを作成するチームが手動で設定) があります。たとえば、MSDN サーバーに保存されている場合、MSDN のcss 幅プロパティ ドキュメントには、関連付けられたキーワードのリスト (この場合は「幅」) とコンテキスト プロパティのリスト (この場合は「DevLang=CSS」) があります。 」)。ページには、複数のキーワード ("System.String"、"String" など) と複数のコンテキスト プロパティ ("DevLang=C#"、"DevLang=VB" など) を含めることができます。

キーワードのリストが MSDN オンライン F1 サービスに到達すると、アルゴリズムは次のようになりますが、ここ数年で変更された可能性があることに注意してください。

  1. 最初のキーワードを取る
  2. そのキーワードに一致するすべてのページを見つける
  3. コンテキスト属性名 (「DevLang」など) に一致するが、値に一致しないすべてのページを除外します。たとえば、Control.Widthページは "DevLang=C#"、"DevLang=VB" とマークされるため、除外されます。ただし、DevLang 属性のないページは除外されません。
  4. 結果が残っていないが、キーワードが残っている場合は、キーワードが不足しない限り、次のキーワードで (順番に) #1 からやり直します。キーワードが残っていない場合は、「バックアップ」操作を実行します。これにより、MSDN 検索結果のリストが返されたり、「見つかりません」ページが表示されたり、その他の解決策が表示されたりする場合があります。
  5. 残りの結果をランク付けします。正確なランキング アルゴリズムは覚えていません。おそらくそれ以降変更されていると思いますが、一般的な考え方は、より多くの属性に一致するページを最初に表示し、より人気のある一致を最初に表示することだったと思います。
  6. ブラウザーで最上位の結果を表示する

Visual Studio アドインでできることのコード サンプルを次に示します。

  1. F1キーバインディングを引き継ぐ
  2. F1 が押されると、ヘルプ コンテキストを取得し、名前=値のペアのセットに変換します。
  3. その名前=値のペアのセットを外部コードに渡して、F1 要求で何かを行います。

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;
    }
}
于 2012-12-19T00:24:18.743 に答える
-1

すべて非常にエキサイティングですが、潜在的に過剰に設計されていますか?私はほとんどのようにプログラム可能なマウスを持っています。検索するボタンの1つを設定しました。つまり、単語をクリックすると、その単語のお気に入りの検索エンジンでブラウザが開きます。通常、MSDNヘルプはそのリストにあります。ASIsSOリンク。私は効果的でシンプルな解決策が好きです:-)

于 2012-12-19T02:22:20.977 に答える