zoukankan      html  css  js  c++  java
  • Delphi中停靠技术的实现

    随着软件技术的不断进步,软件界面也越来越美观,操作也越来越方便。
    综观市面上比较专业的各种软件,我们会发现大部分都提供窗体停靠的功能,特别象工具软件,基本上都或多或少有停靠功能。
    自然,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;


    现在再运行程序,当你把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);
    Begin
    LeftDockPanel.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);
    begin
    MainForm.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只在停靠窗体变成浮动时触发(注:貌似是错的,我的理解是:OnUnDock只能在停靠控件离开被停靠控件时,由被停靠控件触发)
    讲了那么多,大家有没有被搞糊涂?那好,我来做一下总结:
    在Delphi中只要是从TWinControl继承的控件都支持被停靠(如上面的LeftDockPanel),也就是有DockSite这个属性;所有从TControl继承的控件都支持停靠(如上面的DockableForm),也就是有DragKind这个属性.所以支持被停靠的控件都支持停靠,支持停靠的控件不一定支持被停靠,道理很简单,因为TWinControl继承于TControlOnDockOver事件是控制停靠窗体的预览位置;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。
    方法:
    1.TControl.ManualFloat //和ManualDock相对应,使浮动。
    2.TControl.ReplaceDockedControl //替换停靠控件
    3.TWinControl .DoAddDockClient
    4.TWinControl .DockDrop
    5.TWinControl .DockOver
    6.TWinControl .DoDockOver
    7.TWinControl .DoUnDock
    消息:
    CM_DOCKCLIENT,
    CM_DOCKNOTIFICATION,
    CM_UNDOCKCLIENT

  • 相关阅读:
    C#+API实现指定窗体激活
    DEVC++学习之(一)
    javascript 实现原生下载的各种情况
    IssueVision 之WebService安全篇
    Add relationship to BS sample
    ExpandRelationWithCtxt 与 GetRelatedObjects 的区别
    C#调用javascript
    解禁网页限制
    Unix cc options vs gcc options
    IssueVision 之模式篇
  • 原文地址:https://www.cnblogs.com/cxp2009/p/3275007.html
Copyright © 2011-2022 走看看