随着软件技术的不断进步,软件界面也越来越美观,操作也越来越方便。综观市面上比较专业的各种软件,我们会发现大部分都提供窗体停靠的功能,特别象工具软件,基本上都或多或少有停靠功能。自然,Delphi也支持停靠,而且她和VCL紧密结合,对于广大的Delphi程式员来说更是一大福音。让我们省去枯燥的编码时间。把注意力集中在核心程式的构思上。

先让我们来复习一下VCL的结构,在TWinControl类中有一个DockSite属性(boolean),他的作用是是否允许别的控件停靠在他的上面,在TControl类中有一个DragKind属性,如果要这个控件能停靠在别的控件上,就把DragKind属性设成dkDock。就这么简单,只要设置一下属性,一个支持停靠的程式就完成了。

当然,上面说的只是最最基本的步骤,有了以上两步,我们就能继续编写代码实现更复杂的功能。一般的支持停靠的程式都能在主窗口的上下左右停靠,也就是说在主窗口的边上放上能被停靠的控件比较好(只要是从TWinControl继承的都行),一般我们都选择TPanel,为了便于读者理解,我们能假定主窗口的左边能停靠,所以在主窗口上放一个Align属性为alLeft的Panel,取名为LeftDockPanel,宽度为0,DockSite属性为True,当然我们的LeftDockPanel应该是能改动大小的,所以在他右边再放一个TSplitter,取名为LeftSplitter,Align属性为alLeft。

接下来就是停靠控件了,一般的程式停靠控件都是窗体,所以我们也建一个窗体,取名叫DockableForm,DragKind属性设成dkDock,DragMode属性设为dmAutomatic(自动停靠)。

目前我们能运行这个程式了,什么?效果不好?停靠的窗体停靠停靠进去后就不见了!哦,我差点忘了,当停靠窗体停靠时Delphi会产生一些事件,他们分别是:

1.OnDockOver(Sender: TObject; Source: TDragDockObject;X, Y: Integer; State: TDragState; var Accept: Boolean);

2.OnDockDrop(Sender: TObject; Source: TDragDockObject;X, Y: Integer);

3.OnGetSiteInfo(Sender: TObject; DockClient: TControl;var InfluenceRect: TRect;

MousePos: TPoint; var CanDock:Boolean);

4.OnStartDock(Sender: TObject;var DragObject: TDragDockObject);

5.OnEndDock(Sender, Target: TObject; X, Y: Integer);

6.OnUnDock(Sender: TObject; Client: TControl;NewTarget: TWinControl; var Allow: Boolean);

哇,这么多,别急,让我细细道来:

先让我们来看看第一个事件OnDockOver是在停靠控件(DockableForm)掠过被停靠控件(LeftDockPanel)时触发的。Source包含了停靠―拖动操作的信息,其中有一个重要的属性是Control,就是DockableForm,另一个重要的属性是DockRect,就是停靠的位置;X,Y是鼠标的位置,State的状态有dsDragEnter, dsDragLeave, dsDragMove,分别表示拖动进入,拖动离开,拖动移动;Accept是是否同意停靠的意思。OnDockOver事件主要作用是控制停靠窗体的预览位置,下面我们来加入以下代码:

procedure TMainForm.LeftDockPanelDockOver(Sender: TObject;Source: TDragDockObject; X, Y: Integer; State: TDragState;var Accept: Boolean);
var
ARect: TRect;
begin
Accept := Source.Control is TDockableForm;
if Accept then
begin
//修改预览停靠位置
ARect.TopLeft := LeftDockPanel.ClientToScreen(Point(0, 0));
ARect.BottomRight := LeftDockPanel.ClientToScreen(Point(Self.ClientWidth div 3, LeftDockPanel.Height));
Source.DockRect := ARect;
end;
end;     目前再运行程式,当你把DockableForm拖动到主窗口左边时,已出现了预览停靠位置,也就是虚线包含的范围。怎么?窗体又不见了?那
当然了,我们只是讲了OnDockOver,还没周详讲解OnDockDrop呢,他才是决定停靠窗体在哪里出现的罪魁祸首:
OnDockDrop(Sender: TObject;Source: TDragDockObject; X, Y: Integer)参数和OnDockOver差不多,
只是少了State: TDragState和var Accept: Boolean他是在停靠窗体进入被停靠控件时发生的,作用是控制停靠窗体的最终位置。
 
下面添加如下代码:
procedure TMainForm.LeftDockPanelDockDrop(Sender: TObject;Source: TDragDockObject; X, Y: Integer);BeginLeftDockPanel.Width := ClientWidth div 3;LeftSplitter.Left := LeftDockPanel.Width + LeftSplitter.Width;End;

目前再运行程式,哇塞,成功了。出现了一个和Delphi的IDE完全相同的停靠窗体,上面是两条横线,用来把他拖出来,右上角有一个小X是用来关闭的。不过好景不长,当我们把他关闭时,装载DockableForm的LeftDockPanel不能还原,还是霸占着主窗口的客户区,怎么办?

嘻嘻,忘了告诉你们了,其实Delphi早就为我们作好了一切。请打开DockableForm的关闭事件,你会发现原来当你点击右上角那个小X关闭DockableForm时,他会触发DockableForm的OnClose事件,在OnClose事件中把LeftDockPanel的宽度设为0就行了。

procedure TDockableForm.FormClose(Sender: TObject;var Action: TCloseAction);beginMainForm.LeftDockPanel.Width := 0;Action := caHide;end;

以上所讲的是怎么在主窗口上停靠窗体,原代码都通过测试。同理,我们能在主窗口的右边,下边,上边都实现停靠功能。

对了,刚才我们只介绍了OnDockOver和OnDockDrop,忘了介绍别的事件,下面简单介绍一下:

3.OnGetSiteInfo(Sender: TObject; DockClient: TControl;

var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);

这个事件是在窗体移动时触发的,所以经常触发,他里面的DockClient就是TDockableForm,

有一个引用参数叫CanDock,和OnDockOver中的Accept差不多,都是询问是否允许停靠。在这里能不写,CanDock默认就是True,也能写上CanDock := DockClient is TDockableForm;

4.OnStartDock(Sender: TObject;

var DragObject: TDragDockObject);

5.OnEndDock(Sender, Target: TObject; X, Y: Integer);

6.OnUnDock(Sender: TObject; Client: TControl;

NewTarget: TWinControl; var Allow: Boolean);

这三个事件都是在DockableForm上面有用,意思分别是停靠开始,停靠结尾,不停靠(也就是被拖出来时)。

OnStartDock和OnEndDock经常会被触发,

OnUnDock只在停靠窗体变成浮动时触发

讲了那么多,大家有没有被搞糊涂?那好,我来做一下总结:

在Delphi中只要是从TWinControl继承的控件都支持被停靠(如上面的LeftDockPanel),也就是有DockSite这个属性;所有从TControl继承的控件都支持停靠(如上面的DockableForm),也就是有DragKind这个属性.所以支持被停靠的控件都支持停靠,支持停靠的控件不一定支持被停靠,道理非常简单,因为TWinControl继承于TControl。OnDockOver事件是控制停靠窗体的预览位置;OnDockDrap事件是控制停靠窗体的最终位置;OnGetSiteInfo是询问是否能停靠;OnStartDock是停靠开始,OnEndDock是停靠结尾,OnUnDock是不停靠(也就是被拖出来时)。

想必Delphi用的熟的大虾都知道在Delphi的可停靠窗体间能相互停靠,而且花样还非常多,能停靠成并排的,也能停靠成PageControl样式的,两个可停靠窗体合并后的窗体又能再和别的可停靠窗体合并,形成树状。下面来介绍这方面的技术:

说道这里,我们不得不介绍一下CM_DOCKCLIENT消息和TCMDockClient结构,

CM_DOCKCLIENT消息和TCMDockClient结构是相互对应的,TCMDockClient的结构是:

TCMDockClient = packed record

Msg: Cardinal;

DockSource: TDragDockObject;

MousePos: TSmallPoint;

Result: Integer;

end;

其中DockSource包含了停靠―拖动操作的信息,前面已提到过;MousePos是鼠标的位置。CM_DOCKCLIENT事件在停靠和被停靠控件都能捕捉,因为他是TWinControl类发出的,

代码如下:

procedure TWinControl.DockDrop(Source: TDragDockObject; X, Y: Integer);

begin

if (Perform(CM_DOCKCLIENT, Integer(Source), Integer(SmallPoint(X, Y))) >= 0)

and Assigned(FOnDockDrop) then

FOnDockDrop(Self, Source, X, Y);

end;

能看出,TWinControl是先发送DOCKCLIENT消息,再触发OnDockDrop事件的。

为了演示可停靠窗体之间相互停靠,我们先创建一个宿主窗体,取名叫TiledHost,把他的DockSite设成True。他的作用是用来装载两个DockableForm的。

首先在DockableForm中捕捉DOCKCLIENT消息,在里面完成两个窗体的相互停靠

声明:

private

procedure CMDockClient(var Message: TCMDockClient); message CM_DOCKCLIENT;

end;

实现:

procedure TDockableForm.CMDockClient(var Message: TCMDockClient);

var

Host: TForm;

begin

if Message.DockSource.Control is TDockableForm then

begin

Host := TTiledHost.Create(Application);

Host.BoundsRect := Self.BoundsRect;

Self.ManualDock(Host, nil, alNone);

Self.DockSite := False;

Message.DockSource.Control.ManualDock(Host, nil, alNone);

TDockableForm(Message.DockSource.Control).DockSite := False;

Host.Visible := True;

End;

end;

先解释一下上面的代码,首先创建TTiledHost的实例,然后用ManualDock函数把自己停靠到TTiledHost,把Message.DockSource.Control也停靠到TTiledHost,这样就完成了窗体的相互停靠,当然,要是我们要程式产生停靠的预览效果,就在DockableForm的OnDockOver事件里加入代码:

procedure TDockableForm.FormDockOver(Sender: TObject;

Source: TDragDockObject; X, Y: Integer; State: TDragState;

var Accept: Boolean);

var

ARect: TRect;

begin

Accept := Source.Control is TDockableForm;

if Accept then

begin

ARect.TopLeft := ClientToScreen(Point(0, 0));

ARect.BottomRight := ClientToScreen(

Point(ClientWidth div 2, ClientHeight));

Source.DockRect := ARect;

end;

end;

怎么样,效果还能吧。对了,需要注意的是,用ManualDock函数能安全的完成停靠功能,不要用Dock函数。ManualDock函数有一些参数:

function ManualDock(NewDockSite: TWinControl; DropControl: TControl = nil; ControlSide: TAlign = alNone): Boolean;

NewDockSite:要被停靠的窗体;

DropControl:已存在于NewDockSite的TControl,在这里能把他设成nil;

ControlSide: 停靠的位置,能是上,下,左,右,全部等。

当然,我们也能让TiledHost也具有和LeftDockPanel相同有被停靠的功能,只要把TiledHost看成前面的LeftDockPanel,添加一些属性和事件;把TiledHost看成DockableForm,

就能有停靠的功能了。具体的做法这里不再阐述了,相信对VCL有深刻研究的大虾都知道怎么做了。

下面我来讲一下两个窗体怎样停靠成PageControl样式。

首先创建一个窗体,叫TabHost,在他上面放一个PageControl,Align属性设成alClient,让他占满整个TabHost,别忘了把PageControl的DockSite属性设成True.

然后我们依次加入代码:

procedure TDockableForm.FormDockOver(Sender: TObject;

Source: TDragDockObject; X, Y: Integer; State: TDragState;

var Accept: Boolean);

var

ARect: TRect;

begin

Accept := Source.Control is TDockableForm;

if Accept then

begin

ARect.TopLeft := ClientToScreen(ClientRect.TopLeft);

ARect.BottomRight := ClientToScreen(ClientRect.BottomRight);

Source.DockRect := ARect;

end;

procedure TDockableForm.CMDockClient(var Message: TCMDockClient);

var

Host: TForm;

begin

if Message.DockSource.Control is TDockableForm then

begin

Host := TTabHost.Create(Application);

Host.BoundsRect := Self.BoundsRect;

Self.ManualDock(TTabHost(Host).PageControl1, nil, alClient);

Message.DockSource.Control.ManualDock(TTabHost(Host).PageControl1, nil, alClient);

Host.Visible := True;

End;

End;

代码的具体意思在这里就不再解释了,同理也能让TabHost具有停靠和被停靠的功能。还需要说明一下,TPageControl封装了一些对停靠的支持,他捕捉了CM_DOCKCLIENT,

CM_DOCKNOTIFICATION,CM_UNDOCKCLIENT,WM_LBUTTONDBLCLK消息处理停靠动作。具体能查看TPageControl的原代码。

工具条的停靠也相同,在主窗体上放一个ControlBar或CoolBar,把他们的DockSite设成True;再在上面放ToolBar, ToolBar的DragKind属性设成dkDock,DragMode属性设为dmAutomatic。在这里,TControl有一个属性叫FloatingDockSiteClass,他的类型是TWinControl的引用(class of TWinControl),只要在主窗口创建时,把ToolBar的FloatingDockSiteClass属性设成某一个窗体A,比如在设计时A这个窗体叫ToolBarDockForm,但在程式里面不用显式的创建A,Delphi会自动创建,当ToolBar被拖动出来时,Delphi自动把他装载到ToolBarDockForm里,当然ToolBarDockForm也要象上面提到的DockableForm相同设置一定的属性和添加一些代码。

讲了一大堆,还是没有把Delphi支持的停靠功能全部讲完,据我所知,更有非常多。还是把他们列出来供大家参考(前面介绍的就省略了)

属性:

1.TControl. TBDockHeight //存储停靠控件在停靠时的的高度;

2.TControl. LRDockWidth //存储停靠控件在停靠时的的宽度;

3.TControl. UnDockHeight //存储停靠控件在浮动时的的高度;

4.TControl. UnDockWidth //存储停靠控件在浮动时的的宽度;

5.TControl. HostDockSite //存储被停靠控件的实例

6.TControl. FloatingDockSiteClass //前面讲过

7.TControl. Floating //是否浮动

9.TControl. DockOrientation //停靠控件的方位

10.TWinControl .DockClientCount //在这个控件里面有几个已停靠的控件

11.TWinControl . DockClients //在这个控件里面有已停靠的控件的列表

12.TWinControl . DockManager //一个控制停靠的类,其实是个ActiveX控件,和他对应的类是TDockTree.

13. TWinControl .UseDockManager //是否使用DockManager。

转载于:https://www.cnblogs.com/delphi7456/archive/2010/11/09/1873030.html

Delphi中停靠技术的实现[转]相关推荐

  1. Delphi中的容器类(二)

    TStrings类 出于效率的考虑,Delphi并没有象C++和Java那样将字符串定义为类,因此TList本身不能直接存储字符串,而字符串列表又是使用非常广泛的,为此Borland提供了TStrin ...

  2. Delphi中的线程类

    Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchr ...

  3. 探究:如何判断Delphi中的对象指针是否可用

    2019独角兽企业重金招聘Python工程师标准>>> 近日,在网上看到有网友问曰:如何确定一个对象指针是否可用?也就是说,如何确定一个对象指针是否指向一个真正可用的对象实例?其实这 ...

  4. 自己对Delphi中使用正则表达式的研究心得

    在 Delphi 中使用正则表达式, 目前 PerlRegEx 应该是首选, 在此分享一下自己的一些心得体会. 官方网站: http://www.regular-expressions.info/de ...

  5. Delphi中的容器类

    Delphi中的容器类 Posted on 2008-11-15 11:30 YangHe 阅读(122) 评论(0) 编辑 收藏 从Delphi 5开始VCL中增加了一个新的Contnrs单元,单元 ...

  6. Delphi中DLL封装业务逻辑的实现

      三层结构是开发C/S或B/S系统经常采用的策略,这种分层方式将系统分为用户服务.业务服务和数据服务三部分,能够解决客户端与服务器结构维护成本问题.改善客户端与服务器结构延展性问题:解决应用逻辑重复 ...

  7. (转)剖析Delphi中的构造和析构

    剖析Delphi中的构造和析构 1 Delphi中的对象模型: 2 1.1 对象名表示什么? 2 1.2 对象存储在哪里? 2 1.3 对象中存储了什么?它们是如何存储的? 3 2 构造函数与创建对象 ...

  8. Delphi中的容器类(3)

    TBucketList和TObjectBucketList类 从Delphi6开始,VCL的Contnrs单元中又增加了两个新的容器类TBucketList和TObjectBucketList.TBu ...

  9. delphi 中几种多线程操作方式

    在了解多线程之前我们先了解一下进程和线程的关系 一个程序至少有一个主进程,一个进程至少有一个线程. 主线程又程为UI线程. 进程和线程的主要差别在于它们是不同的操作系统资源管理方式.进程有独立的地址空 ...

最新文章

  1. MLPerf Inference 0.7应用
  2. RXSwift基本使用1
  3. nginx.pid failed (2: The system cannot find the file specified
  4. vim ctrlp找到文件后,如何在新窗口或者新标签中打开
  5. PHP 基础篇 - PHP 中 DES 加解密详解
  6. [Leedcode][JAVA][第198题][打家劫舍][动态规划]
  7. 【Java】JShell工具上手即用
  8. jQuery源码分析 整体框架部分及部分常用方法
  9. Swift - 高级运算符介绍
  10. 百度网盘资源转迅雷下载正确打开方式!
  11. 路由变化时使用axios取消所有请求
  12. 站在知乎肩上-做更强的自己(3)
  13. ANSYS湿模态分析(一)_51CAE_新浪博客
  14. 我国将大力发展装配式建筑 2025年装配式建筑占新建建筑比例将超30%
  15. 分享一个react 图片上传组件 支持OSS 七牛云
  16. layui之会议OA系统4.0
  17. OracleConnection.ConnectionString
  18. 原来我是个自由主义者
  19. xp好还是vista好_在XP,Vista和Windows 7中播放您喜欢的DOS游戏
  20. 三星复仇计划开始:苹果一出LTE设备就要起诉它!

热门文章

  1. nn.moduleList 和Sequential的理解
  2. 20150623 javascript实现的简单刷贴
  3. Day10-1.多态 、抽象类
  4. 谁看谁懂的3dmax、ZBrush和Maya区别
  5. 百付宝携手瑞星 打造零风险支付平台
  6. 数据归一化:两种常用的归一化方法
  7. httpd配置.md
  8. 数据库系统概论笔记(第一章 引言)—— 持续更新,争取每周更新一章
  9. 破解root用户密码
  10. 世界上唯一的7星级酒店