難度:★★★☆☆
先行知識:Delphi / 接口 / Win32 / OLE or COM
從一個程序拖動數據到另一個程序(典型的情況是拖動文本)已經不是什麼新鮮事了,很多共享軟件都支持這個功能(比如說著名的Flashget、netants等的浮動窗口功能)。作者一直想在自己的軟件中實現這個功能,經過一段時間的資料搜索,有了部分的了解,但這些文檔大多數使用C++描述。於是,好東西(也算不上好好的吧J)不敢獨享,經過整理我將自己用Delphi的實現方法寫出來,並簡單的講解一下OLE Drag and Drop機制。
所謂OLE Drag and Drop不用翻譯大家一看就能知道它的意思了,它使不同的程序(或同一個程序)通過相互拖動數據來進行交互成為可能。 在這方面windows為我們在後面做了很復雜的工作,幸運的是我們不用擔心它的復雜性,Windows已經為我們提供了兩個相當關鍵的接口:IDropSource、IDropTarget,我們只用實現這兩個接口便可以方便的實現OLE Drag and Drop,前者由允許拖放自己數據的數據源程序實現,後者由允許接收拖放數據的數據目標程序所實現。在本文中,我們只討論後者,因為我們只希望接收來自其它程序拖放過來的數據,而前者已經被大多數程序實現了(如IE、Windows幫助系統等,如果想了解更多關於IDropSource的實現請參看win32 sdk幫助文件)。
接下來我們簡單的了解一下windows是怎樣在後面實現數據拖放的,然後我們實現IDropTarget的一個例子程序(關於程序中的api和格式會在出現的時候給予說明)。Windows在後台調用了一個重要的DoDragDrop函數來檢測接口和調用有我們實現的接口方法,下面是這個函數工作時大概的步驟:
·當我們開始向可以接收數據的窗體拖動數據時,DoDragDrop首先檢查鼠標下的窗體是否被注冊為可以接收的窗體(通過RegisterDragDrop api來注冊,該函數有兩個參數,第一個為要注冊的窗體的句柄,第2個為指向我們實現IDropTarget的類的一個對象指針,在我們的窗體不需要再接收任何拖動過來的數據時使用RevokeDragDrop來解除注冊,它只有一個參數,就是欲解除的窗體句柄,另外重要的一點是要成功的調用這些函數,我們必須在程序開始時使用OleInitialize(nil)在結束時調用OleUninitialize以便初始化OLE library。)
·如果窗體可以接收拖動,DoDragDrop便調用IDropTarget接口的DragEnter方法,該方法通過一個引用參數返回一個拖動的效果dwEffect,它可以有不同的取值(通過檢查IDataObject來決定),你可以在幫助中找到這些值,其中有表示復制、剪切等的操作(指對於實現IDropSource的程序),具體的你還會在下文的代碼中看到。然後DoDragDrop通過調用IDropSource::GiveFeedback來將dwEffect傳遞給IDropSource。
·接下來DoDragDrop根據鼠標的狀態調用諸如IDropTarget接口的DragOver、DragLeave方法,整個過程是在循環中不斷的檢測鼠標的狀態來實現的,如果這時你改變了拖動目標它會再次檢測新的目標並重復上面的過程,如果你在鍵盤上同時按住了其它的鍵,它會調用IDropSource::QueryContinueDrag並在改變了鍵盤狀態碼(你可以通過DragEnter、DragOvert中的grfKeyState參數來檢測改值,並根據它做相應的工作)後繼續重復上面的過程。
·當我們最後松開鼠標後DoDragDrop將調用IDropTarget的Drop方法,它最後一次返回dwEffect,最後根據dwEffect我們可以在這個方法中得到IDataObject中的數據,一次完整的拖放操作就完成了。下面的圖說明一次拖放操作的過程:
上面說了這麼多,其實都是Windows在後台為我們所做的工作,我們只是大概的了解一下這個過程,下面我們通過一個例子來實現一個可以接受文本的memo,窗體中只有一個Tmemo,請注意代碼中的注釋。我們先來看看在程序主窗口創建和撤消時需要做的一些必要的初始化和結束操作:
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOWner);
OleInitialize(nil);
DragAndDropOLE:=TDragAndDropOLE.Create;
// TDragAndDropOLE便是我們要實現IDropTarget接口的類
end;
destructor TForm1.Destroy;
begin
DragAndDropOLE.Free;
OleUninitialize;
inherited;
end;
下面我們來看看關鍵的TDragAndDropOLE的實現,首先他應該實現IunKnown接口,這是一個基本的接口用來實現引用計數,熟悉COM的朋友應該都知道這個接口及其實現方法,下面只給出實現它的代碼不做詳細說明,主要要注意的是IDropTarget的實現方法:
首先是TDragAndDropOLE的聲明部分:
type
TDragAndDropOLE=Class(TObject,IUnknown,IDropTarget)
private
CanDrop:HResult;
fe:TFormatEtc;//數據的格式,在實現部分給出詳細說明
FRefCount:integer;//引用計數
protected
{ Iunkown }
function _AddRef:integer;stdcall;
function _Release:integer;stdcall;
function QueryInterface(const IID:TGUID;out Obj):HResult;stdcall;
{ IdropTarget }
function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
pt: TPoint; var dwEffect: Longint): HResult;stdcall;
function DragOver(grfKeyState: Longint; pt: TPoint;var dwEffect: Longint):HResult;stdcall;
function DragLeave: HResult;stdcall;
function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint;
var dwEffect: Longint): HResult; stdcall;
public
constructor Create;
destructor Destroy;override;
end;
下面是實現部分,首先初始化部分:
constructor TDragAndDropOLE.Create;
begin
FRefCount:=0;
RegisterDragDrop(Form1.Memo1.Handle,self);//上文提到的函數
end;
destructor TDragAndDropOLE.Destroy;
begin
RevokeDragDrop(Form1.Memo1.Handle);
inherited;
end;
接下來實現Iunknown,不再做詳細說明:
function TDragAndDropOLE._AddRef: integer;
begin
result:=InterLockedDecrement(FRefCount);
if Result=0 then Destroy;
end;
function TDragAndDropOLE._Release: integer;
begin
result:=InterLockedIncrement(FRefCount);
end;
function TDragAndDropOLE.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if GetInterface(IID,Obj) then
result:=S_OK
else result:=E_NOINTERFACE;
end;
最重要的IDropTarget實現:
function TDragAndDropOLE.DragEnter(const dataObj: IDataObject;
grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
result:=E_FAIL;
CanDrop:=E_Fail;
if assigned(dataObj) then
begin
with fe do
begin
cfFormat:=CF_TEXT;
ptd:=nil;
dwAspect:=DVASPECT_CONTENT;
lindex:=-1;
tymed:=TYMED_HGLOBAL;
end;
//大家從上面看到的fe是一種我們處理內存數據時常用的轉換格式
//這裡它表示將數據格式作為文字(cfFormat),並將其存入一塊
//全局的內存區域(tymed:=TYMED_HGLOBAL),更多的格式請在win32
//幫助中搜索TFormatEtc
CanDrop:=dataObj.QueryGetData(fe);//按照fe指定的格式檢查數據
result:=CanDrop;
if not Failed(result) then
dwEffect:=DROPEFFECT_COPY
else dwEffect:=DROPEFFECT_NONE;
//注意這裡我們設置了dwEffect,更多的取值請查看win32幫助
end;
end;
function TDragAndDropOLE.DragLeave: HResult;
begin
result:=S_OK;
end;
function TDragAndDropOLE.DragOver(grfKeyState: Integer; pt: TPoint;
var dwEffect: Integer): HResult;
begin
result:=S_OK;
//我們不需要在這裡做其余的操作,當然你可以根據自己的需要完成自己的方法
end;
function TDragAndDropOLE.Drop(const dataObj: IDataObject;
grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
medium:stgMedium;
hData:HGLOBAL;
begin
result:=E_Fail;
if not Failed(CanDrop) then
begin
result:=dataObj.GetData(fe,medium);
//按照fe的格式將數據存入內存的一塊全局區域,注意medium
hData:=HGLOBAL(GlobalLock(medium.hGlobal));
//GlobalLock鎖定這塊區域,並返回指向它的指針
Form1.Memo1.Text:=pchar(hData);
GlobalUnlock(hData);//接觸鎖定
GlobalFree(hData);//釋放
end;
end;
現在我們可以測試它了。本文只是大概的介紹了一下OLE Drag and Drop,只要仔細研究,大家可以實現更復雜的操作。