Anchor Docking

From Lazarus wiki
Jump to navigationJump to search

About docking in general see Docking.

Overview

Docking allows to combine forms or to treat controls as forms. You can use the mouse to drag and dock forms together or undock (split) them. The basic dragging, docking and undocking is provided by the LCL. But you need a docking manager to add splitters and grab zones and to save and restore layouts.

Anchor docking was implemented in the package examples/anchordocking/anchordocking.lpk (Since 0.9.29) is easy to use and provides a lot of extra features and options.

An example can be found in examples/anchordocking/miniide/miniide1.lpi

Obsolete: The old TLazDockingManager and unit LDockCtrl are deprecated. A step by step example

Features

  • Drag and dock: Headers are added that can be dragged to dock controls with the mouse. Preview rectangles will show where and how a control is docked.
  • Any layout: By using the LCL's anchors almost any layout is possible. You can dock to left, right, top, bottom, inside or outside or as page like in a TPageControl. You can enlarge docked controls via the popup menu. No need to undock your whole layout.
  • What you see is how it is structured: There are no hidden panels to align controls in rows and columns. Anchor docking makes sure forms do not overlap and that you can .
  • Splitters are automatically inserted between docked forms to allow resizing.
  • Page docking. Forms can be docked not only left/right/above/below, but in pages too. A TPageControl is automatically created for native look and feel. A page can contain arbitrary docked forms too, including paged docked forms, allowing nested pages and layouts. When a page is undocked the pagecontrol is automatically removed. You can drag and drop the tabs or use the popup menu to move pages.
  • Easy use: With one line of code you can make a form or control dockable. Just give them unique names.
  • Not only forms, but any TWinControl can be made dockable.
  • Can save/load layouts from/to xml config files. The layout information is stored for each dockable control and old layout files will still work even when the application gets more dockable controls or some are removed.
  • Each docked form is put into a docksite providing a header. The header shows the caption of the docked form and contains a close button. You can drag the header to drag the form and undock it or to dock it at another position.
  • The header can be at any of the four sides and moves by default automatically to the smaller side to save space. You can customize this.
  • The header shows a customizable hint allowing to read long captions.
  • When a docksite is resized the child sites are scaled. You can turn this off.
  • Preserve size of forms. For instance when docking a form to a left side of a site the width of the docked form is kept. The same for the other sides and the same for undocking.
  • Flickerless: The restore tries to reuse existing sites and splitters to reduce creation and flickering of forms and to preserve the Z order of forms. This allows to quickly switch between layouts.
  • Header popup menu:
    • lock/unlock (disable dragging)
    • header position (auto, left, top, right, bottom), this is saved/restored with the layout
    • merge (for example after moving a dock page into a layout)
    • undock (needed if no place to undock on screen)
    • enlarge side to left, top, right, bottom
    • close
  • Page popup menu:
    • lock/unlock (disable dragging)
    • undock (needed if no place to undock on screen)
    • tab position for pagecontrol (top,bottom, left, right), this is saved/restored with the layout
    • move page to the left, right, leftmost, rightmost
    • close
  • Forms can be made dock sites. Then they can dock at one side. But they can not be docked itself.

ToDos

  • Write examples and docs
  • Write a design time package for the IDE
  • Easy docking via code
  • popup menu
    • shrink side left, top, right, bottom
    • options
  • simple way to make forms dockable at designtime
  • minimize button and Hide
  • close button: save a default layout
  • on show again: restore a default layout
  • close button for pages
  • Implement automatic menu merging in the LCL (when two forms with main menus are docked).

Usage

Add the package AnchorDocking to the required packages in the project inspector.

Then add the unit AnchorDocking to the uses section of your main unit (the unit with your main form).

Use DockMaster.MakeDockable and/or MakeDocksite on your forms. Read further for examples.

Making forms dockable

There are two ways to make a form dockable. You can use DockMaster.MakeDockable to wrap a form into a dock site. Then your form can dock and be docked completely free. You will use that for the biggest part of your forms. The second way is to use DockMaster.MakeDockSite. The form can dock other sites, but can not be docked itself. You might want to use this for the main form.

Example: miniide1

The example can be found in lazarus/examples/anchordocking/miniide/miniide1.lpi. It has a mainform in unit unti1.pas with a main menu which works as a docksite and several forms that are dockable.

Making the main form a dock site

In the TMainIDE.FormCreate the main form MainIDE is turned into a docksite: <Delphi> DockMaster.MakeDockSite(Self,[akBottom],admrpChild); </Delphi>

The parameter [akBottom] allows to dock other sites at the bottom of MainIDE. The parameter admrpChild tells the DockMaster that when the MainIDE is enlarged the extra space is distributed to the docked sites.

Procedure to create all other forms by name

The MainIDE sets the DockMaster.OnCreateControl event, to enable the DockMaster to create forms by name. This is needed to restore layouts. <Delphi> DockMaster.OnCreateControl:=@DockMasterCreateControl; </Delphi>

<Delphi> procedure TMainIDE.DockMasterCreateControl(Sender: TObject; aName: string; var

 AControl: TControl; DoDisableAutoSizing: boolean);
 procedure CreateForm(Caption: string; NewBounds: TRect);
 begin
   AControl:=CreateSimpleForm(aName,Caption,NewBounds,DoDisableAutoSizing);
 end;

begin

 // first check if the form already exists
 // the LCL Screen has a list of all existing forms.
 // Note: Remember that the LCL allows as form names only standard
 // pascal identifiers and compares them case insensitive
 AControl:=Screen.FindForm(aName);
 if AControl<>nil then begin
   // if it already exists, just disable autosizing if requested
   if DoDisableAutoSizing then
     AControl.DisableAutoSizing;
   exit;
 end;
 // if the form does not yet exist, create it
 if aName='CodeExplorer' then
   CreateForm('Code Explorer',Bounds(700,230,100,250))
 ...
 else if aName='DebugOutput' then
   CreateForm('Debug Output',Bounds(400,400,350,150));

end; </Delphi>

Creating a form can be as simple as this: <Delphi> MyForm:=TMyForm.Create(Self); if DoDisableAutoSizing then

 MyForm.DisableAutoSizing;

</Delphi>

But of course you can put there all kind of initialization code.

The DisableAutoSizing reduces flickering. Make sure your forms Visible property is set to false.

Remember to call DisableAutoSizing for the form if the parameter DoDisableAutoSizing is set to true, because Disable- and EnableAutosizing calls must be balanced. If you miss the DisableAutosizing, the LCL will raise an exception later:

TControl.EnableAutoSizing SourceEditor1:TSimpleForm: missing DisableAutoSizing

MissingDisableAutoSizing.png

If you call DisableAutoSizing too much, your forms will not appear and/or not resize properly as the LCL is waiting for the EnableAutoSizing call that never comes.

Showing forms

When a form should be shown, you have probably used something like 'MyForm.Show. Instead you should now use <Delphi> DockMaster.MakeDockable(MyForm); </Delphi>

This will wrap MyForm in a dock site and show it. It is clever enough to figure out if the form is already wrapped in a dock site.

If you set the OnCreateControl event you can use this: <Delphi> DockMaster.ShowControl('MyForm',true); </Delphi>

Save layout

The DockMaster allows to save the current layout to a TConfigStorage. The miniide example uses the xml version:

<Delphi> procedure TMainIDE.SaveLayout(Filename: string); var

 XMLConfig: TXMLConfigStorage;

begin

 try
   // create a new xml config file
   XMLConfig:=TXMLConfigStorage.Create(Filename,false);
   try
     // save the current layout of all forms
     DockMaster.SaveLayoutToConfig(XMLConfig);
     XMLConfig.WriteToDisk;
   finally
     XMLConfig.Free;
   end;
 except
   on E: Exception do begin
     MessageDlg('Error',
       'Error saving layout to file '+Filename+':'#13+E.Message,mtError,
       [mbCancel],0);
   end;
 end;

end; </Delphi>

Load layout

<Delphi> procedure TMainIDE.LoadLayout(Filename: string); var

 XMLConfig: TXMLConfigStorage;

begin

 try
   // load the xml config file
   XMLConfig:=TXMLConfigStorage.Create(Filename,True);
   try
     // restore the layout
     // this will close unneeded forms and call OnCreateControl for all needed
     DockMaster.LoadLayoutFromConfig(XMLConfig);
   finally
     XMLConfig.Free;
   end;
 except
   on E: Exception do begin
     MessageDlg('Error',
       'Error loading layout from file '+Filename+':'#13+E.Message,mtError,
       [mbCancel],0);
   end;
 end;

end; </Delphi>


Create a TLazDockingManager

<DELPHI>

 DockingManager:=TLazDockingManager.Create(Self);

</DELPHI> The Self as parameter is only used as Owner. That means, when the mainform is freed, the DockingManager is freed too. You can use nil and free the DockingManager yourself.

Optional: Loading a configuration

You can load the user configuration from disk. <DELPHI>

 Config:=TXMLConfigStorage.Create('config.xml',true);
 DockingManager.LoadFromConfig(Config);
 Config.Free;

</DELPHI> This loads the file config.xml. The config can be created by the SaveToConfig function. See below.

Make a form/control dockable

Create a TLazControlDocker for each form/control that should be dockable <DELPHI>

 ControlDocker1:=TLazControlDocker.Create(Self);
 ControlDocker1.Name:='DockerForm1';
 ControlDocker1.Manager:=DockingManager;

</DELPHI>

Optional: Saving the user configuration to disk

When the program is closed you can save the user configuration to disk <DELPHI>

 Config:=TXMLConfigStorage.Create('config.xml',true);
 DockingManager.SaveToConfig(Config);
 Config.WriteToDisk;
 Config.Free;

</DELPHI>

Enlarge/Shrink

The anchor docking manager can enlarge/shrink docked neighbors. This is done via the function TAnchoredDockManager.EnlargeControl.

Enlarge one, shrink another

Shrink one neighbor control, enlarge Control. Two splitters are resized.

     |#|         |#         |#|         |#
     |#| Control |#         |#|         |#
   --+#+---------+#   --> --+#| Control |#
   ===============#       ===#|         |#
   --------------+#       --+#|         |#
       A         |#        A|#|         |#
   --------------+#       --+#+---------+#
   ==================     ===================

Enlarge one, shrink many

Move one neighbor splitter, enlarge Control, resize one splitter, rotate the other splitter.

     |#|         |#|          |#|         |#|
     |#| Control |#|          |#|         |#|
   --+#+---------+#+--  --> --+#| Control |#+--
   ===================      ===#|         |#===
   --------+#+--------      --+#|         |#+--
           |#|   B            |#|         |#|B
           |#+--------        |#|         |#+--
       A   |#=========       A|#|         |#===
           |#+--------        |#|         |#+--
           |#|   C            |#|         |#|C
   --------+#+--------      --+#+---------+#+--
   ===================      ===================

Why use Anchors instead of Align?

Anchors allow to create any possible rectangular layout. Align is limited. Align with Panels can theoretically create a lot of layouts, but still not all. And some operations like enlarge/shrink are more complicated using Align/Panels than using Anchors. Align works good for a few forms, but the more forms are docked, the more the disadvantages of Align become visible.


Layouts that can be created with Align

Align can only create the following layouts:

 +------------------------------------------+
 |      alTop                               |
 +------------------------------------------+
 +------------------------------------------+
 |      alTop                               |
 +------------------------------------------+
 +------++------++--------++-------++-------+
 |alLeft||alLeft||alClient||alRight||alRight|
 +------++------++--------++-------++-------+
 +------------------------------------------+
 |      alBottom                            |
 +------------------------------------------+
 +------------------------------------------+
 |      alBottom                            |
 +------------------------------------------+

The alTop controls are always at the top, filling the whole horizontal width. That's because the LCL first aligns all controls with alTop, then all alBottom, then alLeft, then alRight and finally alClient.

Layouts that can be created with Align and Panels

It is possible to nest Align layouts by using hidden panels. Then any layout that can be recursively splitted in halves can be created. Then you can create for example:

 +--++-------++---+
 |  ||   B   ||   |
 |  |+-------+|   |
 |A |+-------+| E |
 |  ||   C   ||   |
 |  |+-------+|   |
 |  |+-------+|   |
 |  ||   D   ||   |
 +--++-------++---+

This requires only one hidden panel.

Changing Layouts

Now the user wants to enlarge D horizontally (and shrink E):

 +--++-------++---+
 |  ||   B   ||   |
 |  |+-------+|   |
 |A |+-------+| E |
 |  ||   C   ||   |
 |  |+-------++---+
 |  |+------------+
 |  ||   D        |
 +--++------------+

Now you need 3 panels. One for B,C,D,E, one for B,C,E and one for B,C. An algorithm to allow this layout change, must analyze the whole structure, as if there were no panels and must reparent a lot of things. Basically the algorithm must do the same as the anchor docking algorithm, but with the extra work of translating the layout into Align plus hidden panels.

Layouts that are impossible with Align and Panels

Now the user wants to enlarge B horizontally (and shrink A):

 +-----------++---+
 |    B      ||   |
 +-----------+|   |
 +--++-------+| E |
 |A ||   C   ||   |
 |  |+-------++---+
 |  |+------------+
 |  ||   D        |
 +--++------------+

This layout is impossible with Align and panels. The same with

  +-----------++--------------+
  |    A      ||              |
  +-----------+|      B       |
  +-----++----+|              |
  |     ||    |+--------------+
  |     || D  |+----++--------+
  |  C  ||    ||  E ||        |
  |     |+----++----+|   F    |
  |     |+----------+|        |
  |     ||    G     ||        |
  +-----++----------++--------+

Conclusion

Align with hidden panels allows to easily create a simple docking manager that works good for a few forms. But it will always limit the user. Align is useful for row and column layouts, not for tables. Anchor docking works even for complex forms.