Меню + TwebBrowser = Проблема?
Лозовюк Александр
Эту статью можно рассматривать как продолжение или дополнение к моей первой статье о компоненте TwebBrowser - Как сделать WebBrowser средствами Delphi 5.
В конференции я часто натыкался на вопросы типа – "Как добавить свой пункт меню в контекстное меню 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. А он не знает о нужном нам интерфейсе.
Может, эта статья и не ответила на все вопросы, а только создала новые – не
знаю. Это всегда так – как только с чем-то начинаешь разбираться, сразу к старым
вопросам прибавляются новые…
|