Difference between revisions of "Build custom dock manager"

From Lazarus wiki
Jump to navigationJump to search
m (Text replace - "delphi>" to "syntaxhighlight>")
Line 37: Line 37:
 
===TControl support===
 
===TControl support===
  
<delphi>TControl = class
+
<syntaxhighlight>TControl = class
 
   ...
 
   ...
 
   procedure DragDrop(Source: TObject; X,Y: Integer); virtual;
 
   procedure DragDrop(Source: TObject; X,Y: Integer); virtual;
Line 57: Line 57:
 
   property UndockWidth: Integer read GetUndockWidth write FUndockWidth; // Width used when undocked
 
   property UndockWidth: Integer read GetUndockWidth write FUndockWidth; // Width used when undocked
 
   ...
 
   ...
end;</delphi>
+
end;</syntaxhighlight>
  
 
===TWinControl support===
 
===TWinControl support===
  
<delphi>TWinControl = class(TControl)
+
<syntaxhighlight>TWinControl = class(TControl)
 
   ...
 
   ...
 
   property DockClientCount: Integer read GetDockClientCount;
 
   property DockClientCount: Integer read GetDockClientCount;
Line 74: Line 74:
 
   ...
 
   ...
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
==Implementing basic custom DockManager==
 
==Implementing basic custom DockManager==
Line 84: Line 84:
 
Here is basic prototype based on TDockManager virtual methods.
 
Here is basic prototype based on TDockManager virtual methods.
  
<delphi>TCustomDockManager = class(TDockManager)
+
<syntaxhighlight>TCustomDockManager = class(TDockManager)
 
   constructor Create(ADockSite: TWinControl); override;
 
   constructor Create(ADockSite: TWinControl); override;
 
   procedure BeginUpdate; override;
 
   procedure BeginUpdate; override;
Line 105: Line 105:
 
   procedure SetReplacingControl(Control: TControl); override;
 
   procedure SetReplacingControl(Control: TControl); override;
 
   function AutoFreeByControl: Boolean; override;     
 
   function AutoFreeByControl: Boolean; override;     
end;</delphi>
+
end;</syntaxhighlight>
  
 
* DockManager have to know to which TWinControl belong. So manager need field FDockSite of type TWinControl which is assigned by constructor.
 
* DockManager have to know to which TWinControl belong. So manager need field FDockSite of type TWinControl which is assigned by constructor.
  
<delphi>private
+
<syntaxhighlight>private
   FDockSite: TWinControl;</delphi>
+
   FDockSite: TWinControl;</syntaxhighlight>
  
 
* Now we can implement method PositionDockRect which determine dock frame size according to size of DockSite.  
 
* Now we can implement method PositionDockRect which determine dock frame size according to size of DockSite.  
  
<delphi>procedure TCustomDockManager.PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign;
+
<syntaxhighlight>procedure TCustomDockManager.PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign;
 
   var DockRect: TRect); override; overload;
 
   var DockRect: TRect); override; overload;
 
begin
 
begin
 
   DockRect := Rect(0, 0, FDockSite.ClientWidth, FDockSite.ClientHeight);
 
   DockRect := Rect(0, 0, FDockSite.ClientWidth, FDockSite.ClientHeight);
end;</delphi>
+
end;</syntaxhighlight>
  
 
* To make dock manager default for all dockable controls insert initialization section to end of unit. Then simply inclusion of dock manager unit into main form unit will setup your docking system.
 
* To make dock manager default for all dockable controls insert initialization section to end of unit. Then simply inclusion of dock manager unit into main form unit will setup your docking system.
  
<delphi>initialization
+
<syntaxhighlight>initialization
   DefaultDockManagerClass := TCustomDockManager;</delphi>
+
   DefaultDockManagerClass := TCustomDockManager;</syntaxhighlight>
  
 
* It dock site is resized method ResetBounds is executed. Than we can use it to position docked control on dock site. We have to allocate some space on top of control for dock grabber.
 
* It dock site is resized method ResetBounds is executed. Than we can use it to position docked control on dock site. We have to allocate some space on top of control for dock grabber.
  
<delphi>const
+
<syntaxhighlight>const
 
   GrabberSize = 18;
 
   GrabberSize = 18;
  
Line 146: Line 146:
 
       end;
 
       end;
 
     end;
 
     end;
end;</delphi>
+
end;</syntaxhighlight>
  
 
* Now we need to draw grabber with control Name on top of dock site.
 
* Now we need to draw grabber with control Name on top of dock site.
  
<delphi>procedure TCustomDockManager.DrawGrabber(Canvas: TControlCanvas; AControl: TControl);
+
<syntaxhighlight>procedure TCustomDockManager.DrawGrabber(Canvas: TControlCanvas; AControl: TControl);
 
begin
 
begin
 
   with Canvas do begin
 
   with Canvas do begin
Line 159: Line 159:
 
     TextOut(6, 2, AControl.Caption);
 
     TextOut(6, 2, AControl.Caption);
 
   end;
 
   end;
end;</delphi>
+
end;</syntaxhighlight>
  
 
For painting dock site canvas is responding method PaintSite so we need implement it. Only visible controls belonged to dock site should be painted.
 
For painting dock site canvas is responding method PaintSite so we need implement it. Only visible controls belonged to dock site should be painted.
  
<delphi>procedure TCustomDockManager.PaintSite(DC: HDC);
+
<syntaxhighlight>procedure TCustomDockManager.PaintSite(DC: HDC);
 
var
 
var
 
   Canvas: TControlCanvas;
 
   Canvas: TControlCanvas;
Line 198: Line 198:
 
     Canvas.Free;
 
     Canvas.Free;
 
   end;
 
   end;
end;</delphi>
+
end;</syntaxhighlight>
  
  

Revision as of 13:56, 24 March 2012

Introduction

VCL and LCL support basic docking of controls. For advanced docking behavior custom dock manager have to be created. Basic abstract dock manager class TDockManager is placed in Controls unit. All custom manager have to be deriver from this class. Basic LCL implementation of TDockManager is TDockTree which is capable of handling tree of dock zones.

Recommended reading: LCL Drag Dock

Possible docking features

  • Docking zone hierarchy
    • One item
    • Linear list
    • Tree
    • Table
    • Anchored layout
    • Another complex organization
  • Tabbed docking
  • Docking multiple forms to floating conjoined window
  • Themes
  • Persistence, Store/Restore docking layout
  • Tabbed docking with autoshow/autohide forms, pin button to switch autohide/visible
  • Custom header buttons
  • Building default layout directly from code, manual management
  • Supporting visual design-time components, docking customize form
  • Class for global docking operations

Dragging

Docking is basically special case of Drag & Drop action for forms and similar controls. Therefore basic events of controls are similar.

Dragging events: OnDragOver, OnDragDrop, OnStartDrag, OnEndDrag Docking events: OnDockOver, OnDockDrop, OnStartDock, OnEndDock, OnUnDock

Dragged object is represented by class TDragObject and this class is further extended to class TDragControlObject and for needs of docking next to TDragDockObject. Whole process of dragging is controlled by TDragManager similarly to TDockManager.

Docking subsystem

TControl support

TControl = class
  ...
  procedure DragDrop(Source: TObject; X,Y: Integer); virtual;
  procedure Dock(NewDockSite: TWinControl; ARect: TRect); virtual;
  function ManualDock(NewDockSite: TWinControl;
    DropControl: TControl = nil; ControlSide: TAlign = alNone;
    KeepDockSiteSize: Boolean = true): Boolean; virtual;
  function ManualFloat(TheScreenRect: TRect;
    KeepDockSiteSize: Boolean = true): Boolean; virtual;

  property DockOrientation: TDockOrientation read FDockOrientation write FDockOrientation;
  property Floating: Boolean read GetFloating;
  property FloatingDockSiteClass: TWinControlClass read GetFloatingDockSiteClass 
    write FFloatingDockSiteClass;
  property HostDockSite: TWinControl read FHostDockSite write SetHostDockSite;
  property LRDockWidth: Integer read GetLRDockWidth write FLRDockWidth;
  property TBDockHeight: Integer read GetTBDockHeight write FTBDockHeight;
  property UndockHeight: Integer read GetUndockHeight write FUndockHeight; // Height used when undocked
  property UndockWidth: Integer read GetUndockWidth write FUndockWidth; // Width used when undocked
  ...
end;

TWinControl support

TWinControl = class(TControl)
  ...
  property DockClientCount: Integer read GetDockClientCount;
  property DockClients[Index: Integer]: TControl read GetDockClients;
  property DockManager: TDockManager read FDockManager write SetDockManager;
  property DockSite: Boolean read FDockSite write SetDockSite default False;
  property OnUnDock: TUnDockEvent read FOnUnDock write FOnUnDock;
  property UseDockManager: Boolean read FUseDockManager
    write SetUseDockManager default False;
  property VisibleDockClientCount: Integer read GetVisibleDockClientCount;
  procedure DockDrop(DragDockObject: TDragDockObject; X, Y: Integer); virtual;
  ...
end;

Implementing basic custom DockManager

It is possible to derive our custom manager from default TDockTree. But for purpose of creating completely custom manager parent class have to be TDockManager. All methods from parent class should be overridden.

  • For basic demonstration dock manager should be able to manage one control. That class can be extended later.

Here is basic prototype based on TDockManager virtual methods.

TCustomDockManager = class(TDockManager)
  constructor Create(ADockSite: TWinControl); override;
  procedure BeginUpdate; override;
  procedure EndUpdate; override;
  procedure GetControlBounds(Control: TControl;
    out AControlBounds: TRect); override;
  function GetDockEdge(ADockObject: TDragDockObject): boolean; override;
  procedure InsertControl(ADockObject: TDragDockObject); override; overload;
  procedure InsertControl(Control: TControl; InsertAt: TAlign;
    DropCtl: TControl); override; overload;
  procedure LoadFromStream(Stream: TStream); override;
  procedure PaintSite(DC: HDC); override;
  procedure MessageHandler(Sender: TControl; var Message: TLMessage); override;
  procedure PositionDockRect(ADockObject: TDragDockObject); override; overload;
  procedure PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign;
    var DockRect: TRect); override; overload;
  procedure RemoveControl(Control: TControl); override;
  procedure ResetBounds(Force: Boolean); override;
  procedure SaveToStream(Stream: TStream); override;
  procedure SetReplacingControl(Control: TControl); override;
  function AutoFreeByControl: Boolean; override;    
end;
  • DockManager have to know to which TWinControl belong. So manager need field FDockSite of type TWinControl which is assigned by constructor.
private
  FDockSite: TWinControl;
  • Now we can implement method PositionDockRect which determine dock frame size according to size of DockSite.
procedure TCustomDockManager.PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign;
  var DockRect: TRect); override; overload;
begin
  DockRect := Rect(0, 0, FDockSite.ClientWidth, FDockSite.ClientHeight);
end;
  • To make dock manager default for all dockable controls insert initialization section to end of unit. Then simply inclusion of dock manager unit into main form unit will setup your docking system.
initialization
  DefaultDockManagerClass := TCustomDockManager;
  • It dock site is resized method ResetBounds is executed. Than we can use it to position docked control on dock site. We have to allocate some space on top of control for dock grabber.
const
  GrabberSize = 18;

procedure TCustomDockManager.ResetBounds(Force: Boolean);
var
  I: Integer;
  Control: TControl;
  R: TRect;
begin
  for I := 0 to FDockSite.ControlCount - 1 do
    begin
      Control := FDockSite.Controls[I];
      if Control.Visible and (Control.HostDockSite = FDockSite) then
      begin
        R := Control.BoundsRect;
        Control.SetBounds(0, GrabberSize, FDockSite.Width - Control.Left,
          FDockSite.Height - Control.Top);
      end;
    end;
end;
  • Now we need to draw grabber with control Name on top of dock site.
procedure TCustomDockManager.DrawGrabber(Canvas: TControlCanvas; AControl: TControl);
begin
  with Canvas do begin
    Brush.Color := clBtnFace;
    Pen.Color := clBlack;
    FillRect(0, 0, AControl.Width, GrabberSize);
    Rectangle(1, 1, AControl.Width - 1, GrabberSize - 1);
    TextOut(6, 2, AControl.Caption);
  end;
end;

For painting dock site canvas is responding method PaintSite so we need implement it. Only visible controls belonged to dock site should be painted.

procedure TCustomDockManager.PaintSite(DC: HDC);
var
  Canvas: TControlCanvas;
  Control: TControl;
  I: Integer;
  R: TRect;
begin
  Canvas := TControlCanvas.Create;
  try
    Canvas.Control := FDockSite;
    Canvas.Lock;
    try
      Canvas.Handle := DC;
      try
        for I := 0 to FDockSite.ControlCount - 1 do
        begin
          Control := FDockSite.Controls[I];
          if Control.Visible and (Control.HostDockSite = FDockSite) then
          begin
            R := Control.BoundsRect;
            Control.SetBounds(0, GrabberSize, FDockSite.Width - Control.Left,
              FDockSite.Height - Control.Top);
            Canvas.FillRect(R);
            DrawGrabber(Canvas, Control);
          end;
        end;
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  finally
    Canvas.Free;
  end;
end;


to be continued...

See also