Difference between revisions of "Build custom dock manager/ru"

From Free Pascal wiki
Jump to navigationJump to search
Line 79: Line 79:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Implementing basic custom DockManager==
+
==Реализация базового настраиваемого DockManager==
  
It is possible to derive our custom manager from default TDockTree. But for purposes of creating a completely custom manager, the parent class has to be TDockManager. All methods from the parent class should be overridden.
+
Наш собственный менеджер можно получить из TDockTree по умолчанию. Но для создания полностью настраиваемого менеджера родительский класс должен быть TDockManager. Все методы родительского класса следует переопределить.
  
* For basic demonstration, the dock manager should be able to manage one control. That class can be extended later.
+
* Для базовой демонстрации диспетчер стыковочного узла должен уметь управлять одним элементом управления. Позже этот класс может быть расширен.
  
Here is a basic prototype based on TDockManager virtual methods.
+
Вот базовый прототип, основанный на виртуальных методах TDockManager.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 111: Line 111:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
* DockManager has to know to which TWinControl belong. So manager needs field FDockSite of type TWinControl which is assigned by constructor.
+
* DockManager должен знать, к какому TWinControl принадлежит. Итак, менеджеру необходимо поле FDockSite типа TWinControl, которое назначается конструктором.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 117: Line 117:
 
   FDockSite: TWinControl;</syntaxhighlight>
 
   FDockSite: TWinControl;</syntaxhighlight>
  
* Now we can implement a method PositionDockRect which determines dock frame size according to DockSite size.  
+
* Теперь мы можем реализовать метод PositionDockRect, который определяет размер фрейма стыковочного узла в соответствии с размером DockSite.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 126: Line 126:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
* To make dock manager default for all dockable controls, insert an initialization section to the end of the unit. Then simple inclusion of the dock manager unit into main form unit will setup your docking system.
+
* Чтобы сделать диспетчер стыковочного узла по умолчанию для всех пристыковываемых элементов управления, вставьте раздел инициализации в конец модуля. Затем простое включение модуля менеджера стыковочного узла в модуль основной формы настроит вашу систему стыковки.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 132: Line 132:
 
   DefaultDockManagerClass := TCustomDockManager;</syntaxhighlight>
 
   DefaultDockManagerClass := TCustomDockManager;</syntaxhighlight>
  
* If dock site is resized, the ResetBounds method is executed. Then we can use it to position the docked control on dock site. We have to allocate some space on top of the control for the dock grabber.
+
* Если размер стыковочного узла изменяется, выполняется метод <tt>ResetBounds</tt>. Затем мы можем использовать его для размещения стыкуемого элемента управления на стыковочном узле. Нам нужно выделить место поверх элемента управления для граббера(захватчика) стыкуемого элемента.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 156: Line 156:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
* Now we need to draw a grabber with control Name on top of the dock site.
+
* Теперь нам нужно нарисовать граббер с именем элемента поверх стыковочного узла.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 170: Line 170:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
For painting the dock site, the canvas responds to the PaintSite method, so we need to implement it. Only visible controls belonging to the dock site should be painted.
+
Для отрисовки стыковочного узла холст использует метод <tt>PaintSite</tt>, поэтому нам нужно его реализовать. Должны быть отрисованы только видимые элементы управления, принадлежащие стыковочному узлу.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 212: Line 212:
  
  
''to be continued...''
+
''продолжение следует...''
  
 
==See also==
 
==See also==

Revision as of 20:09, 19 August 2021

English (en) русский (ru)

Введение

VCL и LCL поддерживают базовую стыковку элементов управления. Для расширенного режима стыковки необходимо создать настраиваемый диспетчер стыковочного узла. Базовый абстрактный класс диспетче растыковочного узла TDockManager расположен в модуле Controls. Все настраиваемые менеджеры должны быть производными от этого класса. Базовая реализация TDockManager в LCL - это TDockTree, способная обрабатывать дерево стыковочных зон.

Рекомендуемая литература: LCL Drag Dock

Возможные функции стыковки

  • Иерархия зоны стыковки
    • Один элемент
    • Линейный список
    • Дерево
    • Таблица
    • Привязанный макет
    • Еще одна сложная организация
  • Пристыкованные вкладки
  • Пристыковка нескольких форм к плавающему объединенному окну
  • Темы
  • Постоянство, хранение/загрузка состояния макета стыковки
  • Пристыковка вкладок с автоматическим отображением/скрытием форм, кнопка с изображением булавки для переключения автоматического скрытия/отображения
  • Пользовательские кнопки заголовка
  • Создание макета по умолчанию прямо из кода, ручное управление
  • Поддержка визуальных компонентов времени разработки, настройка формы стыковки
  • Класс для глобальных операций пристыковки

Перетаскивание

Пристыковка - это, по сути, особый случай действия перетаскивания для форм и подобных элементов управления. Поэтому основные события элементов управления аналогичны.

События перетаскивания: OnDragOver, OnDragDrop, OnStartDrag, OnEndDrag. События стыковки: OnDockOver, OnDockDrop, OnStartDock, OnEndDock, OnUnDock.

Перетаскиваемый объект представлен классом TDragObject. Этот класс расширен до TDragControlObject; затем это расширяется до TDragDockObject для поддержки функций стыковки. Весь процесс перетаскивания контролируется TDragManager, аналогично TDockManager, управляющему процессом закрепления.

Подсистема стыковки

Поддержка TControl

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; // Высота, используемая при отстыковке
  property UndockWidth: Integer read GetUndockWidth write FUndockWidth; // Ширина, используемая при отстыковке
  ...
end;

Поддержка TWinControl

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;

Реализация базового настраиваемого DockManager

Наш собственный менеджер можно получить из TDockTree по умолчанию. Но для создания полностью настраиваемого менеджера родительский класс должен быть TDockManager. Все методы родительского класса следует переопределить.

  • Для базовой демонстрации диспетчер стыковочного узла должен уметь управлять одним элементом управления. Позже этот класс может быть расширен.

Вот базовый прототип, основанный на виртуальных методах TDockManager.

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 должен знать, к какому TWinControl принадлежит. Итак, менеджеру необходимо поле FDockSite типа TWinControl, которое назначается конструктором.
private
  FDockSite: TWinControl;
  • Теперь мы можем реализовать метод PositionDockRect, который определяет размер фрейма стыковочного узла в соответствии с размером DockSite.
procedure TCustomDockManager.PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign;
  var DockRect: TRect); override; overload;
begin
  DockRect := Rect(0, 0, FDockSite.ClientWidth, FDockSite.ClientHeight);
end;
  • Чтобы сделать диспетчер стыковочного узла по умолчанию для всех пристыковываемых элементов управления, вставьте раздел инициализации в конец модуля. Затем простое включение модуля менеджера стыковочного узла в модуль основной формы настроит вашу систему стыковки.
initialization
  DefaultDockManagerClass := TCustomDockManager;
  • Если размер стыковочного узла изменяется, выполняется метод ResetBounds. Затем мы можем использовать его для размещения стыкуемого элемента управления на стыковочном узле. Нам нужно выделить место поверх элемента управления для граббера(захватчика) стыкуемого элемента.
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;
  • Теперь нам нужно нарисовать граббер с именем элемента поверх стыковочного узла.
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;

Для отрисовки стыковочного узла холст использует метод PaintSite, поэтому нам нужно его реализовать. Должны быть отрисованы только видимые элементы управления, принадлежащие стыковочному узлу.

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;


продолжение следует...

See also