Около 10 лет назад, когда я работал в Microsoft, я написал спецификацию для оригинальной функции «Online F1» в Visual Studio 2005. Так что мои знания несколько авторитетны, но, вероятно, устарели. ;-)
Вы не можете изменить URL-адрес, который использует Visual Studio (по крайней мере, я не знаю, как его изменить), но вы можете просто написать другую надстройку, которая крадет привязку клавиши F1, использует тот же контекст справки, что и по умолчанию Обработчик F1 делает это и направляет пользователя на ваш собственный URL-адрес или приложение.
Сначала немного информации о том, как работает Online F1:
Компоненты Visual Studio IDE помещают ключевые слова в «Контекст справки F1», который представляет собой пакет свойств с информацией о том, что делает пользователь: например. текущий выбор в редакторе кода, тип редактируемого файла, тип редактируемого проекта и т. д.
когда пользователь нажимает F1, среда IDE упаковывает контекст справки в URL-адрес и открывает браузер, указывающий на MSDN.
Вот пример URL-адреса, в данном случае при нажатии F1 в редакторе HTML VS2012, когда было выбрано свойство CSS «ширина».
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 ввел два ключевых слова: «ширина», которую я выбрал, и «vs.csseditor», который MSDN может использовать в качестве «запасного варианта», если, например, мой выбор не найден в MSDN.
Также есть некоторые атрибуты контекстной фильтрации:
TargetFrameworkMoniker = NETFramework,Version=v4.0
DevLang=CSS
Это гарантирует, что F1 загрузит страницу для правильного языка или технологии, в данном случае CSS. (Другой фильтр для .NET 4.0 существует, потому что проект, который я загрузил, нацелен на .NET 4.0)
Обратите внимание, что контекст упорядочен. Ключевое слово «ширина» более важно, чем те, что под ним.
Фактическое содержимое справки в MSDN имеет метаданные (вручную установленные командами, создающими документацию), содержащие ключевые слова и свойства контекста имени/значения, связанные с этой страницей. Например, документация по свойству ширины CSS в MSDN, когда она хранится на серверах MSDN, имеет список связанных с ним ключевых слов (в данном случае: «ширина») и список контекстных свойств (в данном случае: «DevLang=CSS»). Страницы могут иметь несколько ключевых слов (например, «System.String», «String») и несколько свойств контекста (например, «DevLang=C#», «DevLang=VB» и т. д.).
Когда список ключевых слов попадает в сервис MSDN Online F1, алгоритм выглядит примерно так, с той оговоркой, что за последние несколько лет он мог измениться:
- взять первое ключевое слово
- найти все страницы, соответствующие этому ключевому слову
- исключить все страницы, которые соответствуют имени контекстного атрибута (например, "DevLang"), но не соответствуют значению. Это, например, исключит Control.Width., потому что она будет помечена как "DevLang=C#", "DevLang=VB". Но это не исключает страницы без атрибута DevLang.
- Если результатов не осталось, но осталось больше ключевых слов, начните снова с # 1 со следующим ключевым словом (по порядку), если у вас не закончились ключевые слова. Если ключевых слов не осталось, выполните операцию «резервного копирования», которая может возвращать список результатов поиска MSDN, может показывать «не удается найти страницу» или какое-либо другое решение.
- Ранжируйте оставшиеся результаты. Я не помню точного алгоритма ранжирования, и, вероятно, с тех пор он изменился, но я считаю, что общая идея заключалась в том, чтобы сначала показать страницы, которые соответствуют большему количеству атрибутов, и сначала показать более популярные совпадения.
- Показать самый верхний результат в браузере
Вот пример кода, показывающий, как надстройка Visual Studio может:
- взять на себя привязку клавиши F1
- при нажатии F1 получить контекст справки и превратить его в набор пар имя=значение
- передать этот набор пар имя=значение во внешний код, чтобы что-то сделать с запросом 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;
}
}
person
Justin Grant
schedule
19.12.2012