Пример расширения возможностей TWebBrowser

Источник: rsdn
Киселев Роман

Минимум, реализованный в TWebBrowser, причем реализованный автоматическим импортом библиотеки shdocvw.dll (Microsoft Internet Controls), заставляет задуматься о том, как доработать реализацию TWebBrowser до более высокого уровня.

Введение

Компоненты с названиями TWebBrowser из библиотеки VCL или WebBrowser из NET Framework 2.0 вызывают неподдельный интерес, так как предоставляют возможность не только отображения документов HTML, но и автоматизации браузера. Но если компонент System.Windows.Forms.WebBrowser предлагает богатый инструментарий для управления своим поведением, то минимум, реализованный в TWebBrowser, причем реализованный автоматическим импортом библиотеки shdocvw.dll (Microsoft Internet Controls) , заставляет задуматься о том, как доработать TWebBrowser.

ПРИМЕЧАНИЕ

Реализовать компонент TWebBrowser из VCL можно за считанные секунды следующим образом. Запустив среду Delphi, выберите в меню "Component" / "Import Component…", импортируйте библиотеку типов из shdocvw.dll - и компонент готов! Созданный средой в процессе импорта модуль SHDocVw_TLB.pas представляет собой модуль SHDocVw.pas из стандартной поставки Delphi.

Исходные тесты примеров к статье снабжены файлами проектов для BDS 2006. Однако никаких возможностей BDS 2006, несовместимых с Delphi 2005 и, возможно, Delphi 7, в исходных текстах и ресурсах не используется. При упоминании VCL я имею в виду исключительно Win32 VCL, а не NET VCL. Кроме того, под браузером понимается Internet Explorer.

"Что не устраивает?"

Создадим форму Form1: TForm , разместив на ней компоненты WebBrowser1: TWebBrowser , Edit1: TEdit для ввода URL и кнопку Button1: TButton , при нажатии на которую WebBrowser1: TWebBrowser (главный герой нашего повествования) будет выполнять переход по URL. В обработчике нажатия на кнопку воспользуемся одним из методов TWebBrowser.Navigate для отображения браузером страницы с заданным URL. Должно получиться примерно так:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, StdCtrls;

type
  TForm1 = class(TForm)
    WebBrowser1: TWebBrowser; { главный герой нашего повествования }
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  WebBrowser1.Navigate(Edit1.Text);
end;

end.

Результат впечатляет. Действительно заметно, что Delphi - среда быстрой разработки программ: несколько движений мышью, строка кода в обработчике, и перед нами готовый браузер! С другой стороны, не вызывает сомнения, что за всем этим скрывается мощь технологии СОМ, без которой нам также не обойтись.

Фактически, созданное приложение предлагает своему пользователю многие возможности Internet Explorer, который и скрывается за TWebBrowser"ом. Можно пользоваться "горячими" клавишами Ctrl+O (диалог открытия новой страницы), Ctrl+N (новое окно), Ctrl+P (вывод на печать), Сtrl+S (сохранить), Ctrl+A (выделить все), Ctrl+C (копировать), Backspace (переход назад), F5 (обновить) и т.д., а также вызывать контекстное меню для просмотра HTML-кода страницы. Однако если не планируется предоставлять пользователю эти возможности, то от TWebBrowser"а остается неприятный осадок, поскольку простого пути для управления этой функциональностью разработчики VCL не предусмотрели.

Стратегия захвата

Для запрета "горячих" клавиш и контекстного меню можно использовать SetWindowsHookEx . Однако такой подход дает не слишком много.

Интуиция разработчика под Windows заставляет подозревать, что без COM ничего не происходит. Именно, заглянув в MSDN / Web Development / Internet Explorer Development / Programming and Reusing the Browser , становится очевидным, что, реализовав интерфейсы IDocHostUIHandler и IDocHostShowUI , помимо желаемого запрета на "горячие" клавиши и контекстное меню, удается получить возможность расширения DHTML, контроля над диалоговыми окнами и справкой, а также управления многими другими функциями браузера.

Естественно, хотелось бы автоматизировать процесс создания модуля с соответствующими объявлениями интерфейсов. Для этого возьмем файл MsHtmHst.Idl из Platform SDK. Отметив, что в нем отсутствует секция library, добавим ее, уберем cpp_quote и pragma, и получим:

import "ocidl.idl";
import "objidl.idl";
import "oleidl.idl";
import "oaidl.idl";
import "docobj.idl";

[
  uuid(051AF297-E6C8-4d15-A3C7-239CCC2CE906),
  version(1.0),
  helpstring("MSHTMLCustomUI Library")
]
library MSHTMLCustomUI
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");

  [
    object,
    uuid(53DEC138-A51E-11d2-861E-00C04FA35C89),
    pointer_default(unique),
    local
  ]
  interface IHostDialogHelper : IUnknown
  {
    HRESULT ShowHTMLDialog(
            HWND hwndParent,
            IMoniker *pMk,
            VARIANT *pvarArgIn,
            WCHAR *pchOptions,
            VARIANT *pvarArgOut,
            IUnknown *punkHost
            );
  };

  [
    uuid(429AF92C-A51F-11d2-861E-00C04FA35C89)
  ]
  coclass HostDialogHelper
  {
    [default] interface IHostDialogHelper;
  };

  typedef enum tagDOCHOSTUITYPE 
  {
    DOCHOSTUITYPE_BROWSE  = 0,
    DOCHOSTUITYPE_AUTHOR  = 1,
  } DOCHOSTUITYPE;

  typedef enum tagDOCHOSTUIDBLCLK 
  {
    DOCHOSTUIDBLCLK_DEFAULT     = 0,
    DOCHOSTUIDBLCLK_SHOWPROPERTIES  = 1,
    DOCHOSTUIDBLCLK_SHOWCODE    = 2,
  } DOCHOSTUIDBLCLK ;

  typedef enum tagDOCHOSTUIFLAG 
  {
    DOCHOSTUIFLAG_DIALOG          = 0x00000001,
    DOCHOSTUIFLAG_DISABLE_HELP_MENU     = 0x00000002,
    DOCHOSTUIFLAG_NO3DBORDER        = 0x00000004,
    DOCHOSTUIFLAG_SCROLL_NO         = 0x00000008,
    DOCHOSTUIFLAG_DISABLE_SCRIPT_INACTIVE   = 0x00000010,
    DOCHOSTUIFLAG_OPENNEWWIN        = 0x00000020,
    DOCHOSTUIFLAG_DISABLE_OFFSCREEN     = 0x00000040,
    DOCHOSTUIFLAG_FLAT_SCROLLBAR      = 0x00000080,
    DOCHOSTUIFLAG_DIV_BLOCKDEFAULT      = 0x00000100,
    DOCHOSTUIFLAG_ACTIVATE_CLIENTHIT_ONLY   = 0x00000200,
    DOCHOSTUIFLAG_OVERRIDEBEHAVIORFACTORY   = 0x00000400,
    DOCHOSTUIFLAG_CODEPAGELINKEDFONTS     = 0x00000800,
    DOCHOSTUIFLAG_URL_ENCODING_DISABLE_UTF8 = 0x00001000,
    DOCHOSTUIFLAG_URL_ENCODING_ENABLE_UTF8  = 0x00002000,
    DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE = 0x00004000,
    DOCHOSTUIFLAG_ENABLE_INPLACE_NAVIGATION = 0x00010000,
    DOCHOSTUIFLAG_IME_ENABLE_RECONVERSION   = 0x00020000,
    DOCHOSTUIFLAG_THEME           = 0x00040000,
    DOCHOSTUIFLAG_NOTHEME           = 0x00080000,
    DOCHOSTUIFLAG_NOPICS          = 0x00100000,
    DOCHOSTUIFLAG_NO3DOUTERBORDER       = 0x00200000,
    DOCHOSTUIFLAG_DISABLE_EDIT_NS_FIXUP   = 0x00400000,
    DOCHOSTUIFLAG_LOCAL_MACHINE_ACCESS_CHECK= 0x00800000,
    DOCHOSTUIFLAG_DISABLE_UNTRUSTEDPROTOCOL = 0x01000000,
  } DOCHOSTUIFLAG ;

  [
    object,
    uuid(bd3f23c0-d43e-11cf-893b-00aa00bdce1a),
    pointer_default(unique),
    local
  ]
  interface IDocHostUIHandler : IUnknown
  {
    typedef struct _DOCHOSTUIINFO
    {
      ULONG     cbSize;
      DWORD     dwFlags;
      DWORD     dwDoubleClick;
      OLECHAR *   pchHostCss;
      OLECHAR *   pchHostNS;
    } DOCHOSTUIINFO;

    HRESULT ShowContextMenu(
      [in] DWORD dwID,
      [in] POINT* ppt,
      [in] IUnknown* pcmdtReserved,
      [in] IDispatch* pdispReserved);
    HRESULT GetHostInfo([in, out] DOCHOSTUIINFO * pInfo);
    HRESULT ShowUI(
      [in] DWORD dwID,
      [in] IOleInPlaceActiveObject * pActiveObject,
      [in] IOleCommandTarget * pCommandTarget,
      [in] IOleInPlaceFrame * pFrame,
      [in] IOleInPlaceUIWindow * pDoc);
    HRESULT HideUI();
    HRESULT UpdateUI();
    HRESULT EnableModeless([in] BOOL fEnable);
    HRESULT OnDocWindowActivate([in] BOOL fActivate);
    HRESULT OnFrameWindowActivate([in] BOOL fActivate);
    HRESULT ResizeBorder(
      [in] LPCRECT prcBorder,
      [in] IOleInPlaceUIWindow * pUIWindow,
      [in] BOOL fRameWindow);
    HRESULT TranslateAccelerator(
      [in] LPMSG lpMsg,
      [in] const GUID * pguidCmdGroup,
      [in] DWORD nCmdID);
    HRESULT GetOptionKeyPath([out] LPOLESTR * pchKey, [in] DWORD dw);
    HRESULT GetDropTarget(
      [in] IDropTarget * pDropTarget,
      [out] IDropTarget ** ppDropTarget);
    HRESULT GetExternal([out] IDispatch **ppDispatch);
    HRESULT TranslateUrl([in] DWORD dwTranslate, [in] OLECHAR *pchURLIn,
      [out] OLECHAR **ppchURLOut);
    HRESULT FilterDataObject([in]IDataObject *pDO, 
      [out] IDataObject **ppDORet);
  }

  [
    object,
    uuid(3050f6d0-98b5-11cf-bb82-00aa00bdce0b),
    pointer_default(unique),
    local
  ]
  interface IDocHostUIHandler2 : IDocHostUIHandler
  {
    HRESULT GetOverrideKeyPath([out] LPOLESTR * pchKey, [in] DWORD dw);
  }

  [
    object,
    uuid(3050f3f0-98b5-11cf-bb82-00aa00bdce0b),
    pointer_default(unique),
    local
  ]
  interface ICustomDoc : IUnknown
  {
    HRESULT SetUIHandler(
      [in] IDocHostUIHandler * pUIHandler);
  }

  [
    object,
    uuid(c4d244b0-d43e-11cf-893b-00aa00bdce1a),
    pointer_default(unique),
    local
  ]
  interface IDocHostShowUI : IUnknown
  {
    HRESULT ShowMessage(
      [in] HWND hwnd,
      [in] LPOLESTR lpstrText,
      [in] LPOLESTR lpstrCaption,
      [in] DWORD dwType,
      [in] LPOLESTR lpstrHelpFile,
      [in] DWORD dwHelpContext,
      [out] LRESULT * plResult);
    HRESULT ShowHelp(
      [in] HWND hwnd,
      [in] LPOLESTR pszHelpFile,
      [in] UINT uCommand,
      [in] DWORD dwData,
      [in] POINT ptMouse,
      [out] IDispatch * pDispatchObjectHit);
  }

  [
      object,
        uuid(342D1EA0-AE25-11D1-89C5-006008C3FBFC),
      pointer_default(unique),
      local
  ]
  interface IClassFactoryEx : IClassFactory
  {
      HRESULT CreateInstanceWithContext(IUnknown *punkContext, 
        IUnknown *punkOuter, REFIID riid, [out] void **ppv);
  };

  [
    object,
    pointer_default(unique),
    uuid(3050f5fc-98b5-11cf-bb82-00aa00bdce0b)
  ]
  interface IHTMLOMWindowServices : IUnknown
  {
    HRESULT moveTo([in] LONG x,[in] LONG y);
    HRESULT moveBy([in] LONG x,[in] LONG y);
    HRESULT resizeTo([in] LONG x,[in] LONG y);
    HRESULT resizeBy([in] LONG x,[in] LONG y);
  };

}

Полученный IDL-текст с помощью транслятора MIDL из Platform SDK (его можно найти также в каталоге Bin среды Delphi) превращаем в библиотеку типов TLB, которая очень просто импортируется с помощью команд "Component" / "Import Component…"

ПРИМЕЧАНИЕ

Чтобы не тратить время, пользуйтесь готовыми файлами из архива к статье.

Для завершения создания необходимой инфраструктуры наследуем новый компонент от TWebBrowser следующим образом:

TMyWebBrowser = class(TWebBrowser, IDocHostUIHandler, IDocHostShowUI, IDispatch)

или, подробнее:

TMyWebBrowser = class(TWebBrowser, IDocHostUIHandler, IDocHostShowUI, IDispatch)
private
  FExternalDispatch: IDispatch;
  FBindedNames: TStringList;
  FScrollEnabled: boolean;
  FAcceleratorsEnabled: boolean;
  FBorder3DEnabled: boolean;
  FContextMenuEnabled: boolean;
  procedure SetAcceleratorsEnabled(const Value: boolean);
  procedure SetBorder3DEnabled(const Value: boolean);
  procedure SetContextMenuEnabled(const Value: boolean);
  procedure SetScrollEnabled(const Value: boolean);
protected
  // IDocHostUIHandler
  function ShowContextMenu(dwID: LongWord; var ppt: tagPOINT; 
    const pcmdtReserved: IUnknown; 
    const pdispReserved: IDispatch): HResult; stdcall;
  function GetHostInfo(var pInfo: _DOCHOSTUIINFO): HResult; stdcall;
  function ShowUI(dwID: LongWord; 
    const pActiveObject: IOleInPlaceActiveObject; 
    const pCommandTarget: IOleCommandTarget; 
    const pFrame: IOleInPlaceFrame; 
    const pDoc: IOleInPlaceUIWindow): HResult; stdcall;
  function HideUI: HResult; stdcall;
  function UpdateUI: HResult; stdcall;
  function EnableModeless(fEnable: Integer): HResult; stdcall;
  function OnDocWindowActivate(fActivate: Integer): HResult; stdcall;
  function OnFrameWindowActivate(fActivate: Integer): HResult; stdcall;
  function ResizeBorder(var prcBorder: tagRECT; 
    const pUIWindow: IOleInPlaceUIWindow; 
    fRameWindow: Integer): HResult; stdcall;
  function TranslateAccelerator(var lpmsg: tagMSG; 
    var pguidCmdGroup: TGUID; nCmdID: LongWord): HResult; stdcall;
  function GetOptionKeyPath(out pchKey: PWideChar; 
    dw: LongWord): HResult; stdcall;
  function GetDropTarget(const pDropTarget: IDropTarget; 
    out ppDropTarget: IDropTarget): HResult; stdcall;
  function GetExternal(out ppDispatch: IDispatch): HResult; stdcall;
  function TranslateUrl(dwTranslate: LongWord; var pchURLIn: Word; 
    out ppchURLOut: PWord1): HResult; stdcall;
  function FilterDataObject(const pDO: IDataObject; 
    out ppDORet: IDataObject): HResult; stdcall;

  // IDocHostShowUI
  function ShowMessage(var hwnd: _RemotableHandle; 
    lpstrText: PWideChar; lpstrCaption: PWideChar; 
    dwType: LongWord; lpstrHelpFile: PWideChar; 
    dwHelpContext: LongWord; out plResult: LONG_PTR): HResult; stdcall;
  function ShowHelp(var hwnd: _RemotableHandle; 
    pszHelpFile: PWideChar; uCommand: SYSUINT; 
    dwData: LongWord; ptMouse: tagPOINT; 
    const pDispatchObjectHit: IDispatch): HResult; stdcall;

  // IDispatch
  function GetIDsOfNames(const IID: TGUID; Names: 
    Pointer; NameCount, LocaleID: Integer; 
    DispIDs: Pointer): HResult; stdcall;
  function Invoke(DispID: Integer; const IID: TGUID; 
    LocaleID: Integer; Flags: Word; var Params; VarResult, 
    ExcepInfo, ArgErr: Pointer): HResult; stdcall;

 public
  constructor Create(AOwner: TComponent); override;
  procedure SetExternalDispatch(const ppDispatch: IDispatch);
  function InvokeScript(ScriptName: WideString; 
    ScriptParams: array of OleVariant): OleVariant;
  procedure Bind(Name: WideString; Func: TMyGenericFunction);
  procedure Unbind(Name: WideString);

published
  property ContextMenuEnabled: boolean 
    read FContextMenuEnabled write SetContextMenuEnabled default False;
  property AcceleratorsEnabled: boolean 
    read FAcceleratorsEnabled write SetAcceleratorsEnabled default False;
  property Border3DEnabled: boolean 
    read FBorder3DEnabled write SetBorder3DEnabled default False;
  property ScrollEnabled: boolean 
    read FScrollEnabled write SetScrollEnabled default False;
end;

Нажав Ctrl+Shift+C, когда курсор находится внутри объявления класса, получаем пустую реализацию методов. Функции на первых порах могут содержать всего одну строчку:

Result := E_NOTIMPL;

дающую понять вызывающей стороне, что функция не реализована. Если функция возвращает S_OK , то считается, что необходимые действия (например, вывод контекстного меню) ею произведены; возврат S_FALSE , напротив, обозначает, что после вызова функции браузер задействует свою стандартную реакцию. За деталями реализации можно обратиться к архиву с исходными текстами.

Расширяем DOM (Document Object Model)

Внимательный читатель заметит, что помимо IDocHostUIHandler и IDocHostShowUI , помогающих контролировать меню, "горячие" клавиши, вывод диалоговых окон и справки, класс TMyWebBrowser реализует еще и IDispatch . Зачем же? Короткий ответ: для расширения объектной модели документа HTML (DOM).

Однако если браузер встречает в скрипте конструкцию вида:

window.external.что_то

где что_то - некоторый идентификатор, то, при условии, что реализован интерфейс IDocHostUIHandler , вызывается метод IDocHostUIHandler.GetExternal(out ppDispatch: IDispatch) , возвращающий ссылку на объект, поддерживающий IDispatch , в надежде, что объект знает, что такое что_то и сумеет произвести необходимые действия: получение значения что_то (приведенная строчка кода), присвоение значения что_то , вызов метода что_то() . Далее как обычно в Automation, следует вызов IDispatch.GetIDsOfNames и, в случае успеха, вызов IDispatch.Invoke , который и получает значения что_то (приведенная строчка кода), присвоение значения что_то , вызов метода что_то().

Ясно, что это путь к вызову Object Pascal-кода из скриптов. Остается только научиться принимать в вызываемой функции и передавать ей в вызове Invoke произвольное количество параметров, в противном случае решение не будет общим и, как следствие, полезным.

Для передачи произвольного количества параметров используется один параметр типа array of OleVariant , элементы которого создаются при вызове Invoke . Вторым важным условием успешного вызова процедуры Object Pascal из скрипта является вызов TMyBrowser.Bind для установления связи между идентификатором внутри скрипта и любой функцией типа:

function(Params: array of OleVariant): OleVariant of object

Естественно, подобный подход не является обязательным; можно представлять его себе как первую ступеньку на пути к реализации более удобного механизма взаимодействия скриптов и Object Pascal.

Вызов скрипта из кода на Object Pascal

Для удобства работы потребуется небольшая подготовка. Во-первых, нужно импортировать библиотеку типов mshtml.tlb (Microsoft HTML Object Library) , а во-вторых, вырезать из сгенерированного средой Delphi модуля размером 12 Мб самое интересное для нас:

unit MiniMSHTML;

{$TYPEDADDRESS OFF}
{$WARN SYMBOL_PLATFORM OFF}
{$WRITEABLECONST ON}
{$VARPROPSETTER ON}

interface

const
  IID_IHTMLElement: TGUID = '{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}';
  IID_IHTMLElementCollection: TGUID = 
    '{3050F21F-98B5-11CF-BB82-00AA00BDCE0B}';
  IID_IHTMLDocument2: TGUID = '{332C4425-26CB-11D0-B483-00C04FD90119}';
  IID_IHTMLDocument: TGUID = '{626FC520-A41E-11CF-A731-00A0C9082637}';

type
// *********************************************************************//
// DispIntf:  IHTMLElementDisp
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}</com>
// *********************************************************************//
  IHTMLElementDisp = dispinterface
    ['{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}']

    procedure setAttribute(const strAttributeName: 
      WideString; AttributeValue: OleVariant;
                          lFlags: Integer); dispid -2147417611;
    function getAttribute(const strAttributeName: WideString; 
      lFlags: Integer): OleVariant; dispid -2147417610;
    function removeAttribute(const strAttributeName: WideString; 
      lFlags: Integer): WordBool; dispid -2147417609;
    property _className: WideString dispid -2147417111;
    property id: WideString dispid -2147417110;
    property tagName: WideString readonly dispid -2147417108;
    property parentElement: IDispatch readonly dispid -2147418104;
    property style: IDispatch readonly dispid -2147418038;
    property onhelp: OleVariant dispid -2147412099;
    property onclick: OleVariant dispid -2147412104;
    property ondblclick: OleVariant dispid -2147412103;
    property onkeydown: OleVariant dispid -2147412107;
    property onkeyup: OleVariant dispid -2147412106;
    property onkeypress: OleVariant dispid -2147412105;
    property onmouseout: OleVariant dispid -2147412111;
    property onmouseover: OleVariant dispid -2147412112;
    property onmousemove: OleVariant dispid -2147412108;
    property onmousedown: OleVariant dispid -2147412110;
    property onmouseup: OleVariant dispid -2147412109;
    property document: IDispatch readonly dispid -2147417094;
    property title: WideString dispid -2147418043;
    property language: WideString dispid -2147413012;
    property onselectstart: OleVariant dispid -2147412075;
    procedure scrollIntoView(varargStart: OleVariant); dispid -2147417093;
    function contains(const pChild: IHTMLElementDisp): WordBool; 
      dispid -2147417092;
    property sourceIndex: Integer readonly dispid -2147417088;
    property recordNumber: OleVariant readonly dispid -2147417087;
    property lang: WideString dispid -2147413103;
    property offsetLeft: Integer readonly dispid -2147417104;
    property offsetTop: Integer readonly dispid -2147417103;
    property offsetWidth: Integer readonly dispid -2147417102;
    property offsetHeight: Integer readonly dispid -2147417101;
    property offsetParent: IHTMLElementDisp readonly dispid -2147417100;
    property innerHTML: WideString dispid -2147417086;
    property innerText: WideString dispid -2147417085;
    property outerHTML: WideString dispid -2147417084;
    property outerText: WideString dispid -2147417083;
    procedure insertAdjacentHTML(const where: WideString; 
      const html: WideString); dispid -2147417082;
    procedure insertAdjacentText(const where: WideString; 
      const text: WideString); dispid -2147417081;
    property parentTextEdit: IHTMLElementDisp readonly dispid -2147417080;
    property isTextEdit: WordBool readonly dispid -2147417078;
    procedure click; dispid -2147417079;
    property filters: IDispatch readonly dispid -2147417077;
    property ondragstart: OleVariant dispid -2147412077;
    function toString: WideString; dispid -2147417076;
    property onbeforeupdate: OleVariant dispid -2147412091;
    property onafterupdate: OleVariant dispid -2147412090;
    property onerrorupdate: OleVariant dispid -2147412074;
    property onrowexit: OleVariant dispid -2147412094;
    property onrowenter: OleVariant dispid -2147412093;
    property ondatasetchanged: OleVariant dispid -2147412072;
    property ondataavailable: OleVariant dispid -2147412071;
    property ondatasetcomplete: OleVariant dispid -2147412070;
    property onfilterchange: OleVariant dispid -2147412069;
    property children: IDispatch readonly dispid -2147417075;
    property all: IDispatch readonly dispid -2147417074;
  end;

// *********************************************************************//
// DispIntf:  IHTMLElementCollectionDisp
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {3050F21F-98B5-11CF-BB82-00AA00BDCE0B}</com>
// *********************************************************************//
  IHTMLElementCollectionDisp = dispinterface
    ['{3050F21F-98B5-11CF-BB82-00AA00BDCE0B}']
    function toString: WideString; dispid 1501;
    property length: Integer dispid 1500;
    property _newEnum: IUnknown readonly dispid -4;
    function item(name: OleVariant; index: OleVariant): IDispatch; dispid 0;
    function tags(tagName: OleVariant): IDispatch; dispid 1502;
  end;

// *********************************************************************//
// DispIntf:  IHTMLDocument2Disp
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {332C4425-26CB-11D0-B483-00C04FD90119}</com>
// *********************************************************************//
  IHTMLDocument2Disp = dispinterface
    ['{332C4425-26CB-11D0-B483-00C04FD90119}']
    property all: IHTMLElementCollectionDisp readonly dispid 1003;
    property body: IHTMLElementDisp readonly dispid 1004;
    property activeElement: IHTMLElementDisp readonly dispid 1005;
    property images: IHTMLElementCollectionDisp readonly dispid 1011;
    property applets: IHTMLElementCollectionDisp readonly dispid 1008;
    property links: IHTMLElementCollectionDisp readonly dispid 1009;
    property forms: IHTMLElementCollectionDisp readonly dispid 1010;
    property anchors: IHTMLElementCollectionDisp readonly dispid 1007;
    property title: WideString dispid 1012;
    property scripts: IHTMLElementCollectionDisp readonly dispid 1013;
    property designMode: WideString dispid 1014;
    property selection: IDispatch readonly dispid 1017;
    // property selection: IHTMLSelectionObject readonly dispid 1017;
    property readyState: WideString readonly dispid 1018;
    property frames: IDispatch readonly dispid 1019;
    // property frames: IHTMLFramesCollection2 readonly dispid 1019;
    property embeds: IHTMLElementCollectionDisp readonly dispid 1015;
    property plugins: IHTMLElementCollectionDisp readonly dispid 1021;
    property alinkColor: OleVariant dispid 1022;
    property bgColor: OleVariant dispid -501;
    property fgColor: OleVariant dispid -2147413110;
    property linkColor: OleVariant dispid 1024;
    property vlinkColor: OleVariant dispid 1023;
    property referrer: WideString readonly dispid 1027;
    property location: IDispatch readonly dispid 1026;
    // property location: IHTMLLocation readonly dispid 1026;
    property lastModified: WideString readonly dispid 1028;
    property url: WideString dispid 1025;
    property domain: WideString dispid 1029;
    property cookie: WideString dispid 1030;
    property expando: WordBool dispid 1031;
    property charset: WideString dispid 1032;
    property defaultCharset: WideString dispid 1033;
    property mimeType: WideString readonly dispid 1041;
    property fileSize: WideString readonly dispid 1042;
    property fileCreatedDate: WideString readonly dispid 1043;
    property fileModifiedDate: WideString readonly dispid 1044;
    property fileUpdatedDate: WideString readonly dispid 1045;
    property security: WideString readonly dispid 1046;
    property protocol: WideString readonly dispid 1047;
    property nameProp: WideString readonly dispid 1048;
    procedure write(psarray: {??PSafeArray}OleVariant); dispid 1054;
    procedure writeln(psarray: {??PSafeArray}OleVariant); dispid 1055;
    function open(const url: WideString; name: OleVariant; 
      features: OleVariant; replace: OleVariant): IDispatch; dispid 1056;
    procedure close; dispid 1057;
    procedure clear; dispid 1058;
    function queryCommandSupported(
      const cmdID: WideString): WordBool; dispid 1059;
    function queryCommandEnabled(
      const cmdID: WideString): WordBool; dispid 1060;
    function queryCommandState(
      const cmdID: WideString): WordBool; dispid 1061;
    function queryCommandIndeterm(
      const cmdID: WideString): WordBool; dispid 1062;
    function queryCommandText(
      const cmdID: WideString): WideString; dispid 1063;
    function queryCommandValue(
      const cmdID: WideString): OleVariant; dispid 1064;
    function execCommand(const cmdID: WideString; showUI: WordBool; 
      value: OleVariant): WordBool; dispid 1065;
    function execCommandShowHelp(
      const cmdID: WideString): WordBool; dispid 1066;
    function createElement(
      const eTag: WideString): IHTMLElementDisp; dispid 1067;
    property onhelp: OleVariant dispid -2147412099;
    property onclick: OleVariant dispid -2147412104;
    property ondblclick: OleVariant dispid -2147412103;
    property onkeyup: OleVariant dispid -2147412106;
    property onkeydown: OleVariant dispid -2147412107;
    property onkeypress: OleVariant dispid -2147412105;
    property onmouseup: OleVariant dispid -2147412109;
    property onmousedown: OleVariant dispid -2147412110;
    property onmousemove: OleVariant dispid -2147412108;
    property onmouseout: OleVariant dispid -2147412111;
    property onmouseover: OleVariant dispid -2147412112;
    property onreadystatechange: OleVariant dispid -2147412087;
    property onafterupdate: OleVariant dispid -2147412090;
    property onrowexit: OleVariant dispid -2147412094;
    property onrowenter: OleVariant dispid -2147412093;
    property ondragstart: OleVariant dispid -2147412077;
    property onselectstart: OleVariant dispid -2147412075;
    function elementFromPoint(x: Integer; 
      y: Integer): IHTMLElementDisp; dispid 1068;
    property parentWindow: IDispatch readonly dispid 1034;
    property styleSheets: IDispatch readonly dispid 1069;
    property onbeforeupdate: OleVariant dispid -2147412091;
    property onerrorupdate: OleVariant dispid -2147412074;
    function toString: WideString; dispid 1070;
    function createStyleSheet(const bstrHref: WideString; 
      lIndex: Integer): IDispatch; dispid 1071;
    property Script: IDispatch readonly dispid 1001;
  end;

// *********************************************************************//
// DispIntf:  IHTMLDocument3Disp
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {3050F485-98B5-11CF-BB82-00AA00BDCE0B}</com>
// *********************************************************************//
  IHTMLDocument3Disp = dispinterface
    ['{3050F485-98B5-11CF-BB82-00AA00BDCE0B}']
    procedure releaseCapture; dispid 1072;
    procedure recalc(fForce: WordBool); dispid 1073;
    function createTextNode(const text: WideString): IDispatch; dispid 1074;
    property documentElement: IHTMLElementDisp readonly dispid 1075;
    property uniqueID: WideString readonly dispid 1077;
    function attachEvent(const event: WideString; 
      const pdisp: IDispatch): WordBool; dispid -2147417605;
    procedure detachEvent(const event: WideString; 
      const pdisp: IDispatch); dispid -2147417604;
    property onrowsdelete: OleVariant dispid -2147412050;
    property onrowsinserted: OleVariant dispid -2147412049;
    property oncellchange: OleVariant dispid -2147412048;
    property ondatasetchanged: OleVariant dispid -2147412072;
    property ondataavailable: OleVariant dispid -2147412071;
    property ondatasetcomplete: OleVariant dispid -2147412070;
    property onpropertychange: OleVariant dispid -2147412065;
    property dir: WideString dispid -2147412995;
    property oncontextmenu: OleVariant dispid -2147412047;
    property onstop: OleVariant dispid -2147412044;
    function createDocumentFragment: IHTMLDocument2Disp; dispid 1076;
    property parentDocument: IHTMLDocument2Disp readonly dispid 1078;
    property enableDownload: WordBool dispid 1079;
    property baseUrl: WideString dispid 1080;
    property childNodes: IDispatch readonly dispid -2147417063;
    property inheritStyleSheets: WordBool dispid 1082;
    property onbeforeeditfocus: OleVariant dispid -2147412043;
    function getElementsByName(
      const v: WideString): IHTMLElementCollectionDisp; dispid 1086;
    function getElementById(
      const v: WideString): IHTMLElementDisp; dispid 1088;
    function getElementsByTagName(
      const v: WideString): IHTMLElementCollectionDisp; dispid 1087;
  end;

implementation

end.

СОВЕТ

Можно, конечно, пользоваться и сгенерированным модулем в 12 Мб. Однако, кроме того, что бОльшая его часть не поможет пониманию обсуждаемой темы, придется мириться с тем, что работает среда медленнее (даже на моей рабочей машине с двуядерным Athlon-64 3800 и 1Гб оперативной памяти).

После изучения объявления интерфейса IHTMLDocument2Disp , остается только узнать, что именно его свойство Script: IDispatch позволяет осуществлять вызов скриптов с помощью IDispatch.GetIDsOfNames и IDispatch.Invoke .

В классе TMyWebBrowser вызов скрипта производит функция TMyWebBrowser.InvokeScript(ScriptName: WideString; ScriptParams: array of OleVariant): OleVariant , принимая в качестве параметров имя скрипта и его параметры и возвращая результат, если это предусмотрено в скрипте. Детали данного варианта реализации, как обычно, можно изучить по исходным текстам из архива к статье.

Разбор HTML

Думаю, ни у кого не вызовет сомнения то, что браузер обязан производить синтаксический разбор HTML. Разбор осложняется тем, что есть несколько стандартов, и немалое количество разработчиков (в том числе, разработчиков браузеров) и Web-дизайнеров, понимающих эти стандарты по-своему.

Уверен, если вам придется производить синтаксический разбор HTML, то захочется воспользоваться услугами уже готовой библиотеки. Для Windows-программиста может оказаться интересным использование возможностей упоминавшейся ранее mshtml.tlb (Microsoft HTML Object Library) , которая всегда под рукой, с помощью, например, Automation.

Вот простой пример, рисующий дерево разбора с помощью TTreeView :

procedure TMainForm.MyWebBrowserDocumentComplete(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);

    procedure Build(collection: OleVariant; Root: TTreeNode);
    var
      elem: OleVariant;
      i: integer;
      tag: WideString;
    begin
      for i := 0 to collection.length - 1 do
      begin
        elem := collection.item(i, tag);
        tag := elem.tagName;
        DocTreeView.Items.AddChild(Root, tag);
        Build(elem.children, 
          DocTreeView.Items[DocTreeView.Items.Count-1]);
        end;
    end;

var
  doc: OleVariant;
begin
  doc := MyWebBrowser.Document;
  DocTreeView.Items.Clear;

  Build(doc.all.item(0, '').children, DocTreeView.TopItem);
  Build(doc.all.item(1, '').children, DocTreeView.TopItem);
end;

В заключение раздела замечу, что у интерфейса элемента HTML (см. файл MiniMSHTML.pas) имеется немало свойств, позволяющих менять оформление и содержание уже загруженного документа так же, как это обычно делается с помощью скриптов.

Протокол res:// или Как сделать 2 в 1

Если в программе приходится отображать HTML, то можно хранить HTML в ресурсах вместо загрузки с помощью TWebBrowser.Navigate и/или вместо TWebBrowser.Document.Write(длинная_строка_с_HTML). При таком вызове, конечно же, необходимо приводить тип свойства Document к IHTMLDocument2 или (что и делает компилятор в первом случае) запрашивать ссылку на интерфейс IHTMLDocument2 или приводить тип Document к OleVariant и пользоваться Automation. Преимущество хранения HTML в ресурсах состоит в том, что потеря необходимых приложению данных, находящихся в ресурсах, может произойти только вместе с потерей файла с исполнимым кодом. Сохраненные в ресурсах данные извлекаются при необходимости с помощью FindResource и LoadResource.

Однако такой подход затрудняет использование ссылок в HTML.

Для удобного использования возможностей HTML в сочетании с хранением HTML в ресурсах, в браузер, начиная с Internet Explorer 4.0, была встроена поддержка протокола res. Адрес в этом протоколе задается как res://sFile[/sType]/sID , где sFile, sType, sID представляют собой, соответственно, путь к образу с ресурсами, тип ресурса и идентификатор ресурса. Если тип не указан, то предполагается, что тип представляет собой RT_HTML.

Для использования на практике этих сведений создадим файл, который откомпилируем с помощью brcc32 - компилятора ресурсов от Borland, поставляющегося вместе с Delphi:

INDEX       RT_HTML     "page.html"
ABOUT       RT_HTML     "about.html"
ABOUTLOGO   RT_RCDATA   "logo.gif"

При этом нужно указать компоновщику добавить результат компиляции (бинарный файл ресурсов) к генерируемому файлу с помощью следующей строки, которую необходимо добавить в исходный текст проекта (dpr):

{$R 'html_res.res' 'html_res.rc'}

С помощью следующего кода сохраненная в ресурсах страница page.html отображается TWebBrowser"ом при создании формы:

procedure TForm1.FormCreate(Sender: TObject);
begin
    MyWebBrowser1.Navigate('res://' + ParamStr(0) + '/RT_HTML/INDEX');
end;

Вот так можно задать ссылку на рисунок в HTML-документе:

<img src="/RT_RCDATA/ABOUTLOGO" alt="logo" />

Аналогичным образом задаются ссылки на другие документы. Пример можно найти в архиве с исходными текстами к статье.

Заключение

В настоящей статье были представлены методы управления интерфейсом пользователя компонента TWebBrowser, работы с DOM, вызова скриптов из кода на Object Pascal, вызова кода Object Pascal из скриптов. Практическое значение имеют также примеры работы с элементами документа HTML с помощью Automation и хранения HTML в ресурсах приложения.

Литература

  1. MSDN Library / Web Development
  2. Roberts, Scott. Programming Microsoft Internet Explorer 5, ISBN 0-7356-0781-8, 1999

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=20307