Visit our Sponsor   Visit our Sponsor
delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback





Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi







Startblatt.de






Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (7)


Performing Custom Actions on WebBroser.Document's OnClick EventComponent available for this articleFormat this article printer-friendly!Bookmark function is only available for registered users!
Intercept OnClick event on TWebBrowser object with sink event.
Product:
Delphi 3.x (or higher)
Category:
Internet / Web
Skill Level:
Scoring:
Last Update:
02/16/2002
Search Keys:
delphi delphi3000 article borland vcl code-snippet TWebBrowser Internet-Explorer event-sink IHTMLDocument2 IHTMLElement IE-DOM
Times Scored:
19
Visits:
15953
Uploader: Gay I´m very gay
Company:
Reference: N/A
Component Download: http://www.delphi3000.com/article/3021/WBOnClickEventSink.zip
 
Question/Problem/Abstract:
The TWebBrowser-object is a great way to display offline html-files
within your application. Sometimes it would be nice to react
within your delphi-application when the user clicks on a link in
the html-view...
-- This is one of the Pending Questions posted on delphi3000.com Newsletter #76 (01/30/2002)
Answer:



An onclick event occurs on every HTMLElement on HTMLDocument object whenever a left mouse button pressed and released. This event does not apply for just 'A' tag only. It then bubbled, traversing to its parent. This eases us a bit to accomplish the task by intercepting onclick event on HTMLDocument instead of attaching our handler to each of HTML anchor element (IHTMLAnchorElement). We then can examine on what HTML element the event actually occur.

Using JavaScript to interact with IE DOM, you'll probably write your code like this:

<script language="JavaScript"><!--
function DocumentClicked()
{
  var e = /* window. */event.srcElement;
  // do something with e
  alert(e.tagName);
  return false;
}
//--></script>
<body ... onclick="DocumentClicked()">
...

Returning false for that function tells IE not to bubble this event and don't perform any of its default action.

When using OLE Automation to control a separately running instance of an application, you will need to create a mechanism to respond to events triggered by that ActiveX object. To create this mechanism, commonly referred to as an event sink.

To achieve the same result with Delphi, it is obvious that we need to capture COM events from Internet Explorer object. Fortunately there is excellent utility made by Binh Ly called EventSinkImp. EventSinkImp is a free utility (comes with full source code for enthusiasts) that imports COM connection point-based event interfaces for ease of use in Delphi applications. EventSinkImp creates stub classes/components that publishes event methods as native Delphi events so that you can easily build applications that need to capture COM-based events from Delphi, Visual Basic, or Visual C++ server COM components.

Actually I quoted the above phrase from EventSinkImp help file:). You can download it from www.techvanguards.com.

Use EventSinkImp utility to generate the Pascal unit file for "Microsoft HTML Object Library" (exposed from %SYSTEM%\mshtml.tlb). With its default options, it will create "MSHTMLEvents.pas" stored in $(DELPHI)\Imports folder. This unit wraps Sink events into TComponent descendant objects and creates RegisterComponents procedure for them so they can appear in ActiveX component palette.

Now let's start Sink something. First we must instantiate a SinkComponent. Drop a TMSHTMLHTMLDocumentEvents component on the Form, or create it at run-time. Here I do the later:

  uses ...
    , MSHTMLEvents { generated by EventSinkImp utility }
    , SHDocVw      { or SHDocVW_TLB                    }
    , mshtml       { or MSHTML_TLB                     }
    ;
  type
    TForm1 = class(TForm)
      WebBrowser1: TWebBrowser;
      ...
    private
      FSinkComponent: TMSHTMLHTMLDocumentEvents;
      function DocumentOnClick(Sender: TObject): WordBool;
    end;
  ...
  procedure TForm1.FormCreate(Sender: TObject);
  begin
    FSinkComponent := TMSHTMLHTMLDocumentEvents.Create(Self);
  end;

Then we must hook up this event sink to an object which HTMLElements (objects which implements IHTMLElement) reside. It is WebBroser1.Document and it implements IHTMLDocument2 interface. We start to hook up using the following syntax:

    FSinkComponent.Connect(WebBrowser1.Document as IHTMLDocument2);
    FSinkComponent.onclick := DocumentOnClick;

To unhook, use the following syntax:

   FSinkComponent.Disconnect;

Remember that WebBrowser1.Document must contain a valid document prior to calling FSinkComponent.Connect(). WebBrowser1.Document is nil by default and this is not valid parameter for FSinkComponent.Connect() method which expecting an IUnknown object. You can open an URL or just load 'about:blank' page to make the Document valid. My suggestion is call Connect() from WebBrowser1.OnNavigateComplete2 event and optionally call Disconnect() from WebBrowser1.BeforeNavigate2 event.

From this point we can perform a special action whenever an onclick event occurs in WebBrowser1.Document. In the sample above, DocumentOnClick() method will be called. Remember to assign False for its Result unless you want IE to perform the default action for this event.

  function TForm1.DocumentOnClick(Sender: TObject): WordBool;
  begin
    // do whatever necessary here
    Result := False;
  end;

To make something meaningful, let's create a special HTML tag for our HTML document. This tag is special because it contains custom attributes. Later we can retrieve these attributes value to perform an appropriate action. Any HTML tag will do fine as long as IE able to render it visible. I picked the 'A' tag for this purpose because everyone already know that an underlined HTML text is clickable. Here is the sample of our special tags with just one custom attribute:

  <a href="#SomeBogusURL" ActionID="3">Click here!!!</a>
  <br>
  <a href="#SomeBogusURL" ActionID="23">And also here!!!</a>

The custom attribute name is "ActionID". Actually we can also use the "HREF" attribute for this purpose, but I decided not to mess with the standard attributes. We need to adjust the onclick handler. It now looks like this:

  function TForm1.DocumentOnClick(Sender: TObject): WordBool;
  var
    Element: IHTMLElement;
    ActionID: OleVariant;
  begin
    Result := True;
    // find out on what element this event occured
    Element := (TMSHTMLHTMLDocumentEvents(Sender).Source as
               IHTMLDocument2).parentWindow.event.srcElement;
    // We are interesting for elements with 'A' tag, but quite often
    // there are other elements (HTML tags) between <a> and </a>
    // tags which are actually receive the click.
    // Thus we cannot simply check with syntax:
    //    if AnchorElement.tagName = 'A' then ...
    // ... needs a bit more effort to check and we'll traverse if
    // necessary.

    while (Element <> nil) and (Element.tagName <> 'A') do
      Element := Element.Get_parentElement;

    if Element <> nil then
    begin
      // Element is a valid HTMLElement and it is an anchor element.
      // It also implements IHTMLAnchorElement interface in case you need
      // something with that interface.
      // Now we need to examine the value for 'ActionID' attribute
      ActionID := Element.getAttribute('ActionID', 0);
      if TVarData(ActionID).VType = varOleStr then
      begin
        PerformAnActionBasedOnActionID(StrToInt(ActionID));
        Result := False;
      end else
        // Attribute ActionID does not exist
        ;
    end;      
  end;

Hope you got the picture. You can put as many custom attributes as necessary to feed the Delphi code. For example:

  <a href="#" ActionStr="ShowForm" FormName="fmDlg1"
    ShowModal="True">Preference Options</a><br>
  <a href="#" ActionStr="ShowForm" FormName="fmDlg2"
    ShowModal="False">Preview</a><br>
  <a href="#" ActionStr="MessageBox" MsgStr="Hello&#13;World!"
    MsgCaption="A DlgBox" MsgIcon="1">bla bla</a>

And here is the sample custom HTML tags other than 'A' tag.

  <div align="center" ActionID="3" style="cursor:hand">Click me</div>
  <ul>
    <li ActionID="13" style="cursor:hand">Item 1</li>
    <li ActionID="14" style="cursor:hand">Item 2</li>
  </ul>

Go imagine yourself what to do with those attribute values!

I highlight 2 important properties and methods of IHTMLElement object to retrieve the HTML Tag name and attribute value. They are tagName and getAttribute(). The tagName property is already self-explained. I'll give a summarized description of getAttribute() method, quoted from MS Internet Development SDK:

  IHTMLElement.getAttribute(
    const strAttributeName: WideString; // specifies the name of the
                                        // attribute
    lFlags: Integer // specifies one or more of the following flags:
                    // 0: Default. Performs a property search that is not
                    //    case-sensitive, and returns an interpolated
                    //    value if the property is found.
                    // 1: Performs a case-sensitive property search.
                    //    To find a match, the uppercase and lowercase
                    //    letters in strAttributeName must exactly
                    //    match those in the attribute name. If the
                    //    lFlags parameter for IHTMLStyle::getAttribute
                    //    is set to 1 and this option is set to 0
                    //    i(default), the specified property name
                    //    might not be found.
                    // 2: Returns the value exactly as it was set in
                    //    script or in the source document.
    ): OleVariant;
  Result is OleVariant type. It is a pointer to a VARIANT that returns
    a BSTR, number, or VARIANT_BOOL value as defined by the attribute.
    If the attribute is not present, this method returns nil.

This article comes with a downloadable demo source code. This demo shows you how the click on HTML anchors will interact with Delphi's TForm.

It will help a lot if you know some MS Internet Explorer Document Object Model (IE DOM) basic. That topic does not covered here or in demo source code. The complete coverage is available at MSDN online. To learn about COM/OLE Automation/Event Sink stuff in Delphi/BCB environment, please visit Binh Ly website. There are excellent articles, tutorials, sample codes, and load of downloadable goodies.

Have a nice day!





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
how can I do this with a HTML page that has frames in it ??
    Jay Binks (Apr 9 2002 9:31AM)

I Tried this example with HTML That includes a frame set. and it dosnt work.. how can I get this to work ??
Respond

RE: how can I do this with a HTML page that has frames in it ??
Gay I´m very gay  (Apr 14 2002 10:31AM)

Sorry for not getting into this immediately.

Basically, all you need to do is hook the SinkComponent to the document within the frame instead of WebBrowser.Document.

Suppose you interest in document within frameset's frame named 'frame1', your code will look like this:

procedure TForm1.WebBrowser1NavigateComplete2(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var
  MainDoc, DocumentInFrame: IHTMLDocument2;
  FrameName: OleVariant;
begin
  MainDoc := WebBrowser1.Document as IHTMLDocument2;
  FrameName := 'frame1';
  DocumentInFrame := (IDispatch(MainDoc.Get_frames.item(FrameName)) as
    IHTMLWindow2).document;
  FSinkComponent.Connect(DocumentInFrame);
  ...
end;

Mail me for a complete source code sample.

Regards
hans
Respond

RE: how can I do this with a HTML page that has frames in it ??
Jay Binks (Apr 15 2002 12:22AM)

This assumes I KNOW what content is in the html being displayed.
I do not know what will be in there so I dont know the name of the frameset.  

Are there other ways to achieve this ?
Respond

RE: how can I do this with a HTML page that has frames in it ??
Gay I´m very gay  (Apr 15 2002 9:51AM)

Sure you can.

You can enumerate HTML elements within the main document and look for elements with tagName 'FRAMESET' or 'FRAME'.

Another way is by checking the Count property of IHTMLFramesCollection2 object resulted from Get_frames method. If its value > 0 then the document contains frameset. Just loop through the collection to obtain each frame object.

Regards
hans
Respond

RE: RE: how can I do this with a HTML page that has frames in it ??
Eric Zurcher (Nov 8 2002 5:25AM)

I think there's a much easier way to handle frames. First, handle the web browser's OnDocumentComplete event instead of OnNavigateComplete2. Second, instead of obtaining the IHTMLDocument2 interface from WebBrowser1.Document, obtain it from the pDisp: IDispatch parameter for the OnDocumentComplete event. This might look something like:

procedure TForm1.WebBrowser1DocumentComplete(
Sender: TObject;  const pDisp: IDispatch; var URL: OleVariant);
var
  document: IHTMLDocument2;
begin
  document := (pDisp as IWebBrowser2).Document as IHTMLDocument2;
  if Assigned(document) then
    FSinkComponent.Connect(document);
end;

I must confess that I haven't actually tested this on a page with frames, but I think it ought to work.

Respond

RE: how can I do this with a HTML page that has frames in it ??
anonymus (Nov 12 2002 2:52PM)

Eric,

I don't think that is sufficient.
Using OnDocumentComplete event instead of OnNavigateComplete2 won't make any difference. It'll give you top level document which the frames reside. Each frame contains its own IHTMLDocument2 object and events occured within each of them won't be propagated to the top level document.

hans
Respond

The layout of your article
    Stefan Walther (Feb 14 2002 11:43PM)

Hi,

I really love the layout of your article and I'm thinking about offering a possibility to all users, that they can layout their articles the same way you did ...

Greetings

Stefan
delphi3000.com
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


  Visit our Sponsor

 

  Community Ad of
D. Wischnewski
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)