使用OLE拖放不同程序間的數(shù)據(jù)
(OLE Drag and Drop)
難度:★★★☆☆
先行知識:Delphi / 接口 / Win32 / OLE or COM
從一個程序拖動數(shù)據(jù)到另一個程序(典型的情況是拖動文本)已經(jīng)不是什么新鮮事了,很多共享軟件都支持這個功能(比如說著名的
FlashGet、netants等的浮動窗口功能)。作者一直想在自己的軟件中實現(xiàn)這個功能,經(jīng)過一段時間的資料搜索,有了部分的了解,但這些文檔大多數(shù)使用C++描述。于是,好東西(也算不上好好的吧J)不敢獨享,經(jīng)過整理我將自己用delphi的實現(xiàn)方法寫出來,并簡單的講解一下OLE Drag and Drop機(jī)制。
所謂OLE Drag and Drop不用
翻譯大家一看就能知道它的意思了,它使不同的程序(或同一個程序)通過相互拖動數(shù)據(jù)來進(jìn)行交互成為可能。 在這方面windows為我們在后面做了很復(fù)雜的工作,幸運的是我們不用擔(dān)心它的復(fù)雜性,windows已經(jīng)為我們提供了兩個相當(dāng)關(guān)鍵的接口:IDropSource、IDropTarget,我們只用實現(xiàn)這兩個接口便可以方便的實現(xiàn)OLE Drag and Drop,前者由允許拖放自己數(shù)據(jù)的數(shù)據(jù)源程序?qū)崿F(xiàn),后者由允許接收拖放數(shù)據(jù)的數(shù)據(jù)目標(biāo)程序所實現(xiàn)。在本文中,我們只討論后者,因為我們只希望接收來自其它程序拖放過來的數(shù)據(jù),而前者已經(jīng)被大多數(shù)程序?qū)崿F(xiàn)了(如IE、windows幫助系統(tǒng)等,如果想了解更多關(guān)于IDropSource的實現(xiàn)請參看win32 sdk幫助文件)。
接下來我們簡單的了解一下windows是怎樣在后面實現(xiàn)數(shù)據(jù)拖放的,然后我們實現(xiàn)IDropTarget的一個例子程序(關(guān)于程序中的api和格式會在出現(xiàn)的時候給予說明)。Windows在后臺調(diào)用了一個重要的DoDragDrop函數(shù)來檢測接口和調(diào)用有我們實現(xiàn)的接口方法,下面是這個函數(shù)工作時大概的步驟:
·當(dāng)我們開始向可以接收數(shù)據(jù)的窗體拖動數(shù)據(jù)時,DoDragDrop首先檢查鼠標(biāo)下的窗體是否被注冊為可以接收的窗體(通過RegisterDragDrop api來注冊,該函數(shù)有兩個參數(shù),第一個為要注冊的窗體的句柄,第2個為指向我們實現(xiàn)IDropTarget的類的一個對象指針,在我們的窗體不需要再接收任何拖動過來的數(shù)據(jù)時使用RevokeDragDrop來解除注冊,它只有一個參數(shù),就是欲解除的窗體句柄,另外重要的一點是要成功的調(diào)用這些函數(shù),我們必須在程序開始時使用OleInitialize(nil)在結(jié)束時調(diào)用OleUninitialize以便初始化OLE library。)
·如果窗體可以接收拖動,DoDragDrop便調(diào)用IDropTarget接口的DragEnter方法,該方法通過一個引用參數(shù)返回一個拖動的效果dwEffect,它可以有不同的取值(通過檢查IDataObject來決定),你可以在幫助中找到這些值,其中有表示復(fù)制、剪切等的操作(指對于實現(xiàn)IDropSource的程序),具體的你還會在下文的代碼中看到。然后DoDragDrop通過調(diào)用IDropSource::GiveFeedback來將dwEffect傳遞給IDropSource。
·接下來DoDragDrop根據(jù)鼠標(biāo)的狀態(tài)調(diào)用諸如IDropTarget接口的DragOver、DragLeave方法,整個過程是在循環(huán)中不斷的檢測鼠標(biāo)的狀態(tài)來實現(xiàn)的,如果這時你改變了拖動目標(biāo)它會再次檢測新的目標(biāo)并重復(fù)上面的過程,如果你在鍵盤上同時按住了其它的鍵,它會調(diào)用IDropSource::QueryContinueDrag并在改變了鍵盤狀態(tài)碼(你可以通過DragEnter、DragOvert中的grfKeyState參數(shù)來檢測改值,并根據(jù)它做相應(yīng)的工作)后繼續(xù)重復(fù)上面的過程。
·當(dāng)我們最后松開鼠標(biāo)后DoDragDrop將調(diào)用IDropTarget的Drop方法,它最后一次返回dwEffect,最后根據(jù)dwEffect我們可以在這個方法中得到IDataObject中的數(shù)據(jù),一次完整的拖放操作就完成了。下面的圖說明一次拖放操作的過程:

上面說了這么多,其實都是windows在后臺為我們所做的工作,我們只是大概的了解一下這個過程,下面我們通過一個例子來實現(xiàn)一個可以接受文本的memo,窗體中只有一個Tmemo,請注意代碼中的注釋。我們先來看看在程序主窗口創(chuàng)建和撤消時需要做的一些必要的初始化和結(jié)束操作:
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOWner);
OleInitialize(nil);
DragAndDropOLE:=TDragAndDropOLE.Create;
// TDragAndDropOLE便是我們要實現(xiàn)IDropTarget接口的類
end;
destructor TForm1.Destroy;
begin
DragAndDropOLE.Free;
OleUninitialize;
inherited;
end;
下面我們來看看關(guān)鍵的TDragAndDropOLE的實現(xiàn),首先他應(yīng)該實現(xiàn)IunKnown接口,這是一個基本的接口用來實現(xiàn)引用計數(shù),熟悉COM的朋友應(yīng)該都知道這個接口及其實現(xiàn)方法,下面只給出實現(xiàn)它的代碼不做詳細(xì)說明,主要要注意的是IDropTarget的實現(xiàn)方法:
首先是TDragAndDropOLE的聲明部分:
type
TDragAndDropOLE=Class(TObject,IUnknown,IDropTarget)
CanDrop:HResult;
fe:TFormatEtc;//數(shù)據(jù)的格式,在實現(xiàn)部分給出詳細(xì)說明
FRefCount:integer;//引用計數(shù)
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;
下面是實現(xiàn)部分,首先初始化部分:
constructor TDragAndDropOLE.Create;
begin
FRefCount:=0;
RegisterDragDrop(Form1.Memo1.Handle,self);//上文提到的函數(shù)
end;
destructor TDragAndDropOLE.Destroy;
begin
RevokeDragDrop(Form1.Memo1.Handle);
inherited;
end;
接下來實現(xiàn)Iunknown,不再做詳細(xì)說明:
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實現(xiàn):
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;
dw
aspect:=DVASPECT_CONTENT;
lindex:=-1;
tymed:=TYMED_HGLOBAL;
end;
//大家從上面看到的fe是一種我們處理內(nèi)存數(shù)據(jù)時常用的轉(zhuǎn)換格式
//這里它表示將數(shù)據(jù)格式作為文字(cfFormat),并將其存入一塊
//全局的內(nèi)存區(qū)域(tymed:=TYMED_HGLOBAL),更多的格式請在win32
//幫助中搜索TFormatEtc
CanDrop:=dataObj.QueryGetData(fe);//按照fe指定的格式檢查數(shù)據(jù)
result:=CanDrop;
if not Failed(result) then
dwEffect:=DROPEFFECT_COPY
else dwEffect:=DROPEFFECT_NONE;
//注意這里我們設(shè)置了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;
//我們不需要在這里做其余的操作,當(dāng)然你可以根據(jù)自己的需要完成自己的方法
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的格式將數(shù)據(jù)存入內(nèi)存的一塊全局區(qū)域,注意medium
hData:=HGLOBAL(GlobalLock(medium.hGlobal));
//GlobalLock鎖定這塊區(qū)域,并返回指向它的指針
Form1.Memo1.Text:=pchar(hData);
GlobalUnlock(hData);//接觸鎖定
GlobalFree(hData);//釋放
end;
end;
現(xiàn)在我們可以測試它了。本文只是大概的介紹了一下OLE Drag and Drop,只要仔細(xì)研究,大家可以實現(xiàn)更復(fù)雜的操作。