How to handle drag and drop in a TWebBrowser control
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.
So, to summarise we will:
- Create a container for the web browser control that implements IDocHostUIHandler and IOleClientSite and associate this container with the web browser control.
- Create an implementation of IDropTarget that handles drag and drop as we would like.
- 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;
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
27
28function TWBDragDropContainer.GetDropTarget(
29 const pDropTarget: IDropTarget;
30 out ppDropTarget: IDropTarget): HResult;
31begin
32 if Assigned(fDropTarget) then
33 begin
34
35 ppDropTarget := fDropTarget;
36 Result := S_OK;
37 end
38 else
39
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
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
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:
- Text dragged and dropped from other applications. This text will be displayed as pre-formatted text.
- 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:
- We actually have to handle some dropped data – in fact we handle two different kinds of data.
- 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
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;
37 DroppedFileCount: Integer;
38 I: Integer;
39 FileNameLength: Integer;
40 FileName: string;
41begin
42
43 if DataObj.GetData(MakeFormatEtc(CF_HDROP), Medium) = S_OK then
44 begin
45 try
46 try
47
48 DroppedFileCount := DragQueryFile(
49 Medium.hGlobal, $FFFFFFFF, nil, 0
50 );
51
52 for I := 0 to Pred(DroppedFileCount) do
53 begin
54
55 FileNameLength := DragQueryFile(Medium.hGlobal, I, nil, 0);
56 SetLength(FileName, FileNameLength);
57 DragQueryFile(
58 Medium.hGlobal, I, PChar(FileName), FileNameLength + 1
59 );
60
61 FileList.Add(FileName);
62 end;
63 finally
64
65
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.
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
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;
4begin
5 Result := '';
6 for Idx := 1 to Length(TheText) do
7 case TheText[Idx] of
8 '<':
9 Result := Result + '<';
10 '>':
11 Result := Result + '>';
12 '&':
13 Result := Result + '&';
14 '"':
15 Result := Result + '"';
16 #0..#31, #127..#255:
17 Result := Result + '&#'
18 + SysUtils.IntToStr(Ord(TheText[Idx])) + ';';
19 else
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.
- 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 #".
- For bugs in the demo code see the
article-demo
project's README.md
file for details of how to report them.