Skip over navigation

How to handle drag and drop in a TWebBrowser control

Contents

Introduction

While programming an application that uses a TWebBrowser component to display part of its interface I noticed that, although the application didn't use drag and drop, the TWebBrowser was indicating it would accept file drops. What is more, TWebBrowser would load and display text and HTML files, ruining my carefully designed output. Not only that but the control would also display a dialogue box offering to download some types of files.

Obviously this kind of behaviour is not desirable, so I began to search for a means of inhibiting this default behaviour. My first goal was to find the circumstances under which TWebBrowser accepts drag-drop. Next was simply to stop TWebBrowser accepting file drops. Later, in another application I needed to go further and accept certain types of file drops, but wanted to control how this was handled. I then moved on to considering if I could handle text dragged from other applications and dropped on the browser control.

The remainder of this article presents my findings in the hope they will be useful to others who have been experiencing similar problems.

When TWebBrowser Accepts Drag-Drop

Any application that contains a TWebBrowser will not normally accept dragged files unless you intervene. However, if the application initialises OLE by calling OleInitialize then TWebBrowser will accept and try to display or download files dragged onto it!

Try it. Create a new GUI application and drop a TWebBrowser control on it then build and run the application. Try dragging a file from Explorer over the browser control. The "no entry" cursor will be displayed indicating that the file can't be dropped.

Now add OnCreate and OnDestroy event handlers to the main form as shown in Listing 1.

  1uses
  2  ActiveX;
  3
  4{$R *.dfm}
  5
  6procedure TForm1.FormCreate(Sender: TObject);
  7begin
  8  OleInitialize(nil);
  9end;
 10
 11procedure TForm1.FormDestroy(Sender: TObject);
 12begin
 13  OleUninitialize;
 14end;
Listing 1

Run the modified application and again drag a file from Explorer over the browser control. This time you will see a "shortcut" cursor. Drop the file and TWebBrowser will either display it or try to download it, depending on the file type. HTML and text files will be displayed as if you had navigated to them.

This little experiment illustrates the cirumstances when we need to take control of the web browser's handling of drag and drop. They are:

  • Our application needs to initialise OLE.
  • We either don't want, or want to take control of, TWebBrowser's drag-drop handling.

If these circumstances apply to you, then read on. We'll start by looking at how to take charge of the browser control's drag and drop.

Overview of Solution

In article #24 we investigated how to handle OLE drag and drop by implementing the IDropTarget interface. TWebBrowser uses this method of accepting dragged objects. This means that we need to implement IDropTarget and find a way to get the browser control to use our implementation instead of its own.

So how do we supply our own drag drop handler to TWebBrowser? The answer will come as no surprise if you have read some of my earlier TWebBrowser articles. We must implement the IDocHostUIHandler interface and use its GetDropTarget method to hook up our custom IDropTarget implementation with TWebBrowser.

Once we have implemented IDocHostUIHandler we must tell the browser control about it. Whenever TWebBrowser is instantiated it tries to find a IDocHostUIHandler implementation in its host (or container). This is done by querying the host's IOleClientSite interface. This also means we need to implement IOleClientSite in the same object as IDocHostUIHandler.

Further reading

For detailed information about creating a container for TWebBrowser and implementing IOleClientSite and IDocHostUIHandler please see "How to customise the TWebBrowser user interface".

So, to summarise we will:

  1. Create a container for the web browser control that implements IDocHostUIHandler and IOleClientSite and associate this container with the web browser control.
  2. Create an implementation of IDropTarget that handles drag and drop as we would like.
  3. Use the IDocHostUIHandler.GetDropTarget method to notify the browser control about our IDropTarget implementation.

Generic Implementation of Solution

Creating the Web Browser Container

Luckily, in "How to customise the TWebBrowser user interface" we developed a reusable, generic browser container class – TNulWBContainer – that provided minimal implementations of IDocHostUIHandler and IOleClientSite. A do-nothing implementation of IDocHostUIHandler was provided that leaves the browser control's behaviour unchanged. This class also takes care of associating our object with the browser control as its container. We can use this class as a base class for our container class and just add the functionality we need.

Where's the Source?

Source for TNulWBContainer and IDocHostUIHandler is not listed in this article. However the code is provided in this article's demo code , in the UNulContainer and IntfDocHostUIHandler units.

We will reuse the above code here and define a sub class named TWBDragDropContainer that overrides the default implementation of IDocHostUIHandler.GetDropTarget to ensure the browser control hooks into our IDropTarget implementation.

We will make the new class as general as possible by providing a DropTarget property that can be set to any object that implements IDropTarget. This way the class' user can change the drag drop behaviour of the browser control simply by assigning a different object to the DropTarget property.

Listing 2 shows the whole of the UWBDragDropContainer unit that contains our implementation of TWBDragDropContainer.

  1unit UWBDragDropContainer;
  2
  3interface
  4
  5uses
  6  ActiveX,
  7  IntfDocHostUIHandler, UNulContainer;  // from article #18
  8
  9type
 10
 11  TWBDragDropContainer = class(TNulWBContainer,
 12    IUnknown, IOleClientSite, IDocHostUIHandler
 13  )
 14  private
 15    fDropTarget: IDropTarget;
 16  protected
 17    function GetDropTarget(const pDropTarget: IDropTarget;
 18      out ppDropTarget: IDropTarget): HResult; stdcall;
 19  public
 20    property DropTarget: IDropTarget
 21    read fDropTarget write fDropTarget;
 22  end;
 23
 24implementation
 25
 26{ TWBDragDropContainer }
 27
 28function TWBDragDropContainer.GetDropTarget(
 29  const pDropTarget: IDropTarget;
 30  out ppDropTarget: IDropTarget): HResult;
 31begin
 32  if Assigned(fDropTarget) then
 33  begin
 34    // We are handling drag-drop: notify browser of drop target object
 35    ppDropTarget := fDropTarget;
 36    Result := S_OK;
 37  end
 38  else
 39    // We are not handling drag-drop: use inherited default behaviour
 40    Result := inherited GetDropTarget(pDropTarget, ppDropTarget);
 41end;
 42
 43end.
Listing 2

Our new class includes a reimplementation of the inherited GetDropTarget method and defines the new DropTarget property.

GetDropTarget checks to see if a drop target handler is assigned to the DropTarget property. If so we return a reference to our drop target object via the method's ppDropTarget parameter and return S_OK. Conversely if DropTarget is unassigned then we just call the inherited GetDropTarget method to get the default behaviour. Doing all the hard work in the base class has made this class very easy to implement.

Implementing IDropTarget

Article #24, "How to receive data dragged from other applications", discussed how to implement IDropTarget. Implementation depends on what type of data the application will accept, so we won't discuss that further in this section. We will come back to look a couple of specific examples later.

Some Boilerplate Code for the Main Form

To use our web browser container object we need to create it in the main form and set its DropTarget property. Listing 3 shows the code that needs to be added to the main form.

  1unit FmDemo3;
  2
  3interface
  4
  5uses
  6  ..., UWBDragDropContainer, ...
  7
  8type
  9  TForm1 = class(TForm)
 10    ...
 11    WebBrowser1: TWebBrowser;
 12    procedure FormCreate(Sender: TObject);
 13    procedure FormDestroy(Sender: TObject);
 14    ...
 15  private
 16    ...
 17    fWBContainer: TWBDragDropContainer;
 18    ...
 19  end;
 20
 21var
 22  Form1: TForm1;
 23
 24implementation
 25
 26uses
 27  ActiveX;
 28
 29{$R *.dfm}
 30
 31...
 32
 33procedure TForm1.FormCreate(Sender: TObject);
 34begin
 35  OleInitialize(nil);
 36  fWBContainer := TWBDragDropContainer.Create(WebBrowser1);
 37  fWBContainer.DropTarget := TMyDropTarget.Create;
 38  WebBrowser1.Navigate('about:blank');
 39end;
 40
 41procedure TForm1.FormDestroy(Sender: TObject);
 42begin
 43  fWBContainer.DropTarget := nil;
 44  OleUninitialize;
 45  FreeAndNil(fWBContainer);
 46end;
 47
 48...
 49
 50end.
Listing 3

In the form's OnCreate event handler we create an instance of TWBDragDropContainer and assign its DropTarget property with our drop target object (see below for examples of such an object). Next we navigate to about:blank just to ensure the browser control has created a document object. The drop target object won't be noticed by the browser until this has been done. Of course you could load any document here, instead of loading about:blank. We also intialise OLE as before.

† When navigating to about:blank it would be better practice to wait for the document to finish loading before continuing.

In FormDestroy we simply clear the container object's DropTarget property before freeing the container itself. It is probably not necessary to set DropTarget to nil, but it is neater, and may be safer, to explicitly release it. Then we unintialise OLE and free our container object.

That takes care of the implementation of IDocHostUIHandler and how to notify the browser control of it's implementation. We can now move on to look at exactly how we implement the drop target. This is done by means of a couple of case studies.

Case studies

Case Study 1: Inhibiting Drag and Drop

I mentioned in the introduction that one of my goals was to stop web browser controls accepting drag and drop. This case study will show how to do that by creating a "do nothing" implementation of IDropTarget named TNulDropTarget in the unit UNulDropTarget. See Listing 4.

  1unit UNulDropTarget;
  2
  3interface
  4
  5uses
  6  Windows, ActiveX;
  7
  8type
  9
 10  TNulDropTarget = class(TInterfacedObject, IDropTarget)
 11  protected
 12    { IDropTarget methods }
 13    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
 14      pt: TPoint; var dwEffect: Longint): HResult; stdcall;
 15    function DragOver(grfKeyState: Longint; pt: TPoint;
 16      var dwEffect: Longint): HResult; stdcall;
 17    function DragLeave: HResult; stdcall;
 18    function Drop(const dataObj: IDataObject; grfKeyState: Longint;
 19      pt: TPoint; var dwEffect: Longint): HResult; stdcall;
 20  end;
 21
 22implementation
 23
 24{ TNulDropTarget }
 25
 26function TNulDropTarget.DragEnter(const dataObj: IDataObject;
 27  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
 28begin
 29  dwEffect := DROPEFFECT_NONE;
 30  Result := S_OK;
 31end;
 32
 33function TNulDropTarget.DragLeave: HResult;
 34begin
 35  Result := S_OK;
 36end;
 37
 38function TNulDropTarget.DragOver(grfKeyState: Integer; pt: TPoint;
 39  var dwEffect: Integer): HResult;
 40begin
 41  dwEffect := DROPEFFECT_NONE;
 42  Result := S_OK;
 43end;
 44
 45function TNulDropTarget.Drop(const dataObj: IDataObject;
 46  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
 47begin
 48  dwEffect := DROPEFFECT_NONE;
 49  Result := S_OK;
 50end;
 51
 52end.
Listing 4

All we do is set the cursor effect to DROPEFFECT_NONE in all relevant methods and return S_OK from each method.

We now need to make two minor modifications to the boilerplate code that was presented earlier: we must use UNulDropTarget and alter the FormCreate method to create a TNulDropTarget object. Listing 5 shows the changes.

  1uses
  2  ..., UNulDropTarget, ...
  3
  4procedure TForm1.FormCreate(Sender: TObject);
  5begin
  6  ...
  7  fWBContainer.DropTarget := TNulDropTarget.Create;
  8  ...
  9end;
Listing 5

If you run this code you will not be able to drop any dragged object on the web browser control. You can prove this works by commenting out the line in FormCreate where fWBContainer.DropTarget is assigned. The browser's default drag-drop will be enabled once more.

Case Study 2: Accepting Files and Text Dropped on the Browser Control

This case study will be more complex. We will start with a browser control containing an HTML document that displays some instructions along with a placeholder box where data extracted from dragged and dropped objects can be displayed. We will accept two kinds of data objects:

  1. Text dragged and dropped from other applications. This text will be displayed as pre-formatted text.
  2. Files dragged and dropped from Explorer. The names of the files will be listed.

Implementation of IDropTarget

Let us begin by looking at how to implement IDropTarget. This code is more complicated than we've seen before for two reasons:

  1. We actually have to handle some dropped data – in fact we handle two different kinds of data.
  2. We have to communicate with the main form to enable the form to display the data. We could reduce this complexity by implementing IDropTarget in the main form, but I prefer to separate the code out.

Listing 6 shows the declaration of TCustomDropTarget, our class that implements IDropTarget.

  1type
  2
  3  IDropHandler = interface(IInterface)
  4    ['{C6A5B98C-9D4C-4205-B0C2-3F964938DF26}']
  5    procedure HandleText(const Text: string);
  6    procedure HandleFiles(const Files: TStrings);
  7  end;
  8
  9  TCustomDropTarget = class(TInterfacedObject, IDropTarget)
 10  private
 11    fCanDrop: Boolean;
 12    fDropHandler: IDrophandler;
 13    function CanDrop(const DataObj: IDataObject): Boolean;
 14    function MakeFormatEtc(const Fmt: TClipFormat): TFormatEtc;
 15    function GetTextFromObj(const DataObj: IDataObject;
 16      const Fmt: TClipFormat): string;
 17    procedure GetFileListFromObj(const DataObj: IDataObject;
 18      const FileList: TStrings);
 19    procedure DisplayData(const DataObj: IDataObject);
 20    procedure DisplayFileList(const DataObj: IDataObject);
 21    procedure DisplayText(const DataObj: IDataObject);
 22  protected
 23    { IDropTarget methods }
 24    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
 25      pt: TPoint; var dwEffect: Longint): HResult; stdcall;
 26    function DragOver(grfKeyState: Longint; pt: TPoint;
 27      var dwEffect: Longint): HResult; stdcall;
 28    function DragLeave: HResult; stdcall;
 29    function Drop(const dataObj: IDataObject; grfKeyState: Longint;
 30      pt: TPoint; var dwEffect: Longint): HResult; stdcall;
 31  public
 32    constructor Create(const Handler: IDropHandler);
 33  end;
Listing 6

For now, ignore the IDropHandler interface and the private methods of TCustomDropTarget – they will be explained later. We will start by looking at how the IDropTarget methods are implemented. Listing 7 shows the methods.

  1function TCustomDropTarget.DragEnter(const dataObj: IDataObject;
  2  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
  3begin
  4  Result := S_OK;
  5  fCanDrop := CanDrop(dataObj);
  6  if fCanDrop and (dwEffect and DROPEFFECT_COPY <> 0) then
  7    dwEffect := DROPEFFECT_COPY
  8  else
  9    dwEffect := DROPEFFECT_NONE;
 10end;
 11
 12function TCustomDropTarget.DragLeave: HResult;
 13begin
 14  Result := S_OK;
 15end;
 16
 17function TCustomDropTarget.DragOver(grfKeyState: Integer; pt: TPoint;
 18  var dwEffect: Integer): HResult;
 19begin
 20  if fCanDrop and (dwEffect and DROPEFFECT_COPY <> 0) then
 21    dwEffect := DROPEFFECT_COPY
 22  else
 23    dwEffect := DROPEFFECT_NONE;
 24  Result := S_OK;
 25end;
 26
 27function TCustomDropTarget.Drop(const dataObj: IDataObject;
 28  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
 29begin
 30  Result := S_OK;
 31  fCanDrop := CanDrop(dataObj);
 32  if fCanDrop and (dwEffect and DROPEFFECT_COPY <> 0) then
 33  begin
 34    dwEffect := DROPEFFECT_COPY;
 35    DisplayData(dataObj);
 36  end
 37  else
 38    dwEffect := DROPEFFECT_NONE;
 39end;
Listing 7

About IDropTarget & IDataObject

If you are not comfortable working with these interfaces please see "How to receive data dragged from other applications" which explains how to implement IDropTarget and use the methods of IDataObject. I won't spend much time explaining this here.

In DragEnter we check if we can handle the dragged data by calling the CanDrop method and recording its result for later use. If we can handle the data we intend to instruct Windows to display the copy cursor by setting dwEffect to DROPEFFECT_COPY. However before we can do this we must check that the caller supports copying by checking that dwEffect includes DROPEFFECT_COPY. Should we not be able to handle the data, or the caller doesn't support copying, we inhibit the drop by setting dwEffect to DROPEFFECT_NONE.

In DragOver we test fCanDrop and again check that the caller supports copying before setting dwEfect.

We need do nothing in DragLeave, so we simply return S_OK.

Finally, in Drop we re-check the data object and dwEffect to see if we should accept the drop and again set dwEffect accordingly. If we are accepting the drop we call DisplayData to interpret the data object and to display it.

So, how do we tell if we accept the dragged data object? It's simple. We accept data objects that store text (with format type CF_TEXT) or those that can list files dropped on the application (with format type CF_HDROP). The CanDrop method checks for these formats, as Listing 8 shows:

  1function TCustomDropTarget.CanDrop(const DataObj: IDataObject): Boolean;
  2begin
  3  Result := (DataObj.QueryGetData(MakeFormatEtc(CF_HDROP)) = S_OK)
  4    or (DataObj.QueryGetData(MakeFormatEtc(CF_TEXT)) = S_OK);
  5end;
Listing 8

CanDrop uses MakeFormatEtc to populate the TFormatEtc structure required by the data object's QueryGetData method. MakeFormatEtc, which was explained in article #24, is shown in Listing 9.

  1function TCustomDropTarget.MakeFormatEtc(
  2  const Fmt: TClipFormat): TFormatEtc;
  3begin
  4  Result.cfFormat := Fmt;
  5  Result.ptd := nil;
  6  Result.dwAspect := DVASPECT_CONTENT;
  7  Result.lindex := -1;
  8  Result.tymed := TYMED_HGLOBAL;
  9end;
Listing 9

Let us move on to examine how we extract the information to be displayed from the data object. Listing 10 presents all the relevant methods.

  1procedure TCustomDropTarget.DisplayData(const DataObj: IDataObject);
  2begin
  3  if DataObj.QueryGetData(MakeFormatEtc(CF_HDROP)) = S_OK then
  4    DisplayFileList(DataObj)
  5  else if DataObj.QueryGetData(MakeFormatEtc(CF_TEXT)) = S_OK then
  6    DisplayText(DataObj)
  7  else
  8    raise Exception.Create('Drop data object not supported');
  9end;
 10
 11procedure TCustomDropTarget.DisplayFileList(const DataObj: IDataObject);
 12var
 13  Files: TStringList;
 14begin
 15  if Assigned(fDropHandler) then
 16  begin
 17    Files := TStringList.Create;
 18    try
 19      GetFileListFromObj(DataObj, Files);
 20      fDropHandler.HandleFiles(Files);
 21    finally
 22      FreeAndNil(Files);
 23    end;
 24  end;
 25end;
 26
 27procedure TCustomDropTarget.DisplayText(const DataObj: IDataObject);
 28begin
 29  if Assigned(fDropHandler) then
 30    fDropHandler.HandleText(GetTextFromObj(DataObj, CF_TEXT));
 31end;
 32
 33procedure TCustomDropTarget.GetFileListFromObj(
 34  const DataObj: IDataObject; const FileList: TStrings);
 35var
 36  Medium: TStgMedium;         // storage medium containing file list
 37  DroppedFileCount: Integer;  // number of dropped files
 38  I: Integer;                 // loops thru dropped files
 39  FileNameLength: Integer;    // length of a dropped file name
 40  FileName: string;           // name of a dropped file
 41begin
 42  // Get required storage medium from data object
 43  if DataObj.GetData(MakeFormatEtc(CF_HDROP), Medium) = S_OK then
 44  begin
 45    try
 46      try
 47        // Get count of files dropped
 48        DroppedFileCount := DragQueryFile(
 49          Medium.hGlobal, $FFFFFFFF, nil, 0
 50        );
 51        // Get name of each file dropped and process it
 52        for I := 0 to Pred(DroppedFileCount) do
 53        begin
 54          // get length of file name, then name itself
 55          FileNameLength := DragQueryFile(Medium.hGlobal, I, nil, 0);
 56          SetLength(FileName, FileNameLength);
 57          DragQueryFile(
 58            Medium.hGlobal, I, PChar(FileName), FileNameLength + 1
 59          );
 60          // add file name to list
 61          FileList.Add(FileName);
 62        end;
 63      finally
 64        // Tidy up - release the drop handle
 65        // don't use DropH again after this
 66        DragFinish(Medium.hGlobal);
 67      end;
 68    finally
 69      ReleaseStgMedium(Medium);
 70    end;
 71  end;
 72end;
 73
 74function TCustomDropTarget.GetTextFromObj(const DataObj: IDataObject;
 75  const Fmt: TClipFormat): string;
 76var
 77  Medium: TStgMedium;
 78  PText: PChar;
 79begin
 80  if DataObj.GetData(MakeFormatEtc(Fmt), Medium) = S_OK then
 81  begin
 82    Assert(Medium.tymed = MakeFormatEtc(Fmt).tymed);
 83    try
 84      PText := GlobalLock(Medium.hGlobal);
 85      try
 86        Result := PText;
 87      finally
 88        GlobalUnlock(Medium.hGlobal);
 89      end;
 90    finally
 91      ReleaseStgMedium(Medium);
 92    end;
 93  end
 94  else
 95    Result := '';
 96end;
Listing 10

Recall that DisplayData is called when a data object is dropped. All it does is test the format of the dropped data and call DisplayFileList if files were dropped or DisplayText if text was dropped. No other data format should have been passed to DisplayData, so we raise an exception if that is the case.

DisplayFileList simply creates a string list to receive the names of the dropped files and calls GetFileListFromObj to extract the file list from the data object. Similarly, DisplayText calls GetTextFromObj to extract the dropped text from the data object.

GetTextFromObj & GetFileListFromObj

Similar methods are explained in detail in article #24. See "Example 1: Text stored in global memory" and " Example 2: File list stored in global memory" for details.

You will have noticed that both DisplayFileList and DisplayText only get data from the data object if the fDropHandler field is set. They also call methods on fDropHandler when it is set. So what is fDropHandler?

Put simply fDropHandler is the means by which we communicate with the main form. fDropHandler is an instance of an object that supports IDropHandler, the interface we declared in Listing 6. A reference to such an object is passed to TCustomDropTarget's constructor and recorded in fDropHandler. The relevant methods of IDropHandler are called by DisplayFileList and DisplayText. Listing 11 shows the constructor, which needs no further explanation.

  1constructor TCustomDropTarget.Create(const Handler: IDropHandler);
  2begin
  3  inherited Create;
  4  fDropHandler := Handler;
  5end;
Listing 11

And where is IDropHandler implemented? It could be any object that knows how to display the data, so where better than the main form?

The Main Form

The main form contains a web browser control to display the HTML page. From what we've said above, the form must also implement IDropHandler.

Using a blank form, drop a TWebBrowser control on it, aligned to fill all the form's client area. Create onCreate, OnDestroy and OnShow event handlers and change the form's interface section to look like Listing 12:

  1uses
  2  ..., ActiveX, MSHTML, ...;
  3
  4type
  5  TForm1 = class(TForm, IDropHandler)
  6    WebBrowser1: TWebBrowser;
  7    procedure FormCreate(Sender: TObject);
  8    procedure FormDestroy(Sender: TObject);
  9    procedure FormShow(Sender: TObject);
 10  private
 11    fWBContainer: TWBDragDropContainer;
 12    procedure SetBoxInnerHTML(const HTML: string);
 13  protected
 14    { IDropHandler methods }
 15    procedure HandleText(const Text: string);
 16    procedure HandleFiles(const Files: TStrings);
 17  end;
Listing 12

Now implement FormCreate, FormDestroy and FormShow as shown in Listing 13.

  1procedure TForm1.FormCreate(Sender: TObject);
  2begin
  3  OleInitialize(nil);
  4  fWBContainer := TWBDragDropContainer.Create(WebBrowser1);
  5  fWBContainer.DropTarget := TCustomDropTarget.Create(Self);
  6  WebBrowser1.Navigate(ExtractFilePath(ParamStr(0)) + 'Page.html');
  7end;
  8
  9procedure TForm1.FormDestroy(Sender: TObject);
 10begin
 11  fWBContainer.DropTarget := nil;
 12  OleUninitialize;
 13  FreeAndNil(fWBContainer);
 14end;
 15
 16procedure TForm1.FormShow(Sender: TObject);
 17begin
 18  while WebBrowser1.ReadyState <> READYSTATE_COMPLETE do
 19  begin
 20    Sleep(5);
 21    Application.ProcessMessages;
 22  end;
 23end;
Listing 13

In FormCreate we once again initialise OLE. We then create a web browser container object and use it to assign the drop target, now implemented by TCustomDropTarget. A reference to this form is passed to TCustomDropTarget's constructor, because our form implements IDropHandler. Instead of navigating to about:blank we navigate to an HTML page named Page.html in the same directory as the executable program. We will discuss this file later.

FormDestroy simply tidies up, while FormShow pauses until the HTML document has fully loaded.

We still have to implement the methods of IDropHandler, so let us do that now. Listing 14 shows our implementation, along with that of the helper method, SetBoxInnerHTML that is used update the display.

  1procedure TForm1.HandleFiles(const Files: TStrings);
  2var
  3  Idx: Integer;
  4  HTML: string;
  5begin
  6  HTML := '<ul>'#13#10;
  7  for Idx := 0 to Pred(Files.Count) do
  8    HTML := HTML
  9      + '<li>'
 10      + MakeSafeHTMLText(Files[Idx])
 11      + '</li>'#13#10;
 12  HTML := HTML + '</ul>';
 13  SetBoxInnerHTML(HTML);
 14end;
 15
 16procedure TForm1.HandleText(const Text: string);
 17begin
 18  SetBoxInnerHTML('<pre>' + MakeSafeHTMLText(Text) + '</pre>');
 19end;
 20
 21procedure TForm1.SetBoxInnerHTML(const HTML: string);
 22var
 23  BoxDiv: IHTMLElement;
 24  Doc: IHTMLDocument3;
 25begin
 26  if Supports(WebBrowser1.Document, IHTMLDocument3, Doc) then
 27  begin
 28    BoxDiv := Doc.getElementById('box');
 29    if Assigned(BoxDiv) then
 30      BoxDiv.innerHTML := HTML;
 31  end;
 32end;
Listing 14

SetBoxInnerHTML examines the displayed HTML document to find an element with an id of "box". If such an element exists its inner HTML is set to the code passed to method as a parameter. This causes the required HTML to be displayed in the document. The HTML document presented below contains a <div> with the required id.

HandleFiles accepts a string list containing the names of the dropped files. It creates a HTML unordered list of file names. HandleText simply encloses the text passed to it in <pre> tags.

Both HandleFiles and HandleText make use of a helper function named MakeSafeHTMLText that ensures the text contains only valid HTML characters. MakeSafeHTMLText, which was taken from the DelphiDabbler Code Snippets Database, appears in Listing 15 below.

  1function MakeSafeHTMLText(TheText: string): string;
  2var
  3  Idx: Integer; // loops thru the given text
  4begin
  5  Result := '';
  6  for Idx := 1 to Length(TheText) do
  7    case TheText[Idx] of
  8      '<':  // opens tags
  9        Result := Result + '&lt;';
 10      '>':  // closes tags
 11        Result := Result + '&gt;';
 12      '&':  // begins char references
 13        Result := Result + '&amp;';
 14      '"':  // quotes (can be a problem in quoted attributes)
 15        Result := Result + '&quot;';
 16      #0..#31, #127..#255:  // control and special chars
 17        Result := Result + '&#'
 18          + SysUtils.IntToStr(Ord(TheText[Idx])) + ';';
 19      else  // compatible text: pass thru
 20        Result := Result + TheText[Idx];
 21    end;
 22end;
Listing 15

HTML of Displayed Page

All that remains now is to show the content of Page.html, the HTML page displayed in the web browser control, which we do in Listing 16.

  1<!DOCTYPE html
  2  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  3  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5  <head>
  6    <title>Article #25 Demo 4</title>
  7    <style type="text/css">
  8      #box {
  9        background-color: #ff9;
 10        border: 1px silver solid;
 11        padding: 0.25em;
 12        margin: 6px 0;
 13        height: 160px;
 14        width: 420px;
 15        overflow: auto;
 16      }
 17    </style>
 18  </head>
 19  <body>
 20    <h1>
 21      Drag Drop Test
 22    </h1>
 23    <p>
 24      Drag and drop one or more files or a text selection on this
 25      browser control.
 26    </p>
 27    <div id="box">
 28      <div style="text-align:center;vertical-align:bottom;">
 29        <em>Dragged and dropped text or list of files will be
 30      displayed here.</em>
 31      </div>
 32    </div>
 33  </body>
 34</html>
Listing 16

The main thing to note is the <div> with id of "box" which is where the dropped text or file list is displayed. We style the <div> with a silver border and pale yellow background, and give it a fixed height and width so that scroll bars will appear for any oversized content.

Exercising the Code

Run this code and experiment by dragging single and multiple files from Explorer and by selecting and dragging text from a text editor or word processor that supports text drag and drop.

This completes our investigation of how to handle drag and drop in the TWebBrowser control.

Demo Code

A demo program to accompany this article can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-25 sub-directory. Alternatively download a zip file containing all the demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

The demo includes the complete source code of four demo programs. See the demo's README.md file for details.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Going Further

My GUI for the PasHi Pascal Highlighter, PasHiGUI, program uses a TWebBrowser control and overrides drag and drop using similar methods to those described here. If you are interested in seeing how this works please feel free to download and examine the program's source code.

Summary

In this article we discovered that the default drag and drop behaviour of a TWebBrowser control varies depending on whether OLE has been initialised.

We examined general techniques that can be used to override the web browser control's drag drop handling by implementing the IDropTarget, IDocHostUIHandler and IOleSlientSite interfaces.

Next we looked at how to inhibit TWebBrowser from handling drag and drop at all.

Finally we moved on to learn how to customise the control's handling of drag and drop in applications that need to support drag and drop but do not want the control's default behaviour.

References

The Windows API help file supplied with Delphi provided documentation for IDropTarget.

Two of my earlier articles provide much of the background information relied on in this article. They are:

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.