[Prev][Next][Index][Thread]

Re: Browser gadget sugguestions for a DUIM app



oodl@my-deja.com writes:

> I am looking for some suggestions as to what I can use.  Does
> anything like this exist?  Would it be possible to use it in a DUIM
> app?  I don't know much about Windows programming.

The simplest approach I can think of at this point is to embed a web
browser activex control in a DUIM pane. There is provisional DUIM
support for this sort of thing in the OLE libraries with FD 2.0 but it
is unfortunately not complete enough for robust hosting of controls
(yet!). I tried it with the internet explorer control and it displayed
web pages, but resizing of the window did not resize the control
properly.

So, here's a hack, err, I mean, workaround that will allow you to host
ActiveX controls relatively easily. I put a demonstration of a project
that shows a minimal web browser on my site. It hosts the internet
explorer control in a DUIM pane, lets you key in an URL and hit the go
button to display the HTML at that url. Maybe you can use the same
activex control for your browser app, as long as you don't mind
requiring Internet Explorer. But the basic technique will work with
any ActiveX control if you want to use another browser control.

The sample project is at:

http://www.double.co.nz/dylan/projects/simple-web-browser.zip

Here's how it works...

The latest version of the Microsoft Active Template Library (ATL) for
MSVC++ 6.0 registers a window class that hosts ActiveX controls - it
is a full control container. It has a class name of 'AtlAxWin' and
uses whatever you pass in the window text to decide what control to
create. If you pass in a class id as "{class-id-here}" then it will
use this class id to create the control. If you pass in an url, it
will create the internet explorer control and navigate to that url. I
use this in the project.

You will need the ATL DLL that contains the new window class. This is
available from Microsoft at:

http://activex.microsoft.com/controls/vc/atl.cab

Unzip the .cab file (WinZip works) and run the self installing
executable. This will put an atl.dll in your system path. To register
the window class you need to call a function 'AtlAxWinInit' in this
DLL. But there is no .lib file available (unless you have VC++ 6.0) so
you have to use LoadLibrary/GetProcAddress from Dylan:

  define C-function AtlAxWinInit
    result value :: <BOOL>;
    indirect: #t;
    c-modifiers: "__stdcall";
  end;

  *atl-module* := LoadLibrary("atl.dll");
  let function = GetProcAddress(*atl-module*, "AtlAxWinInit");
  AtlAxWinInit(function);

With this approach you don't need to link to any additional files.

Once that is registered you can create a window of the new class. I
create a DUIM gadget for html:

  define class <html-gadget> (<value-gadget>)
  end class <html-gadget>;

With a back end to do the creation of the ATL window:

define class <msie-html-gadget>
  (<win32-gadget-mixin>,
   <bordered-gadget-mixin>,
   <html-gadget>,
   <basic-value-gadget>,
   <leaf-pane>)
  virtual constant slot msie-ole-control; // described later
end class <msie-html-gadget>;

define method class-for-make-pane(
  framem :: <win32-frame-manager>,
  class == <html-gadget>,
  #key)
=> (class :: <class>, options :: false-or(<sequence>))
  values(<msie-html-gadget>, #f)
end method class-for-make-pane;

define method make-gadget-control(
  gadget :: <msie-html-gadget>,
  parent :: <hwnd>,
  options,
  #key x, y, width, height)
=> (handle :: <HWND>)
  let ext-style = 
    if(gadget.border-type == #"none") 
      0 
    else 
      $WS-EX-CLIENTEDGE 
    end;
  let handle :: <hwnd> = CreateWindowEx(ext-style,
    "AtlAxWin",
    "http://www.double.co.nz",
    %logior(options,
      $WS-GROUP,
      $WS-TABSTOP),
    x, y, width, height,
    parent,
    $null-hMenu,
    application-instance-handle(),
    $NULL-VOID);
  handle
end method make-gadget-control;

Once these methods are defined you can create the gadget in a frame:

  define frame <atl-browser-frame> (<simple-frame>)
    pane browser-pane (frame)
      make(<html-gadget>);

    pane go-button (frame)
      make(<push-button>, label: "Go", activate-callback: on-go-button);

    pane url-pane (frame)
      make(<text-field>, text: "http://www.double.co.nz");

    layout (frame)
      vertically()
        horizontally()
          frame.url-pane;
          frame.go-button;
        end;
       frame.browser-pane;
      end;

    keyword title: = "Simple Web Browser";
  end frame <atl-browser-frame>;

To prevent a crash when exiting the program, you need to unload the
DLL that was loaded previously after program exit:

begin
  load-atl-library-stuff();
  main();
  FreeLibrary(*atl-module*);
end;

Running something like the above is enough to display a frame, with a
web browser window displaying the home page at
http://www.double.co.nz. You can navigate through the links from there
as well.

To make the url-pane and go-button work you need to be able to get the
underlying web browser control. The COM interface used by the web
browser is called IWebBrowser2. The easiest way of getting the Dylan
wrapper for this is to use the COM typelibrary wizard to create
dispatch-client wrappers for the 'Microsoft Internet Controls' type
library. These are installed with IE 4+. I've included the generated
library in the download by the way.

The way of getting the interface pointer from the ATL window is by
sending it a windows message, using a special message that ATL has
registered:

  *get-control-message* := RegisterWindowMessage("WM_ATLGETCONTROL");

The implementation of the virtual slot in the gadget does this:

  define method msie-ole-control( gadget :: <msie-html-gadget> )
    let interface = c-type-cast(<c-interface>,
                                SendMessage(gadget.window-handle, 
                                  *get-control-message*, 0, 0));
    let (result, com-interface) = QueryInterface(interface, 
      as(<refiid>, "{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}"));  
    pointer-cast(<iwebbrowser2>, com-interface);
  end method msie-ole-control;

The {D30C...9e26e} is the GUID for the IWebBrowser2 interface. This
method basically asks the ATL window for the interface pointer. This
is returned as a DWORD through the SendMessage call - but it is really
a pointer to the interface, so I cast it to that. I QueryInterface it
for the IWebBrowser2 interface and return it. Note that I don't do any
error checking above - an exercise for the reader :-).

Use the slot above to implement the go-button:

  define method on-go-button(g)
    let frame = g.sheet-frame;
    let browser-gadget = frame.browser-pane;
    let-com-interface html-control = browser-gadget.msie-ole-control;
    let-resource url :: <bstr> = as(<bstr>, frame.url-pane.gadget-value);
    let-resource optional :: <lpvariant> = make(<lpvariant>);
    IWebBrowser2/Navigate(html-control, 
      url, 
      optional, 
      optional, 
      optional, 
      optional);    
  end method on-go-button;

The above uses a couple of macros to automatically release the
reference count for the interface, and to free the <bstr> and
<variant>. All these classes contain data allocated by windows - and
windows needs to be told when you are finished with it. I was playing
around with using 'let-' macros instead of the usual 'with-' style for
COM interfaces. This was to avoid heavy source code nesting I was
getting when using the MSXML COM libraries. The macros are::

  define macro let-com-interface
    { let-com-interface ?:name :: ?type:expression = ?value:expression;
      ?:body }
    => { let ?name :: ?type = ?value;
         block()         
           ?body
         cleanup
           release(?name)
         end }
  end macro let-com-interface;

  define macro let-resource
    { let-resource ?:name :: ?type:expression = ?value:expression;
      ?:body }
    => { let ?name :: ?type = ?value;
         block()         
           ?body
         cleanup
           destroy(?name)
         end }
  end macro let-resource;

The methods that can be called on the web browser control are listed
in the automatically generated wrappers to the type library. They are
also documented in the windows platform SDK.

That's about it to get a very simple browser working in Functional
Developer. The project for download at my website should compile and
run fine on FD 2.0 with the com, win32 and duim library packs.

Chris.
-- 
http://www.double.co.nz/dylan



References: