본문 바로가기

프로그래밍/Delphi

[강좌-Delphi]웹브라우저(TWebBrowser)의 사용자 인터페이스 커스터마이징


1. 소개

  델파이 포럼이나 뉴스 그룹에서 TWebBrowser에 대한 질문 중에 가장 많이 올라오는 것을 추려 보면 다음 

  3가지로 요약 됩니다.

   1) TWebBrowser에서 기본 팝업 메뉴를 변경하는 방법

   2) 웹브라우저의 테두리를 변경할 수 있는 방법

   3) 웹브라우저의 스크롤바를 없애는 방법 


  이에 본 글에서는 다음과 같은 관련된 사항들을 정리해 보고 해답을 찾고자 합니다.

       - 어플리케이션에 임베딩 된 웹브라우저의 Display를 런타임시에 변경이 가능하도록 하는 방법과 웹페이지의 

   대화상자에 HTML 콘텐츠를 추가 하거나 HTML을 대화상자의 일부로 표시하는 방법

       - 웹페이지의 대화상자에서 일반적으로는 노출된 TEXT를 복사할 수 있는데 복사를 방지하는 방법

       - 어플리케이션이 윈도우 테마를 사용하는 경우 웹페이지내에 노출된 컨트롤(예 : 버튼)을 윈도우 테마를 사용하

         도록 하는 방법


  자주하는 질문에 대한 해결방안으로는  

1)항

웹브라우저의 UI를 관리하는 인터페이스인 IDocHostUIHandler를 구현하는 개체를 만들고 이 개체를 사용하여 팝업 메뉴를 제어할 수 있습니다.

2)항과 3)항

단순하게 웹브라우저의 DOM 객체의 속성들을 적절히 변경하는 방법을 통해 해결이 가능합니다. 이때 DOM은 웹페이지가 완전히 로딩이 완료 되어야만 완성이 되므로 웹페이지 로딩이 완료 될 때까지 기다려야만 합니다. 이 방법 말고 IDocHostHandler를 구현하여 테두리 또는 스크롤바에 대한 표시 설정을 변경할 수 있습니다.

  IDocHostUIHandler를 이용하면 브러우저(TWebBrowser) 개체에 기본 CSS(Cascading Style Sheet)를 제공할 수 

  있고, 폼의 색상과 글꼴을 알아내고, 브라우저에서 사용될 CSS를 런타임에 지정할 수 있습니다.


  이 글을 통해 IDocHostUIHandler를 커스터마이징하는 방법과 TWebBrowser가 객체를 이용하는 방법에 대해 배울 

  수 있도록 했습니다.




2. 해결 방안 살펴보기



  TWebBrowser의 콘르롤들의 표시 방법(Display)를 커스터마이징하여 메뉴(Context Menu) 컨틀롤을 얻을 수 있고 브라우저내 컨트롤 들의 속성을 쉽게 변경할 수 있도록 하는 향후 재사용 가능한 모듈을 만드는 것이 이 글의 목표 입니다.

  먼저 마이크로소프트 개발자 네트워크 라이브러리 문서에서 언급된 TWebBrowser에 대한 관련 내용을 확인 해 보겠습니다. 우리가 이용 하는 것은 MS Internet Explore(IE) 기반의 DOM 모델 사용을 기본으로 하기 때문에 MS의 백서를 확인 해 보는 것은 기본이며 백서 내용을 이해 하고 보면 수월 해 집니다.


"The mechanism for WebBrowser Control customization is designed to be automated when a container provides support for ActiveX controls. Whenever the WebBrowser Control is instantiated, it attempts to find IDocHostUIHandler, IDocHostUIHandler2 and IDocHostShowUI implementations from the host, if they are available. The WebBrowser Control does this by a QueryInterface call on the host's IOleClientSiteinterface.

"This architecture works automatically for an application that implements an IOleClientSite interface and that passes an IOleClientSite pointer to the WebBrowser Control through the browser's IOleObject::SetClientSite method."

[번역]---------------------------------------------------------------------------------------------
"WebBrowser 컨트롤 사용자 지정 메커니즘은 컨테이너 에서 ActiveX 컨트롤을 지원할 때 자동화되도록 설계되었으며 WebBrowser 컨트롤이 인스턴스화 될 때마다 호스트 에서 IDocHostUIHandler , IDocHostUIHandler2 및 IDocHostShowUI 구현을 사용할 수있는 경우 해당 개체 를 찾을 수 있습니다 .WebBrowser 컨트롤 이 작업은 호스트의 IOleClientSite 인터페이스 에서 QueryInterface 호출에 의해 수행 됩니다.
"이 아키텍처는 IOleClientSite 인터페이스를 구현하고 IOleObject :: SetClientSite 메서드를 통해 IOleClientSite 포인터를 WebBrowser 컨트롤에 전달하는 응용 프로그램에서 자동으로 작동합니다."


위 내용을 이해하기 쉽게 설명 하면

   - TWebBrowser를 운영하기 위한 컨테이너(Container)를 생성해야 합니다.

   - 이 컨테이너는 IDocHostUIHandler 인터페이스를 찾을 때 브라우저 컨트롤에서 쿼리할 수 있도록 IOleClientSite 

     인터페이스를 구현 해야만 합니다.

   - 또한 컨테이너의 IOleClientSite 인터페이스에 대한 참조를 IOleObject.SetCilentSite 메소드를 통해 브라우저 

     컨트롤에 전달하여 호스팅 되고 있음을 TWebBrowser에 등록 해야 합니다.

   - 컨테이너 개체는 IDocHostUIHandler 인터페이스도 구현 해야 하며 이 구현에서 브라우저 컨트롤에 요구되는 

사용자 비즈니스 로직을 제공해야만 합니다.(IDocHostUIHandler2, IDocHostShowUI는 사용하지 않음)

   * 컨테이너 클래스는 Public 브라우저를 사용자가 제어할 수 있는 속성들을 노출 시킬 수 있습니다.




3. 재사용 가능한 기본 클래스 작성



    TWebBrowser를 핸들링 하기 위한 모든 기능을 가진 컨테이너로 "TNulWBContainer" 클래스를 만들어 보겠습니다. 이 컨테이너 클래스를 만들기 위해 다음 내용을 알아야 합니다.

  • IOleClientSite 인터페이스에 대한 구현
  • IDocHostUIHandler 인터페이스에 대한 일반적인 구현
  • 참조 카운팅없이 IUnknown 인터페이스에 대한 구현
  • 웹브라우저 컨트롤에 컨테이너를 등록하는 방법
  • 웹브라우저 컨트롤을 하기 위한 속성(Property)를 노출 시키는 방법



   TNulWBContainer 선언

  TWebBrowser를 핸들링 하기 위한 컨테이너 "TNulWBContainer"를 정의 합니다.


type
  TNulWBContainer = class(TObject,
    IUnknown, IOleClientSite, IDocHostUIHandler
  )
  private
    fHostedBrowser: TWebBrowser;
    // Registration method
    procedure SetBrowserOleClientSite(const Site: IOleClientSite);
  public
    constructor Create(const HostedBrowser: TWebBrowser);
    destructor Destroy; override;
    property HostedBrowser: TWebBrowser read fHostedBrowser;
  end;

  일반적으로 사용자 Class를 선언하는 방식과 동일한데 상속 부분의 "TObject" 클래스와 "IKnown, IOleClientSite, IDocHostUIHandler" 인터페이스를 주의해서 보세요.


SetBrowserOleClientSite는 웹브라우저 컨트롤에 이 컨테이너를 등록 하거나 제거 하기 위한 방법을 제공 합니다.


생성자 함수를 살펴 보면 전달된 웹브라우저 컨트롤에 대한 참조를 "fHostedBrowser" 프로퍼티에 저장 합니다.

이 컨테이너 객체는 생성자에서 SetBrowserOleClientSite에 의해 웹브라우저의 핸들러로 자기 자신을 지정합니다.

이때 파라미터로 상속받은 인터페이스인 "IOleClientSite" 가 전달되게 됩니다. 

constructor TNulWBContainer.Create(const HostedBrowser: TWebBrowser);
begin
  Assert(Assigned(HostedBrowser));
  inherited Create;
  fHostedBrowser := HostedBrowser;
  SetBrowserOleClientSite(Self);
end;


소멸자를 살펴보면 SetBrowserOleClient(nil)에 의해 파라미터 nil을 지정함으로써 웹브라우저 핸들러 컨테이너 지정을 해제 하게 됩니다.


destructor TNulWBContainer.Destroy;
begin
  SetBrowserOleClientSite(nil);
  inherited;
end;


SetBrowserOleClient 함수를 구현해 봅니다. 

이 함수는 웹브라우저의 IOleObject 인터페이스를 얻고, SetClientSite에 파라미터를 넘겨 주어야 하는데 이 파라미터는 TWebBrowser컨트롤에 컨테이너를 등록하기 위한 IOleClientSite  인터페이스 개체 참조 이거나 컨테이너 등록을 해제 시키기 위한 nil 중의 하나여야 합니다. 소스는 다음과 같습니다.

procedure TNulWBContainer.SetBrowserOleClientSite(
  const Site: IOleClientSite);
var
  OleObj: IOleObject;
begin
  Assert((Site = Self as IOleClientSite) or (Site = nil));
  if not Supports(
    fHostedBrowser.DefaultInterface, IOleObject, OleObj
  ) then
    raise Exception.Create(
      'Browser''s Default interface does not support IOleObject'
    );
  OleObj.SetClientSite(Site);
end;


참조(Reference) 카운트 되지 않는 객체의 구현 


  다음 단계로 레퍼런스 참조 카운트가 되지 않은 객체를 구현 합니다. 이를 위하여 IUnknown 인터페이스를 상속 받아  필요한 인터페이스를 구현합니다. IUnKnown은 "System" Unit에 정의 되어 있으며 "protected" 섹션에 메소드를 추가 합니다.

//선언부
type
  TNulWBContainer = class(TObject,
    IUnknown, IOleClientSite, IDocHostUIHandler)
  private
    ...
  protected
    { IUnknown }
    function QueryInterface(const IID: TGUID; out Obj): HResult;
      stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    ...
  end;


// 구현부

function TNulWBContainer.QueryInterface(
  const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function TNulWBContainer._AddRef: Integer;
begin
  Result := -1;
end;

function TNulWBContainer._Release: Integer;
begin
  Result := -1;
end;


.눈여겨 볼 것은 참조 카운트가 되지 않도록 하기 위하여 _AddRef함수와 _Release함수에서 -1을 리턴 하도록 했다는 것입니다



IOleClientSite 인터페이스 구현



다음 단계로 IOleClientSite 인터페이스에 대한 구현부를 작성합니다. 생성자 부분에서 객체의 IOleClientSite 인터페이스를 브라우저 컨트롤에 등록 합니다. 브라우저 컨트롤은 등록된 "IOleClientSite" 인터페이스를 통해 QueryInterface메서드를 호출하여 IDocHostUIHandler를 Access 하려고 시도 합니다. 브라우저 개체는 이것 되에 IOleClientSite 인터페이스의 다른 기능들을 필요로 하지 않기 때문에 더이상 아무것도 하지 않는 더미 인터페이스를 구현 하여 재사용 할 수 있습니다.


IOleClientSite 인터페이스를 위한 메서드들을 다음과 같이 추가해 줍니다.

// 선언부
type
  TNulWBContainer = class(TObject,
    IUnknown, IOleClientSite, IDocHostUIHandler)
  private
    ...
  protected
    { IUnknown }
    ...
    { IOleClientSite }
    function SaveObject: HResult; stdcall;
    function GetMoniker(dwAssign: Longint;
      dwWhichMoniker: Longint;
      out mk: IMoniker): HResult; stdcall;
    function GetContainer(
      out container: IOleContainer): HResult; stdcall;
    function ShowObject: HResult; stdcall;
    function OnShowWindow(fShow: BOOL): HResult; stdcall;
    function RequestNewObjectLayout: HResult; stdcall;
    ...
  public
    ...
  end;


// 구현부
function TNulWBContainer.GetContainer(
  out container: IOleContainer): HResult;
  {Returns a pointer to the container's IOleContainer
  interface}
begin
  { We do not support IOleContainer.
    However we *must* set container to nil }
  container := nil;
  Result := E_NOINTERFACE;
end;

function TNulWBContainer.GetMoniker(dwAssign, dwWhichMoniker: Integer;
  out mk: IMoniker): HResult;
  {Returns a moniker to an object's client site}
begin
  { We don't support monikers.
    However we *must* set mk to nil }
  mk := nil;
  Result := E_NOTIMPL;
end;

function TNulWBContainer.OnShowWindow(fShow: BOOL): HResult;
  {Notifies a container when an embedded object's window
  is about to become visible or invisible}
begin
  { Return S_OK to pretend we've responded to this }
  Result := S_OK;
end;

function TNulWBContainer.RequestNewObjectLayout: HResult;
  {Asks container to allocate more or less space for
  displaying an embedded object}
begin
  { We don't support requests for a new layout }
  Result := E_NOTIMPL;
end;

function TNulWBContainer.SaveObject: HResult;
  {Saves the object associated with the client site}
begin
  { Return S_OK to pretend we've done this }
  Result := S_OK;
end;

function TNulWBContainer.ShowObject: HResult;
  {Tells the container to position the object so it is
  visible to the user}
begin
  { Return S_OK to pretend we've done this }
  Result := S_OK;
end;


  이로써 위 최소한 아무것도 하지 않는 더미 클래스는 Client Site 또는 웹브라우저 컨트롤을 핸들러 로서 동작 합니다. 


IDocHostUIHandler 구현


  이미 구현 했던 인터페이스와 다르게 델파이는 "IOleHostUIHandler" 인터페이스는 제공하지 않습니다. 따라서 다음과 같이 IDocHostUIHandler를 정의 합니다.

type
  IDocHostUIHandler = interface(IUnknown)
    ['{bd3f23c0-d43e-11cf-893b-00aa00bdce1a}']
    { Display a shortcut menu }
    function ShowContextMenu(
      const dwID: DWORD;
      const ppt: PPOINT;
      const pcmdtReserved: IUnknown;
      const pdispReserved: IDispatch): HResult; stdcall;
    { Retrieves the user interface capabilities and requirements
      of the application that is hosting the web browser }
    function GetHostInfo(
      var pInfo: TDocHostUIInfo): HResult; stdcall;
    { Enables us to replace browser menus and toolbars etc }
    function ShowUI(
      const dwID: DWORD;
      const pActiveObject: IOleInPlaceActiveObject;
      const pCommandTarget: IOleCommandTarget;
      const pFrame: IOleInPlaceFrame;
      const pDoc: IOleInPlaceUIWindow): HResult; stdcall;
    { Called when the browser removes its menus and toolbars.
      We remove menus and toolbars we displayed in ShowUI }
    function HideUI: HResult; stdcall;
    { Notifies that the command state has changed so the host
      can update toolbar buttons etc. }
    function UpdateUI: HResult; stdcall;
    { Called when a modal UI is displayed }
    function EnableModeless(
      const fEnable: BOOL): HResult; stdcall;
    { Called when the document window is activated or
      deactivated }
    function OnDocWindowActivate(
      const fActivate: BOOL): HResult; stdcall;
    { Called when the top-level frame window is activated or
      deactivated }
    function OnFrameWindowActivate(
      const fActivate: BOOL): HResult; stdcall;
    { Called when a frame or document's window's border is
      about to be changed }
    function ResizeBorder(
      const prcBorder: PRECT;
      const pUIWindow: IOleInPlaceUIWindow;
      const fFrameWindow: BOOL): HResult; stdcall;
    { Called when accelerator keys such as TAB are used }
    function TranslateAccelerator(
      const lpMsg: PMSG;
      const pguidCmdGroup: PGUID;
      const nCmdID: DWORD): HResult; stdcall;
    { Called by the web browser control to retrieve a registry
      subkey path that overrides the default IE registry settings }
    function GetOptionKeyPath(
      var pchKey: POLESTR;
      const dw: DWORD ): HResult; stdcall;
    { Called when the browser is used as a drop target and enables
      the host to supply an alternative IDropTarget interface }
    function GetDropTarget(
      const pDropTarget: IDropTarget;
      out ppDropTarget: IDropTarget): HResult; stdcall;
    { Called to obtain our IDispatch interface. Used to enable the
      browser to call methods in the host (e.g. from JavaScript) }
    function GetExternal(
      out ppDispatch: IDispatch): HResult; stdcall;
    { Gives the host an opportunity to modify the URL to be loaded }
    function TranslateUrl(
      const dwTranslate: DWORD;
      const pchURLIn: POLESTR;
      var ppchURLOut: POLESTR): HResult; stdcall;
    { Allows us to replace the web browser data object. It enables
      us to block certain clipboard formats or support additional
      clipboard formats }
    function FilterDataObject(
      const pDO: IDataObject;
      out ppDORet: IDataObject): HResult; stdcall;
  end;

  위 정의에서 참조된 인터페이스 및 구조체들은 ActiveX 또는 System 유닛에 정의 되어 있습니다. 예외 처리는 후반부에서 자세히 살혀 보도록 하겠습니다.

이미 위에서 아무것도 하지 않는 구현부를 가진 클래스를 정의 했었습니다. IDocHostUIHandle의 모든 메서드를 추가하기 위해서는 TNullWBContainer의 "Protected" 섹션에  정의 하고 다음과 같이 구현부를 만듭니다.

function TNulWBContainer.EnableModeless(
  const fEnable: BOOL): HResult;
begin
  { Return S_OK to indicate we handled (ignored) OK }
  Result := S_OK;
end;

function TNulWBContainer.FilterDataObject(
  const pDO: IDataObject;
  out ppDORet: IDataObject): HResult;
begin
  { Return S_FALSE to show no data object supplied.
    We *must* also set ppDORet to nil }
  ppDORet := nil;
  Result := S_FALSE;
end;

function TNulWBContainer.GetDropTarget(
  const pDropTarget: IDropTarget;
  out ppDropTarget: IDropTarget): HResult;
begin
  { Return E_FAIL since no alternative drop target supplied.
    We *must* also set ppDropTarget to nil }
  ppDropTarget := nil;
  Result := E_FAIL;
end;

function TNulWBContainer.GetExternal(
  out ppDispatch: IDispatch): HResult;
begin
  { Return E_FAIL to indicate we failed to supply external object.
    We *must* also set ppDispatch to nil }
  ppDispatch := nil;
  Result := E_FAIL;
end;

function TNulWBContainer.GetHostInfo(
  var pInfo: TDocHostUIInfo): HResult;
begin
  { Return S_OK to indicate UI is OK without changes }
  Result := S_OK;
end;

function TNulWBContainer.GetOptionKeyPath(
  var pchKey: POLESTR;
  const dw: DWORD): HResult;
begin
  { Return E_FAIL to indicate we failed to override
    default registry settings }
  Result := E_FAIL;
end;

function TNulWBContainer.HideUI: HResult;
begin
  { Return S_OK to indicate we handled (ignored) OK }
  Result := S_OK;
end;

function TNulWBContainer.OnDocWindowActivate(
  const fActivate: BOOL): HResult;
begin
  { Return S_OK to indicate we handled (ignored) OK }
  Result := S_OK;
end;

function TNulWBContainer.OnFrameWindowActivate(
  const fActivate: BOOL): HResult;
begin
  { Return S_OK to indicate we handled (ignored) OK }
  Result := S_OK;
end;

function TNulWBContainer.ResizeBorder(
  const prcBorder: PRECT;
  const pUIWindow: IOleInPlaceUIWindow;
  const fFrameWindow: BOOL): HResult;
begin
  { Return S_FALSE to indicate we did nothing in response }
  Result := S_FALSE;
end;

function TNulWBContainer.ShowContextMenu(
  const dwID: DWORD;
  const ppt: PPOINT;
  const pcmdtReserved: IInterface;
  const pdispReserved: IDispatch): HResult;
begin
  { Return S_FALSE to notify we didn't display a menu and to
    let browser display its own menu }
  Result := S_FALSE
end;

function TNulWBContainer.ShowUI(
  const dwID: DWORD;
  const pActiveObject: IOleInPlaceActiveObject;
  const pCommandTarget: IOleCommandTarget;
  const pFrame: IOleInPlaceFrame;
  const pDoc: IOleInPlaceUIWindow): HResult;
begin
  { Return S_OK to say we displayed own UI }
  Result := S_OK;
end;

function TNulWBContainer.TranslateAccelerator(
  const lpMsg: PMSG;
  const pguidCmdGroup: PGUID;
  const nCmdID: DWORD): HResult;
begin
  { Return S_FALSE to indicate no accelerators are translated }
  Result := S_FALSE;
end;

function TNulWBContainer.TranslateUrl(
  const dwTranslate: DWORD;
  const pchURLIn: POLESTR;
  var ppchURLOut: POLESTR): HResult;
begin
  { Return E_FAIL to indicate that no translations took place }
  Result := E_FAIL;
end;

function TNulWBContainer.UpdateUI: HResult;
begin
  { Return S_OK to indicate we handled (ignored) OK }
  Result := S_OK;
end;

  지금까지 구현한 것은 아무것도 하지 않는 재사용 가능한 기본 클래스(Base Class)입니다. TWebBrowser를 핸들링 하기 위하여 이 기본 클래스의 인스턴스를 생성할 수 있습니다. 그러나 아직은 말그대로 아무것도 안하는 기본 클래스이기 때문에 TWebBrowser를 핸들링 하는 가시적인 것이 아무것도 없는 껍데기일 뿐입니다. 다음 4.항에서 이 기본 클래스 "TNulWBContainer"를 기반으로 목적하는 작업을 할 수 있도록 추가 코드를 작성해 보겠습니다.



4. 커스터마이징 클래스 개발하기


 이번에 작성할 TWBContainer 클래스는 TNulWBContainer를 기저 클래스로 하고 있으며 웹브라우저를 커스터마이징 하기 위한 요구사항들을 추가합니다. 웹 브라우저를 커스터마이징 하여 다음과 같은 작업들을 수행 하게 할 수 있습니다.


  • 웹 브라우저의 기본 팝업 메뉴를 변경하거나, TWebBrowser.PopupMenu 프로퍼티에 메뉴를 할당하기
  • 웹 브라우저의 3D 테두리를 보이기 / 감추기
  • 웹 브라우저의 스크롤 바를 보이기 / 감추기
  • 실행중에 웹 문서의 표현을 CSS를 이용하여 변경하기
  • 브라우저 컨트롤내 텍스트 선택을 가능 / 불가능하게 하기
  • 웹브라우저내 각종 컨트롤들을 델파이로 작성한 응용 프로그램의 테마로 표시하기


  다음 소스에서 웹브라우저 커스터마이징을 위해 몇개의 프로퍼티를 정의 합니다. 또한 IDonHostUIHandler 인터페이스의 ShowContextMenu, GetHostInfo 두개의 메서드를 구현 합니다.   ShowContextMenu는 웹브라우저의 팝업 메뉴를 핸들링 하고, GetHostInfo는 웹브라우저 컨트롤의 표현방법을 사용자화된 표현방법을 사용 하도록 활성화 해 주는 역할을 합니다.


type
  TWBContainer = class(TBaseContainer,
    IDocHostUIHandler, IOleClientSite)
  private
    fUseCustomCtxMenu: Boolean;
    fShowScrollBars: Boolean;
    fShow3DBorder: Boolean;
    fAllowTextSelection: Boolean;
    fCSS: string;
  protected
    { Re-implemented IDocHostUIHandler methods }
    function ShowContextMenu(const dwID: DWORD;
      const ppt: PPOINT; const pcmdtReserved: IUnknown;
      const pdispReserved: IDispatch): HResult; stdcall;
    function GetHostInfo(var pInfo: TDocHostUIInfo): HResult; stdcall;
  public
    constructor Create(const HostedBrowser: TWebBrowser);
    property UseCustomCtxMenu: Boolean
      read fUseCustomCtxMenu write fUseCustomCtxMenu default False;
    property Show3DBorder: Boolean
      read fShow3DBorder write fShow3DBorder default True;
    property ShowScrollBars: Boolean
      read fShowScrollBars write fShowScrollBars default True;
    property AllowTextSelection: Boolean
      read fAllowTextSelection write fAllowTextSelection default True;
    property CSS: string
      read fCSS write fCSS;
  end;

  커스터마이징 하려는 웹브라우저 컨트롤 각각의 외관을 다루기 위해 몇 개의 프로퍼티를 추가 했고, 위에 추가된 프로퍼티에 대해 살펴보면


- UseCustomCtxMenu : 이 프로퍼티가 false이면 웹브라우저 컨트롤은 기본 컨텍스트 메뉴를 노출 시키고, true 

      이면 TWebBrowser의 PopupMenu에 할당된 메뉴가 사용 됩니다. UseContextMenu가 true 인데 PopupMenu

      가 할당되어 있지 않다면 팝업 메뉴는 노출되지 않습니다.


- Show3DBorder : 웹브라우저는 이 프로퍼티가 true이면 테두리를 3D로 표시하고 false이면 테두리를 표시하지

      않습니다.


- ShowScrollBars : TWebBrowser는 이 프로퍼티가 true이면 스크롤바를 노출 시킵니다.


- AllowTextSelection : 브라우저 내의 컨트롤의 TEXT를 선택 가능하도록 하려면 이 프로퍼티를 true로 지정 

      합니다.


- CSS : CSS(Cascading Stype Sheet)를 지정할 수 있습니다. 이 문자열 프로퍼티는 유효한 CSS 구문이거나 빈 

      문자열을 지정할 수 있으며 웹 문서의 표현이 이 CSS에 의해 표시 됩니다.


  다음은 TWBContainer가 생성시 처리해야 할 목적으로 생성자를 만들어 보겠습니다. 위에서 정의한 프로퍼티의 기본 설정 값을 지정하는 목적으로 사용 했습니다.

constructor TWBContainer.Create(const HostedBrowser: TWebBrowser);
begin
  inherited;
  fUseCustomCtxMenu := False;
  fShowScrollBars := True;
  fShow3DBorder := True;
  fAllowTextSelection := True;
  fCSS := '';
end;

  이번엔 IDonHostUIHandler 인터페이스의 "ShowContextMenu" 메서드를 재 구현 해보겠습니다. 


function TWBContainer.ShowContextMenu(
  const dwID: DWORD;
  const ppt: PPOINT;
  const pcmdtReserved: IInterface;
  const pdispReserved: IDispatch): HResult;
begin
  if fUseCustomCtxMenu then
  begin
    // tell IE we're handling the context menu
    Result := S_OK;
    if Assigned(HostedBrowser.PopupMenu) then
      // browser has a pop up menu so activate it
      HostedBrowser.PopupMenu.Popup(ppt.X, ppt.Y);
  end
  else
    // tell IE to use default action: display own menu
    Result := S_FALSE;
end;


  FUseCustomCtxMenu 필드 값이 FALSE 이면 브라우저의 컨텍스트 메뉴(Context Menu)를 사용하지 않는다고 S_FAIL을 리턴 해 줌으로써 브라우저의 기본 팝업 메뉴가 동작 되게 하는 효과가 있습니다.


  FUseCustomCtxMenu 필드 값이 TRUE 이면 브라우저에게 사용자가 직접 만든 컨텍스트 메뉴를 사용하겠다는 S_OK 리턴 값을 줍니다. 이렇게 하면 브라우저의 기본 팝업 메뉴는 표시되지 않게 됩니다. 대신 브라우저의 PopupMenu 프로퍼티가 지정되어 있다면 Popup 메소드를 이용해 사용자 지정 컨텍스트 메뉴를 표시할 수 있습니다. 


  파라미터 ppt는 팝업메뉴를 동작 시키기 위한 마우스 오른쪽 버튼이 클릭된 위치 정보가 제공 되며, 팝업 메뉴의 좌측 상단의 시작 위치로 사용 됩니다.



  다음은 GetHostInfo에 대해서 알아 보겠습니다.

GetHostInfo는 브라우저의 테두리, 스크롤바, 문자선택, 그리고 테마의 지원과 기본 스타일시트(CSS)의 표시 여부를 결정하기 때문에 조금 더 복잡합니다.  이러한 요구사항들을 처리하기 위해 TDocHostUIInfo 구조체를 채워 넣고 파라미터로 전달 해 줌으로써 브라우저의 행위를 결정하도록 합니다. 


TDocHostUIInfo의 선언부는 다음과 같습니다.

type
  TDocHostUIInfo = record
    cbSize: ULONG;          // size of structure in bytes
    dwFlags: DWORD;         // flags that specify UI capabilities
    dwDoubleClick: DWORD;   // specified response to double click
    pchHostCss: PWChar;     // pointer to CSS rules
    pchHostNS: PWChar;      // pointer to namespace list for custom tags
  end;


GetHostInfo 메서드는 다음과 같이 구현 됩니다.


function TWBContainer.GetHostInfo(
  var pInfo: TDocHostUIInfo): HResult;
const
  DOCHOSTUIFLAG_SCROLL_NO = $00000008;
  DOCHOSTUIFLAG_NO3DBORDER = $00000004;
  DOCHOSTUIFLAG_DIALOG = $00000001;
  DOCHOSTUIFLAG_THEME = $00040000;
  DOCHOSTUIFLAG_NOTHEME = $00080000;
begin
  try
    // Clear structure and set size
    ZeroMemory(@pInfo, SizeOf(TDocHostUIInfo));
    pInfo.cbSize := SizeOf(TDocHostUIInfo);
    // Set scroll bar visibility
    if not fShowScrollBars then
      pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_SCROLL_NO;
    // Set border visibility
    if not fShow3DBorder then
      pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;
    // Decide if text can be selected
    if not fAllowTextSelection then
      pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_DIALOG;
    // Ensure browser uses themes if application is doing
    if ThemeServices.ThemesEnabled then
      pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_THEME
    else if ThemeServices.ThemesAvailable then
      pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NOTHEME;
    // Record default CSS as Unicode
    pInfo.pchHostCss := TaskAllocWideString(fCSS);
    if not Assigned(pInfo.pchHostCss) then
      raise Exception.Create(
        'Task allocator can''t allocate CSS string'
      );
    // Return S_OK to indicate we've made changes
    Result := S_OK;
  except
    // Return E_FAIL on error
    Result := E_FAIL;
  end;
end;


여기서 TDocHostUIInfo의 cbSize, dwFlags, pchHostCss 필드들을 사용 합니다. 나머지 필드들은 "0" 값으로 세팅 했습니다. 


cbSize는 넘겨줄 구조체 TDocHostUIInfo의 사이즈를 지정 합니다.


dwFlag는 다음 기준으로 지정하면 됩니다.

DOCHOSTUIFLAG_SCROLL_NO : 수직 스크롤바의 표시를 비활성 시킵니다.

DOCHOSTUIFLAG_NO3DBORDER : 테두리를 3D로 표시하지 않도록 합니다.

DOCHOSTUIFLAG_DIALOG : 다이얼로그 박스안의 TEXT에 대한 선택을 제한 합니다.

테마에 대한 처리

  어플리케이션의 테마를 웹 콘트롤에 반영 되도록 하는 것은 좀 더 복잡 합니다. 

  테마가 활성화 되어 있는지 체크하기 위하여 Themes 유닛의 ThemeServices 객체를 사용합니다.

  활성화 되어 있다면 브라우저가 테마를 사용하도록 하기 위해 DOCHOSTUIFLAG_THEME를 사용하면 됩니다.

  테마가 활성화 되어 있지 않지만 테마가 이용 가능하다면 DOCHOSTUIFLAG_HOTHEME를 사용함으로써 

  브라우저의 테마 기능을 꺼 버릴 수 있습니다. 테마가 사용가능하지 않다면 더이상 할 작업은 없습니다.


마지막으로 pchHostCss 필드에 유니코드 텍스트로 된 기본 스타일시트를 지정할 수 있습니다. 유니코드 문자열을 저장할 공간을 만들기 위해 CoTaskMemAlloc 메모리 할당 로직을 사용 했습니다. 참고로 이 메모리 할당 함수는 사용 후에 메모리를 해제해 주어야 하는 코드를 추가로 작성해 주지 않아도 됩니다(TWebBrowser가 처리함). 

아래 TaskAllocWideString 함수의 구현 부분을 살펴보면 유니코드 문자열을 저장할 메모리 공간을 확보하는데 있어서 유니코드를 지원하거나 지원하지 않는 델파이 버전에 따른 코드가 삽입되어 있습니다(델파이는 유니코드를 Delphi 2009 버전 부터 지원 합니다.  물론 DelphiXE 이후 버전은 기본으로 유니코드가 지원 됩니다.)


function TaskAllocWideString(const S: string): PWChar;
var
  StrLen: Integer;  // length of string in bytes
begin
  // Store length of string in characters, allowing for terminal #0
  StrLen := Length(S) + 1;
  // Allocate buffer for wide string using task allocator
  Result := CoTaskMemAlloc(StrLen * SizeOf(WideChar));
  if Assigned(Result) then
    // Convert string to wide string and store in buffer
    StringToWideChar(S, Result, StrLen);
end;


여기까지 웹브라우저를 커스터마이징 하기 위한 모든 코드 작성이 완료 되었습니다. 추가적으로 브라우저를 커스터마이징 하기 위한 컨테이너를 만들려면 위에서 작성한 TNullWBContainer를 상속 받는 클래스를 만들고 IDOCHostUIHandler 인터페이스중 적절한 메서드를 재 구현 해 주기만 하면 됩니다.


참고로 웹브라우저의 문서가 로딩 되기 전에 TWBContainer의 프로퍼티들이 미리 지정 되도록 코딩 해야 합니다. 윈도우 XP SP2이전 버전에서는 첫 번째 문서가 로딩 될 떼 기본 CSS가 읽혀 지기 때문입니다. 첫 번째 문서가 읽혀지고 난 다음에 CSS를 변경하게 되면 아무런 효과도 없게 됩니다.


다음 글에서는 이글에서 작성한 TNulWBContainer, TWBContainer를 이용한 실사용 데모를 작성해 보겠습니다.




참조 : How to customise the TWebBrowser user interface  by delphiDabbler.com