拖拽的一些文章摘录

来源:互联网 发布:精灵服务端外网端口 编辑:程序博客网 时间:2024/06/05 05:40
首先纠正大家一个概念性的错误

Delphi中的所有组件的Drag&Drop的相关操作都是伪造的,
Delphi在controls.pas单元中定义了一个自定义消息,如下:
  ...
const
  CM_BASE                   = $B000;
  ...
  <font color=red>CM_DRAG                   = CM_BASE + 47;</font>
  ...

在controls.pas单元中可以看到,Delphi在这里完整的模拟了一套Drag&Drop体系,
基本上大多数的Drag&Drop相关操作都在这里实现了,
少部分的如TreeView和ListView的在ComCtrls.pas中实现

整个体系简单地说分成两部分
1、判断鼠标的动作,并解释为等价的Drag&Drop消息
2、组件接收CMDrag消息,判断参数含义,作出相应的动作,触发相应的事件

其中1可以参见controls.pas中的TDragObject.MouseMsg方法
2则可以参见各控件的CMDrag方法,这个方法被定义为响应CM_Drag消息


到此可以基本得出结论:
Delphi中的Drag&Drop的相关操作都是伪造的,
因此如果我们使用Delphi自身提供的Drag&Drop的相关操作,
则只有Drag&Drop在同一个应用程序中发生,而且所有相关控件都是Delphi控件时才可能正确运行。

这里luket提出的问题是要从TWebBrowser中选择文本然后Drop到TreeView中,
但是这里TWebBrowser不是Delphi组件,Delphi只是引用了系统中注册的COM对象,并封装为Delphi类而已
因此TWebBrowser显然不会与Delphi组件那样运行,因而TreeView的全部Drag&Drop相关事件都根本不会发生

大家请注意看上面SuperMMX贴的那段代码,其实也在不断的计算鼠标位置、判断鼠标状态,
很明显也是在伪造Drag&Drop操作!  
好啦,按照我的观点得到的结论必然是直接使用Delphi的相关事件是绝对行不通的,
只好用Windows标准的方法了,按照Win32 Help的说法,
要编程控制控件的Drag&Drop动作,必须按照以下步骤:
(a)对于Drag&Drop动作的Target控件: (对于本问就是TreeView)
  1、实现一个IDropTarget接口
  2、调用RegisterDragDrop将一个控件与一个IDropTarget接口实例关联起来
  3、调用RevokeDragDrop取消2中的关联

(b)对于Drag&Drop动作的Source控件:  (对于本问就是TWebBrowser)
  1、实现一个IDropSource接口,控制Drag&Drop动作的Source控件
  2、实现一个IDataObject接口,控制被Drag&Drop的数据
  2、调用DoDragDrop方法,开始Drag&Drop动作

(其中RegisterDragDrop、RevokeDragDrop、DoDragDrop都是API,
Delphi中的接口定义在....../Source/Rtl/Win/Ole2.pas中)

在本问中TWebBrowser不需要我们负责,因此(b)不在讨论之列,我们主要讨论(a)
a.2调用RegisterDragDrop将一个控件与一个IDropTarget接口实例关联起来
程序如下:
type
  TfmMain = class(TForm)
  private
    DropTarget: IDropTarget;
  ...
  end;

procedure TfmMain.FormCreate(Sender: TObject);
begin
  DropTarget := ITreeViewDropTarget.Create(TreeView);
  RegisterDragDrop(TreeView.Handle, DropTarget);
end;

a.3、调用RevokeDragDrop取消2中的关联
程序如下:
procedure TfmMain.FormDestroy(Sender: TObject);
begin
  RevokeDragDrop(TreeView.Handle);
  DropTarget.Free;
end;

好了,上面的代码很简单,不用多说,再来重点看如何实现一个符合我们要求的IDropTarget:
a.1实现一个IDropTarget接口:
首先要作一些和本问不太相关的工作,实现IUnknow中的抽象方法(abstract method),如下:
type
  ITreeViewDropTarget = class(IDropTarget)
  private
    FRefCount: LongInt;
  protected
    TreeView: TTreeView;
  public
    constructor Create(ATreeView: TTreeView);

    function QueryInterface(const iid: TIID; var obj): HResult; override; stdcall;
    function AddRef: Longint; override; stdcall;
    function Release: Longint; override; stdcall;
    ...
    property RefCount: LongInt read FRefCount;
  end;

// 因为要和TreeView绑定
// 所以增加了一个TreeView,保存目标TreeView的引用
constructor ITreeViewDropTarget.Create(ATreeView: TTreeView);
begin
  Inherited Create;
  FRefCount := 0;
  TreeView := ATreeView;
end;

function ITreeViewDropTarget.QueryInterface(const iid: TIID; var obj): HResult;
begin
  IUnknown(Obj) := Nil;
  Result := S_OK;
end;

function ITreeViewDropTarget.AddRef: Longint;
begin
  Inc(FRefCount);
  Result := RefCount;
end;

function ITreeViewDropTarget.Release: Longint;
begin
  Dec(FRefCount);
  Result := RefCount;
end;


各位,下面就是最重要的工作了,实现IDropTarget的四个方法:
如下:
type
  ITreeViewDropTarget = class(IDropTarget)
  public
    ...
    function DragEnter(dataObj: IDataObject; grfKeyState: Longint;
      pt: TPoint; var dwEffect: Longint): HResult; override; stdcall;
    function DragOver(grfKeyState: Longint; pt: TPoint;
      var dwEffect: Longint): HResult; override; stdcall;
    function DragLeave: HResult; override; stdcall;
    function Drop(dataObj: IDataObject; grfKeyState: Longint; pt: TPoint;
      var dwEffect: Longint): HResult; override; stdcall;
    ...
  end;
这四个方法的作用请大家去看Win32 Help,写得很详细,我就不翻译了,
实现的代码如下:(这里只是说明原理,因此一切从简)
function ITreeViewDropTarget.DragEnter(dataObj: IDataObject; grfKeyState: Longint;
                                       pt: TPoint; var dwEffect: Longint): HResult;
begin
  dwEffect := DropEffect_Copy;
  Result := S_OK;
end;

function ITreeViewDropTarget.DragOver(grfKeyState: Longint; pt: TPoint;
                                      var dwEffect: Longint): HResult;
begin
  dwEffect := DropEffect_Copy;
  Result := S_OK;
end;

function ITreeViewDropTarget.DragLeave: HResult;
begin
  Result := S_OK;
end;

function ITreeViewDropTarget.Drop(dataObj: IDataObject; grfKeyState: Longint; pt: TPoint;
                                  var dwEffect: Longint): HResult;
begin
  dwEffect := DropEffect_Copy;
  Result := S_OK;
end;


现在我们可以修改上面四个方法,详细地控制Drag&Drop操作,
我懒得作了,luket同志自己搞定吧

最后剩余的问题就是在ITreeViewDropTarget.Drop方法中应该对Drop过来的数据进行处理
数据全部在DataObj: IDataObject中,具体如何使用luket同志自己去看Win32 Help吧,
有很详细的说明,我没仔细看,想来应该不难


好啦,我就到此为止了,
各位有兴趣的同志继续研究一下,根据luket把上面四个方法写完吧!  
 
这是 DFW 上最早有关 Drag&Drop 的完整描述,也是我第一次看到 李颖 这个名字。今天
是 2003 年的 5 月,DFW 新建了一个 Keylife 的功能,几天前,我看到论坛上有关这个
Drag&Drop 的问题还有很多人在问,有趣的是,回答几乎都是:“用 Drag&Drop 控件包”!
于是想在自己的 Keylife 中写一篇这样的主题。写前,总想先看看 DFW 上有关这个主题
的提问和回答的质量,于是我就查到了这里(也有幸看到了李颖和他的帖子)。看完后,基
本已经打消了写这个主题的念头,DFW 什么都有,有控件包用,也不是什么坏事,想深入
了解的,查就是了,更何况最早的帖子是在 1999 年。
做为补充,我在这里写一点直接使用这个 IDropTarget 的代码,原理李颖已经说完了。
1.直接在类声明中声明(其实是继承) IDropTarget 接口,不需要使用代理。
type
 TForm1 = class(TForm,IDropTarget) // Delphi 中,类不支持多重继承,但接口可以
  private
    // 实现 IDropTarget 接口需要实现的全部方法
    // 标准的做法:使用别名,以免和 Delphi 自己的控件同名方法重名。
    // 显然,不怕编译警告,可以忽略下面四个别名声明。
    function IDropTarget.DragEnter=ADragEnter;
    function IDropTarget.DragOver=ADragOver;
    function IDropTarget.DragLeave=ADragLeave;
    function IDropTarget.Drop=ADrop;

    function ADragEnter(const dataObj:IDataObject; grfKeyState: Longint;
                   pt: TPoint; var dwEffect: Longint): HResult; stdcall;
    function ADragOver(grfKeyState: Longint; pt: TPoint;
                               var dwEffect: Longint): HResult; stdcall;
    function ADragLeave: HResult; stdcall;
    function ADrop(const dataObj: IDataObject; grfKeyState: Longint;
                   pt: TPoint; var dwEffect: Longint): HResult; stdcall;
 end;
2.在创建过程中绑定拖拉。
procedure TForm1.FormCreate(Sender: TObject);
begin
 OleInitialize(nil);                            // 在调用前初始化 Ole 库
 if RegisterDragDrop(Handle,Self) <> S_OK then  // 注册成功了?
     ShowMessage('拖放注册失败');
end;
3.在析构中解除绑定。
procedure TForm1.FormDestroy(Sender: TObject);
begin
 RevokeDragDrop(Handle); // 终止拖拉授权
 OleUninitialize;
end;
4.在 IDropTarget 接口的 Drop 方法中执行处理。
function TForm1.ADrop(const dataObj: IDataObject; grfKeyState: Integer;
  pt: TPoint; var dwEffect: Integer): HResult;
var
  EnumFormat:IEnumFormatEtc;
  FormatEtc: TFormatEtc;
begin
 DataObj.EnumFormatEtc(DATADIR_GET,EnumFormat); // 取得 IEnumFORMATETC 接口
  while (EnumFormat.Next(1,FormatEtc, nil) <> S_FALSE) do  // 枚举开始
   begin
     case FormatEtc.cfFormat of
         CF_TEXT:GetText(DataObj,FormatEtc); // GetText(...) 是一个自己的处理函数
     end;
   end;  
  Result := S_OK;
end;
5.在 IDropTarget 接口的 DragOver 方法中定义光标样式。
function TForm1.ADragOver(grfKeyState: Integer; pt: TPoint;
  var dwEffect: Integer): HResult;
begin
  dwEffect := DROPEFFECT_COPY;
  Result := S_OK;
end;
6.剩下的 2 个接口方法,可以直接返回 S_OK 。
7.一个处理函数的样式 —— GetText(...) 。
procedure TForm1.GetText(DataObj: IDataObject; FormatEtc: TFormatEtc);
var
 p: pointer;
 StgMed:TStgMedium;
begin
  if (DataObj.QueryGetData(FormatEtc) = NOERROR) then
  begin
    DataObj.GetData(FormatEtc,StgMed); // 获取 stgMEDIUM 结构
    p := GlobalLock(StgMed.hGlobal);   // 获取内存首地址指针
    ListBox1.Items.Add(string(p));     // 文本装入一个 ListBox 控件
    GlobalFree(StgMed.hGlobal);
    ReleaseStgMedium(StgMed);
  end;
end;
以上这些希望能够作为补充。为希望真正了解“拖拉”实质而查找到这里的富翁提供一点帮助。
原创粉丝点击