9.2开发拖放功能的一般步骤
拖放作为windows提供的一种方便操作对象的功能,在delphi中可以很容易地开发
出来。根据拖放操作的过程可以把开发步骤划分为四个阶段,即:
●开始拖动操作
●接收拖动项目
●放下拖动项目
●终止拖动操作
在介绍过程中我们将结合一个tabset(标签集)的拖放操作实例。界面设计如图。
在运行时当用户把一个标签拖动到另一个标签的位置时,该标签将移动到该位置并引起
标签集的重新布置。
9.2.1开始拖动操作
当拖动模式(dragmode)设置为dmautomatic时,用户在源控件上按下鼠标时拖动自动
开始;当设置为dmmanual时通过处理鼠标事件来决定拖动是否开始。如果想开始拖动调
用begindrag方法。
在tabset拖放中,我们用下面的mousedown事件处理过程来开始一个标签的拖动。
首先判断按下的是否是左键,而后再判断项目是否合法。
procedure tform1.tabset1mousedown(sender: tobject; button: tmousebutton;
shift: tshiftstate; x, y: integer);
var
dragitem: integer;
begin
if button = mbleft then
begin
dragitem := tabset1.itematpos(point(x, y));
if (dragitem > -1) and (dragitem < tabset1.tabs.count) then
tabset1.begindrag(false);
end;
end;
9.2.2接收拖动项目
一个控件能否接收拖动项目是由该控件的ondragover事件决定的。在tabset拖动中,主要是利用鼠标的位置进行判断。
procedure tform1.tabset1dragover(sender, source: tobject; x, y: integer;
state: tdragstate; var accept: boolean);
var
droppos: integer;
begin
if source = tabset1 then
begin
droppos := tabset1.itematpos(point(x, y));
accept := (droppos > -1) and (droppos <> tabset1.tabindex) and
(droppos < tabset1.tabs.count);
end;
else
accept := false;
end;
9.2.3放下拖动项目
当ondragover事件处理过程返回的accept为true且项目被放下时,由ondragdrop事
件处理过程来完成拖动放下后的响应。在tabset拖放实例中是改变标签的位置。
procedure tform1.tabset1dragdrop(sender, source: tobject; x, y: integer);
var
oldpos: integer;
newpos: integer;
begin
if source = tabset1 then
begin
oldpos := tabset1.tabindex;
newpos := tabset1.itematpos(point(x, y));
if (newpos > -1) and (newpos <> oldpos) then
tabset1.tabs.move(oldpos, newpos);
end;
end;
9.2.4结束拖动操作
结束拖动操作的方式有两种:或者是用户释放了鼠标键或者是程序用enddrag方法
强行中止拖动。结束拖动操作的后果有两种:放下被接受或放下被忽略。
拖动操作结束后源控件都要收到一条消息响应拖动结束事件onenddrag。
9.3 拖放应用实例:文件管理器的拖放支持
在第六章最后开发的文件管理器应用实例,虽然功能上已初具规模,但在操作上
与windows的文件管理器相比还有很大不足。其中最大的缺陷是它不支持文件的拖放移动
和拖放拷贝。在这一章结束的时候,我们可以来弥补这一缺陷了。
文件拖放移动指的是当用户把一个文件拖动到目录树下的某一目录并放下时,文件
将自动移动到该目录中;文件拖放拷贝指的是当用户把一个文件拖动到某个驱动器标签
上并放下时,文件将自动拷贝到该驱动器的当前目录下。作为源控件的文件列表框和作
为目标控件的目录树、驱动器标签可以位于不同的子窗口。驱动器的当前目录是任一子
窗口的最新操作结果,而不论这一子窗口与拖动源、拖动目标是否有关系。
为了实现上述功能,有两个问题必须首先解决:
1.如何记录每一驱动器的当前目录?
为此我们定义了一个全局变量:
var curentdirlist: array[0...25] of string[70];
在directoryoutline的onchange事件中:
procedure tfmform.directoryoutlinechange(sender: tobject);
begin
createcaption;
filelist.clear;
filelist.directory := directoryoutline.directory;
filelist.update;
currentdirlist[drivetabset.tabindex] := directoryoutline.directory;
filemanager.directorypanel.caption := directoryoutline.directory;
end;
由于drivetabset在响应ondragdrop事件前先响应onclick事件,并由该事件激
发directoryoutline的onchange事件,因而可保证在任何时候ondragdrop事件中用
到的currentdirlist数组项不为空字符串。
2.如何保证移动、拷贝与子窗口的无关性?
在这里一个关键问题是我们判断源控件时是用is操作符进行类型检查:
if source is tfilelist then …
如果我们用下面的语句:
if source = filelist then
…
则移动、拷贝操作将限制在本子窗口范围内。
当解决了上述问我们的工作就只是遵循拖放的一般开发步骤,按步就班来完成了。
1.filelist开始拖动操作
procedure tfmform.filelistmousedown(sender: tobject; button: tmousebutton;
shift: tshiftstate; x, y: integer);
begin
if button = mbleft then
with sender as tfilelistbox do
begin
if itematpos(point(x, y), true) >= 0 then
begindrag(false);
end;
end;
itematpos用来检查当前是否有文件存在。而begindrag方法传递参数false,允许filelist单独处理鼠标事件而并不开始拖动。事实上这种情况是大量存在的。
2.directoryoutline、drivetabset决定是否能接受拖动的就地放下。
procedure tfmform.directoryoutlinedragover(sender, source: tobject; x,
y: integer; state: tdragstate; var accept: boolean);
begin
if source is tfilelistbox then
accept := true;
end;
procedure tfmform.drivetabsetdragover(sender, source: tobject; x,
y: integer; state: tdragstate; var accept: boolean);
var
proppos: integer;
begin
if source is tfilelistbox then
with drivetabset do
begin
proppos := itematpos(point(x,y));
accept := (proppos > -1) and (proppos < tabs.count);
end;
end;