Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

(Ebook - Pdf) Kick Ass Delphi Programming

.pdf
Скачиваний:
284
Добавлен:
17.08.2013
Размер:
5.02 Mб
Скачать

Go!

Keyword

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Responding to Windows Messages

In most cases, Delphi’s interface for handling Windows messages—Application’s OnMessage event handler—is sufficient. Programs can define their own OnMessage handler, and Delphi will dutifully pass Windows messages on to that procedure. But Delphi doesn’t allow you to define multiple OnMessage handlers in a single program, making custom processing of Windows messages for multiple windows somewhat of a problem. In our case, it’s a real problem—only one control in an application can be accepting dropped files.

The first solution that comes to mind is to have an event handler that knows about all of the controls that need to process Windows messages. This event handler can then compare the Msg.hwnd value with the Handle properties of each control, passing the message on to the proper control’s event handler. This is certainly possible, but it requires that your program know—at compile time—all of the controls that might want to process Windows messages.

A related solution would be to define an OnMessage event chaining facility, whereby controls that want to process Windows messages link themselves to a chain of OnMessage event handlers, and the chaining facility itself hooks Application, OnMessage, and then searches the list of linked controls whenever a message comes in. Controls could then link and unlink themselves from the OnMessage chain at will, although your code will have to take care to prevent breaking the chain and leaving some controls hanging without messages.

Both of these solutions have one major drawback—they require that all controls that will be handling Windows messages know about how the main application is handling the message chain. The lower-level portions of your program have to know about the higher-level implementation. This is not a good thing.

Suppose, for example, your latest masterpiece is finished except for a really cool spreadsheet control. Flipping through the latest copy of Hacker Monthly, you find a review of Spreadsheet MAX, the coolest spreadsheet control ever invented. It’s perfect for your application and you order a copy. When it arrives, you find that it works flawlessly except that it stomps on the Application.OnMessage event handler to do its thing—completely wiping out the OnMessage chaining that you had set up. How was the spreadsheet supposed to know that you were providing message chaining?

If there’s a reliable way to make multiple controls properly handle the Application.OnMessage event handler, I certainly haven’t found it. So my solution is to forget it—don’t use Application.OnMessage at all if you have multiple windows that need to process Windows messages. There are alternative methods of de-furring felines.

Custom Controls

If you have a special type of control that you want to respond to particular messages, simply write a custom

version of the control. For example, if you wanted a TForm descendent that responds to WM_DROPFILES, you could create the custom TFMDDForm control shown in Listing 2.5.

Listing 2.5 The TFMDDForm custom component

{

FMDDFORM.PAS -- implements a form that responds to the WM_DROPFILES Windows message.

}

unit fmddform;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, FMDD;

type

TFMDDEvent = procedure (Sender: TObject; DragDropInfo : TDragDropInfo) of object;

TFMDDForm = class(TForm) private

{ Private declarations } FOnFMDD : TFMDDEvent;

procedure WMDropFiles (var Message: TMessage); message WM_DROPFILES;

protected

{ Protected declarations } public

{ Public declarations }

constructor Create(AOwner: TComponent); override; destructor Destroy; override;

published

{ Published declarations }

property OnFMDD: TFMDDEvent read FOnFMDD write FOnFMDD; end;

procedure Register;

implementation

constructor TFMDDForm.Create(AOwner: TComponent); begin

inherited Create (AOwner); FMDD.AcceptDroppedFiles (Handle);

end;

destructor TFMDDForm.Destroy; begin

FMDD.UnacceptDroppedFiles (Handle); inherited Destroy;

end;

procedure TFMDDForm.WMDropFiles (var Message: TMessage); var

DragDropInfo : TDragDropInfo; begin

if assigned (FOnFMDD) then begin

DragDropInfo := FMDD.GetDroppedFiles (Message.wParam); FOnFMDD (Self, DragDropInfo);

DragDropInfo.Free;

end;

end;

procedure Register; begin

RegisterComponents('Samples', [TFMDDForm]); end;

end.

If you have the stomach for it, you could dig into the source for TWinControl and create an OnFMDD event so that all windowed controls knew about the WM_DROPFILES windows message. That’d take care of drag and drop for good, but it’s a rather drastic measure, and it doesn’t do you any good if all of a sudden you want a control to respond to multiple user-defined messages whose values aren’t even defined until run-time. There’s a more general (and more involved) solution that’s completely flexible.

Products | Contact Us | About Us | Privacy | Ad Info | Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.

All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Subclassing

The problem of custom responses to Windows messages isn’t new to Delphi—it’s been around for as long as Windows has been around. They’ve even got a name for the technique: subclassing. Technically, there’s subclassing, and then there’s superclassing, the difference being that subclassing restricts a windows’ response to messages, and superclassing adds to the processing of a windows’ message. In my mind, they’re the same thing simply because you use the same technique to implement either. The technique? Well, it’s kind of ugly when compared to the elegance of Delphi (okay, it’s really ugly), but it works wonders, and we can encapsulate the ugliness so you never see it in your programs.

The idea behind subclassing is pretty simple. Every window has an associated data structure that Windows knows about. Among the many wonderous things in that structure is a pointer to the windows’ window procedure—the procedure that processes Windows messages. When Windows receives a message that’s destined for a window, it looks up the address of that windows’ window procedure and calls it, passing the information pertinent to the particular message. When you subclass a window, you replace the windows’ window procedure with your custom routine, and save a pointer to the old procedure so you can pass messages on to it. All of this stuff is documented in the Windows SDK manuals, and there are some fairly good examples (all in C—you can’t have everything). True, it’s a bit-twiddling and hackerish kind of thing to do, but sometimes you just have to get your hands dirty. (Have you taken a look at the VCL source code lately? If you want an eye-opening experience, glance at CONTROLS.PAS sometime.)

At any rate, Delphi gives us all the tools we need to subclass a window, and we can use those tools to create a drag and drop interface that your programs can interact with in the normal Delphi fashion. As always, we’ll start with the requirements.

Defining the Interface

We want drag and drop to operate as much as possible like a normal Delphi event. Since we’re not defining a new custom control, we can’t define an OnFMDD event that can be assigned at design-time. So we have to simulate that behavior at run-time. To do that, we need to:

1.Define a TFMDDEvent type for the event handler.

2.Declare an OnFMDragDrop event handler in the form’s private section.

3.When the form is created, pass the address of the event handler to the FMDD interface so the interface knows that we want the form to accept dropped files.

4.When a drag and drop event occurs (that is, when the form receives a WM_DROPFILES message), the FMDD interface will call the OnFMDragDrop event handler, passing it a TDragDropInfo object.

5.When the form is closed, call the FMDD interface to stop accepting dropped files.

That requirements list leads us to the interface section shown in Listing 2.6.

Listing 2.6 The interface section of the new FMDD unit

interface

uses Windows, Messages, Classes, Controls;

type

TDragDropInfo = class (TObject) private

FNumFiles : UINT; FInClientArea : Boolean; FDropPoint : TPoint; FFileList : TStringList;

public

constructor Create (ANumFiles : UINT); destructor Destroy; override;

property NumFiles : UINT read FNumFiles;

property InClientArea : Boolean read FInClientArea; property DropPoint : TPoint read FDropPoint; property Files : TStringList read FFileList;

end;

TFMDDEvent = procedure (DDI : TDragDropInfo) of object; procedure AcceptDroppedFiles (Control : TWinControl;

AOnDrop : TFMDDEvent); procedure UnacceptDroppedFiles (Control : TWinControl);

Note that the TDragDropInfo object remains the same. We’ve removed the

GetDroppedFiles function and redefined the AcceptDroppedFiles and

UnacceptDroppedFiles procedures. The result is a much cleaner interface than the previous one—one that doesn’t expect you to know about ugly little things like window handles and Windows messages. Of course, somebody has to know about

those things. The details are hidden in FMDD’s implementation section.

Implementing the New Interface

The devil, as always, is in the details. FMDD has to do a lot of processing behind the scenes. There are three separate, but interrelated, parts to FMDD’s processing:

1.AcceptDroppedFiles has to save the window handle of the passed control and the OnDrop event handler for future use. This procedure also must call DragAcceptFiles to enable WM_DROPFILES processing for this window, and then subclass the window so that it can respond to the message.

2.We need a Windows message handler that will respond to

WM_DROPFILES messages by constructing a TDragDropInfo object and passing it to the appropriate control.

3. UnacceptDroppedFiles should un-subclass the window and call DragAcceptFiles to prevent future WM_DROPFILES messages from being sent to the window.

Because FMDD should allow multiple windows to accept dropped files, we’ll have to keep a list of window handles and their associated event handling procedures. When AcceptDroppedFiles is called, it’ll save the control’s information in the list. The procedure that handles the WM_DROPFILES message will look up the window’s handle in the list so that it knows which object to send an OnFMDragDrop event to, and then the UnacceptDropped Files procedure has to remove the control’s information from the list. Fortunately, Delphi’s TList component is tailor-made for list processing, and it’s a snap to add items to, delete items from, and look up items in a TList.

The tricky part of the implementation is the subclassing—mostly because it deals with Windows arcana. I touched briefly on what subclassing is previously, but purposely left out any discussion of how to do it until we got to the implementation. It’s time now to get our hands dirty.

Products | Contact Us | About Us | Privacy | Ad Info | Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.

All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Subclassing Revisited

To subclass a window, you need to obtain and save a pointer to the window’s existing window procedure, and then set the new window procedure pointer in the window’s data structure. To do this, you call Windows’ GetWindowLong and SetWindowLong API functions, which retrieve and set values in the window’s internal data structure.

Once you’ve successfully subclassed a window, Windows will send all messages destined for that window to the new window procedure. It’s this procedure’s job to respond to the messages that it’s interested in (in our case, WM_DROPFILES), and then pass all of the other messages on to the previous window procedure—a pointer to which you saved when you subclassed the window. You can’t just call that old window procedure, though. Instead, you have to call the CallWindowProc API function and pass it the address of the old window procedure and the parameters that Windows passed to you.

The last part of subclassing is unsubclassing—putting things back the way you found them. Unsubclassing is a simple matter of calling SetWindowLong again, this time replacing the new window procedure with the old one.

It all sounds a little more complicated than it really is, and once you’ve seen and puzzled over an example, it becomes as clear as mud (which is about as clear as any Windows programming gets).

The new FMDD unit that supports subclassing is shown in Listing 2.7.

Listing 2.7 The new FMDD unit that supports multiple controls

{

FMDD.PAS — File Mangler Drag and Drop

}

unit fmdd;

interface

uses Windows, Messages, Classes, Controls;

type

TDragDropInfo = class (TObject)

private

FNumFiles : UINT; FInClientArea : Boolean; FDropPoint : TPoint; FFileList : TStringList;

public

constructor Create (ANumFiles : UINT); destructor Destroy; override;

property NumFiles : UINT read FNumFiles;

property InClientArea : Boolean read FInClientArea; property DropPoint : TPoint read FDropPoint; property Files : TStringList read FFileList;

end;

TFMDDEvent = procedure (DDI : TDragDropInfo) of object;

procedure AcceptDroppedFiles (Control : TWinControl; AOnDrop : TFMDDEvent);

procedure UnacceptDroppedFiles (Control : TWinControl);

implementation

uses ShellAPI;

type

{

TSubclassItem stores information about a subclassed window

}

TSubclassItem = class (TObject)

private

 

Handle : HWND;

{ the window handle }

WindowProc : TFNWndProc;

{ its old window procedure }

FOnDrop : TFMDDEvent;

{ control's OnFMDragDrop event handler }

public

 

constructor Create (AHandle : HWND;

AWndProc : TFNWndProc; AOnDrop : TFMDDEvent);

end;

var

SubclassList : TList;

constructor TSubclassItem.Create (AHandle : HWND;

AWndProc : TFNWndProc; AOnDrop : TFMDDEvent);

begin

inherited Create; Handle := AHandle; WindowProc := AWndProc; FOnDrop := AOnDrop;

end;

{

WMDragDrop creates the TDragDropInfo object and calls the FOnDrop event handler.

}

procedure WMDragDrop (hDrop : THandle; FOnDrop : TFMDDEvent);

var

DragDropInfo : TDragDropInfo; TotalNumberOfFiles, nFileLength : Integer; pszFileName : PChar;

i : Integer;

begin

if not assigned (FOnDrop) then exit;

{

hDrop is a Handle to the internal Windows data structure which has information about the dropped files.

}

{

Find out total number of files dropped by passing -1 for the index parameter to DragQueryFile

}

TotalNumberOfFiles := DragQueryFile (hDrop , $FFFFFFFF, Nil, 0);

DragDropInfo := TDragDropInfo.Create (TotalNumberOfFiles);

{

Determine if the files were dropped in the client area

}

DragDropInfo.FInClientArea :=

DragQueryPoint (hDrop, DragDropInfo.FDropPoint);

for i := 0 to TotalNumberOfFiles - 1 do begin

{

Get the length of a filename by telling DragQueryFile which file your querying about ( i ), and passing Nil for the buffer parameter. The return value is the length of the file name.

}

nFileLength := DragQueryFile (hDrop, i , Nil, 0) + 1; GetMem (pszFileName, nFileLength);

{

Copy a file name.

Tell

DragQueryFile the file

you're interested in

(i)

and the length of your buffer.

NOTE: Make sure that

the

length is 1 more than the filename

to make room for the

nul

character!

}

DragQueryFile (hDrop , i, pszFileName, nFileLength);

{Add the file to the string list } DragDropInfo.FFileList.Add (pszFileName);

{free the allocated memory... }

FreeMem (pszFileName, nFileLength); end;

{

Call DragFinish to release the memory that Shell allocated for this handle.

NOTE: This is a real easy step to forget and could explain memory leaks and incorrect program performance.

}

DragFinish (hDrop);

{call the event handler } FOnDrop (DragDropInfo);

{and destroy the TDragDropInfo object } DragDropInfo.Free;

end;

{

find and return the list item that corresponds to the passed window handle.

}

function FindItemInList (Handle : HWND) : TSubclassItem; var

i : Integer;

Item : TSubclassItem; begin

for i := 0 to SubclassList.Count - 1 do begin Item := SubclassList.Items[i];

if Item.Handle = Handle then begin Result := Item;

exit;

end;

end;

Result := Nil; end;

{

FMDDWndProc handles WM_DROPFILES messages by calling WMDragDrop. All other messages are passed on to the window's old window proc.

}

function FMDDWndProc (

Handle : HWND; Msg : UINT;

wparam: WPARAM; lparam: LPARAM) : LRESULT; stdcall;

var

Item : TSubclassItem; begin

Item := FindItemInList (Handle); if Item <> Nil then begin

if Msg = WM_DROPFILES then begin WMDragDrop (wparam, Item.FOnDrop); Result := 0;

end else

Result := CallWindowProc (Item.WindowProc, Handle, Msg, wparam, lparam)

end else

Result := 0;

end;

{