Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Меню в WEB-браузере


- Будьте добры позавите пожалуйста Ваню.
- Вани нет, он покинул этот мир.
- Он, что умер?
- Нет, к интернету подключился.

В конференции я часто натыкался на вопросы типа – "Как добавить свой пункт меню в контекстное меню IE, как это делает ReGet", "Как запретить появление контекстного меню в TwebBrowser” или "Как показать свое меню вместо стандартного". А вот ответов в большинстве случаев не было, или они советовали попробовать другие компоненты. Но когда мне самому понадобилось в рамках одного проекта сразу, и запретить появление меню, и вставить свой пункт в стандартное меню IE, я решил покопать в этом направлении. И, конечно, MSDN выручила меня в этих поисках. Так что не бойтесь, меню и TwebBrowser – очень даже дружны между собой и то, что с легкостью делают ребята с ReGet Software, не такая уже и неприступная магия…

Запрещение появления меню в TwebBrowser

Хотя в инспекторе и есть такое свойство для этого компонента – PopurMenu, но его использование очень ограничено. Давайте для примера создадим PopurMenu с двумя произвольными пунктами и присвоим свойству PopurMenu TwebBrowser значение PopurMenu1. Запускаем приложение. Щелчок правой кнопкой мыши – ура, меню наше исправно отображаеться. Но радоваться рано. Загружаем любую страницу в браузер, снова щелкае мышкой – вместо нашего меню появляеться стандартное контекстное меню IE. Почему же так?

Компонент TwebBrowser всего лишь оболочка для COM объектов IE, а пока никакая страница не загружена – все сообщения передаются непосредственно вашей программе и, обрабатывая их, программа воспринимает TwebBrowser как обычный VCL-компонент. Поэтому наше меню и появлялось. Когда же вызван метод Navigate, управление идет уже через СОМ интерфейсы, поэтому сообщения обрабатываються не оконным компонентом, а кодом "под оболочкой".

Вообще запретить появление меню можно. Вот некоторые способы:


...
private
...
procedure WMMouseActivate(var Msg: TMessage); message WM_MOUSEACTIVATE;
end;
...

Ставим обработчик для сообщения WM_MOUSEACTIVATE на уровне головной формы приложения.

Потом пишем процедуру:


procedure TMainForm.WMMouseActivate(var Msg: TMessage);
begin
  try
    inherited;
    //Анализируем, какая кнопка мыши нажата
    if Msg.LParamHi = 516 then // если правая
      // показываем свое меню
      PopupMenu1.Popup(Mouse.CursorPos.x, Mouse.CursorPos.y);
    Msg.Result := 0;
  except
  end;
end;

Но ведь практически всегда у нас на форме есть еще компоненты, для которых нужно выводить или свое меню, или не выводить его вообще. Поэтому в процедуре обработки нужно еще и анализировать координаты, где нажата мышка, и сравнивать их с координатами области, занятой окном браузера.

Что бы просто запретить появление меню можно преобразовать процедуру так:


procedure TForm1.WMMouseActivate(var Msg: TMessage);
begin
  try
    inherited;
    if Msg.LParamHi = 516 then
      Msg.Result:= MA_NOACTIVATEANDEAT;
  except
  end;
end;

Значение Msg.LparamHi показывает, какая кнопка нажата. 513 - нажата левая, 516 – нажата правая.

Все, что сказано было про координаты, справедливо и здесь. Все эти решения имеют один недостаток – процедура срабатывает на каждое нажатие кнопки мыши в пределах всего приложения.

И еще один недостаток/особенность – полностью, со 100% надежностью перекрыть меню IE своим таким способом почему-то не удается, а вот просто запретить появление – да.

А можно ли управлять отображением меню на уровне самого WebBrowser? Да, отвечает MSDN.

Для этого нужно сперва получить доступ к интерфейсу IDocHostUIHandler и вызвать один из его методов – ShowContextMenu.

Учтите, версия IE – не ниже 4.0. (В С/С++ он описан в файлах Mshtmhst.h; Mshtmhst.idl )

Получить этот интерфейс можно вызывая QueryInterface с параметром IID_IDocHostUIHandler. Он предназначен для управления панелями, меню и контекстными меню WebBrowser-a.

Нас интересует пока только метод ShowContextMenu. Вот его обьявление:


function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT; const
  pcmdtReserved: IUnknown; const pdispReserved: IDispatch): HRESULT; stdcall;

dwID
идентификатор меню, которое будет отображаться
ppt
указатель на структуру, которая указывает на координаты, где нужно отобразить меню.
pcmdTarget
ссылка на IOleCommandTarget интерфейс, который используется для запроса статуса и команд, которые должны выполняться меню.
рdispObject
ссылка на IDispatch интерфейс объекта, для того, что бы вызывать различные меню для различных объектов.

Метод возвращает:

S_OK
Отображается стандартное меню.
S_FALSE
Отображается другое, определенное программой меню.
DOCHOST_E_UNKNOWN
Идентификатор меню неизвестен.

В Internet Explorer 4.0 параметр pdispObject не используется, но в IЕ 5 и позже параметр содержит адрес IDispatch интерфейса. Таким способом можно выборочно запрещать появление контекстных меню.

Некоторые другие интересные методы IDocHostUIHandler:


function HideUI: HRESULT; stdcall;

Вызываеться, когда удаляеться пункт меню или панели инструментов.


function ShowUI(const dwID: DWORD; const pActiveObject: IOleInPlaceActiveObject;
  const pCommandTarget: IOleCommandTarget; const pFrame: IOleInPlaceFrame; const
  pDoc: IOleInPlaceUIWindow): HRESULT; stdcall;

Вызиваеться, когда приложение заменяет меню или панель инструментов.

Добавление пункта в стандартное меню

Иногда, если вы пишете какое-то приложение, которое взаимодействует с браузером, вам необходимо вызвать его непосредственно из IE. Но как добавить свой пункт в меню?

Пункт меню должен быть связан через URL с файлом, который содержит текст сценария, например, на JavaScript. Для того, что бы добавить пункт меню, откройте программно в реестре ключ:


HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt

В этом разделе создайте подраздел, который будет иметь название такое же, как и пункт меню. Значение по умолчанию содержит URL, по которому находиться скрипт. Для подчеркивания вставьте в название перед нужной буквой символ &.

Скрипт будет загружен и выполнен в скрытом окне. В его свойстве external.menuArguments будет содержаться объект window того окна, где был выполнен скрипт меню.

Пример. Вставляет пункт с названием "Демо" в стандартное меню. При нажатии выполняется скрипт, который содержится в файле "С:\demo_script.htm".


HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
MenuExt
Open in new window = "file://c:\demo_script.htm"

В файл впишите следующее


<SCRIPT LANGUAGE="JavaScript" defer>
open(external.menuArguments.location.href);
</SCRIPT>

Действие скрипта заключаеться в следующем:

Он открывает новое окно браузера, и загружает в него документ, который определен в external.menuArguments.location.href – окне, в котором было вызвано меню.

Дополнительные ключи.

Под ключом, где содержится URL скрипта, есть еще несколько величин. Одна из них определяет, в каком из доступных контекстных меню появиться этот новый пункт. Вторая определяет, что сценарий должен выполняться как dialog box.

Ключ "Contexts" имеет тип DWORD, и задает контексты, в которых будет появляться ваше меню. Определяется как применение операции логического ИЛИ над следующими константами:


(0x1 << CONTEXT_MENU_DEFAULT) (evaluates to 0x1) 
(0x1 << CONTEXT_MENU_IMAGE) (evaluates to 0x2) 
(0x1 << CONTEXT_MENU_CONTROL) (evaluates to 0x4) 
(0x1 << CONTEXT_MENU_TABLE) (evaluates to 0x8) 
(0x1 << CONTEXT_MENU_TEXTSELECT) (evaluates to 0x10) 
(0x1 << CONTEXT_MENU_ANCHOR) (evaluates to 0x20) 
(0x1 << CONTEXT_MENU_UNKNOWN) (evaluates to 0x40)

Так, к примеру, вам нужно, что бы ваше меню появлялось только когда есть выделенный текст. Тогда запишите значение 0x10 (CONTEXT_MENU_TEXTSELECT)

Второй ключ - flag с типом DWORD. Если первый бит установлен в 0x1, то сценарий выполняется так, если бы он был вызван методом showModalDialog. Окно, в котором выполняеться скрипт не скрываеться, и не закрываеться после выполнения сценария.

Как реализовать все это в Дельфи?

  1. Поскольку метод вставки пунктов меню позволяет вставить только ссылку на файл со скриптом, то нужно писать скрипт который будет вызывать вашу программу и передавать ей нужные значения. Для этого нужно еще знать VBScript или JavaScript.
  2. Большая проблема состоит в том, что описания интерфейса IDocHostUIHandler нет в файлах.

Но его описание есть в иходниках компонента EmbeddedWB, который можно взять на http://www.euromind.com/iedelphi/

Немножко поразбиравшись, я пришел к таким результатам:

Интерфейс не поддерживаеться стандартным TWebBrowser. Попытка перенести описание интерфейса с EmbeddedWB ник чему не приводит. Я понял с исходников, что при вызове IUnknown(WebBrowser1) as IDocHostUIHandler происходит обращение к DefaultInterface, а он у TWebBrowser IWebBrowser2. А он не знает о нужном нам интерфейсе.

Может, эта статья и не ответила на все вопросы, а только создала новые – не знаю. Это всегда так – как только с чем-то начинаешь разбираться, сразу к старым вопросам прибавляются новые…

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay