Здравствуйте, __Dimentiy, Вы писали:
__D>Всем привет!
__D>Вопрос такой:
__D>Есть TPanel *ppanel и класс окна TMyForm *pform, экземпляры которого будут к этой панели пристыковываться.
__D>Для автоматической стыковки сделано следующее:
__D>panel->DockSite = true;
__D>panel->UseDockManager = true;
form->>DockKind = dkDock;
form->>DragMode = dmAutomatic;
form->>UseDockManager = true;
__D>Всё остальное оставлено по умолчанию.
__D>Суть вопроса заключается в следующем (вообщем объясните ламеру) как к примеру при загрузке приложения (создании новых окон)
__D>втыкать эти окна в панель с нужным размером. Сейчас при стыковке область панели делиться ровно на половину и размер стыкуемых окон изменяется и становиться равным, можно изменить её после стыковки ручками, но при отстыковки и стыковки заново старое положение не запоминается.
__D>Может надо всю эту операцию писать ручками? Если да, то натолкните на мысль в какую сторону копать, пожалуйста.
__D>Спасибо! Удачи!
Зайди сюда:
http://www.torry.net/quicksearchd.php?SID=752fc72946a43f6824d02e67a6d3dda4&String=dockpanel&Title=Yes
и посмотри как там сделано
Здравствуйте, __Dimentiy, Вы писали:
__D>Есть TPanel *ppanel и класс окна TMyForm *pform, экземпляры которого будут к этой панели пристыковываться.
__D>Суть вопроса заключается в следующем (вообщем объясните ламеру) как к примеру при загрузке приложения (создании новых окон)
__D>втыкать эти окна в панель с нужным размером. Сейчас при стыковке область панели делиться ровно на половину и размер стыкуемых окон изменяется и становиться равным, можно изменить её после стыковки ручками, но при отстыковки и стыковки заново старое положение не запоминается.
Это особенности TDockTree, который является стандартным DockManager'ом.
Когда форма отстыковывается от хоста, layout DockManager перестраивается таким образом, чтобы оставшиеся окна
занимали всю площадь хоста. Позиция ранее пристыкованного окна нигде не сохраняется. Поэтому при повторной стыковке,
DockManager действует так, будто это окно стыкуется в первый раз. Управлять этим поведением в стандартном TDockTree нельзя.
Совсем недавно я достаточно плотно занимался докингом окон. Задачей было написать докинг, поддерживающий стыковку
как отдельных окон рядом, так и в виде закладок. И не только на панели, но и на плавающие окна. Т.е. требовалось обеспечить
поведение, как у Delphi/Builder IDE. В процессе написания я наткнулся на ряд неочевидных моментов.
Сохранять и загружать layout можно методами DockManager->SaveToStream() и DockManager->LoadFromStream().
Стандартный механизм сохранения и восстановления TDockTree работает, опираясь на имена компонентов, которые
придочены к хосту. При восстановлении layout методом LoadFromStream() обязательным требованием является наличие
уже созданных компонентов (окон) с теми же самыми именами, что при сохранении. Кроме того, следует учитывать еще,
что TDockTree ищет эти компоненты у Owner своего хозяина, т.е. у Owner хоста (в вашем случае это ppanel->Owner).
Поэтому надо следить за Owner'ом придоченных компонентов. Т.к. панель обычно лежит на форме (и форма — владелец панели),
а владельцем других окон является Application, то если просто сохранить "в лоб" позицию придоченных окон, загрузить
обратно неполучится. Есть и другие проблемы при сохранении и загрузке пристыкованных окон (при загрузке происходит
подвисание).
Собственную реализацию DockManager мне было писать лениво.
В свете выше перечисленных проблем, я использовал не очень красивый, но работающий обходной путь. Перед сохранением
я замещаю все придоченные окна специально созданными временными панелями, сохраняю их позицию и дополнительно информацию
о том, какая панель замещала какое окно. Затем при восстановлении я восстанавливаю сначала layout панелей, а потом
возвращаю на места окна. Такой способ работает.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Здравствуйте, __Dimentiy, Вы писали:
__D>Может надо всю эту операцию писать ручками? Если да, то натолкните на мысль в какую сторону копать, пожалуйста.
Пусть меня побьют ногами, но когда у меня встала задача расширить возможности стандартного докманагера, я посчитал самым нормальным вариантом скопировать весь код TDockTree себе и подправить его как надо. У моего докманагера есть возможность задавать констрейнты, сохранять всю структуру в виде XML-файла, сохранять позицию и размеры придоченных контролов (таким образом что при отстыковке/стыковке он становится в прежнюю позицию), пропорциональное изменение размеров пристыкованных контролов и т.п.. Приведу куски кода относящиеся к сохранению позиции при отстыковке/стыковке. Ключевые моменты я постарался выделить. Все это дает (по крайней мере я на то надеюсь) только общую картину, скомпилировать это даже не стоит пытаться. Всеь код я привести не могу.
type
TCtlDockInfo = class
private
FTree: TMyDockTree;
FControl: TControl;
FParentZone: TMyDockZone;
FSiblingZone: TMyDockZone;
FParentOrient: TDockOrientation;
FInsertBefore: Boolean;
// Property access
function GetAsXmlElement: TXmlElement;
procedure SetAsXmlElement(const Value: TXmlElement);
public
// Construction/destruction
constructor Create(ATree: TMyDockTree; AControl: TControl; AParentZone: TMyDockZone;
ASiblingZone: TMyDockZone; AParentOrient: TDockOrientation;
AInsertBefore: Boolean); overload;
constructor Create(ATree: TMyDockTree; AData: TXmlElement); overload;
// Properties
property AsXmlElement: TXmlElement read GetAsXmlElement write SetAsXmlElement;
end;
// ...
implementation
// ...
procedure TMyDockTree.LoadFromStream(Stream: TStream);
var
SS: TStringStream;
Elem: TXmlElement;
J: Integer;
Info: TCtlDockInfo;
function DoLoadZone(ZoneElem: TXmlElement; ParentZone: TMyDockZone): TMyDockZone;
var
Z: TMyDockZone;
I: Integer;
CompName: string;
PrevZone, ChildZone: TMyDockZone;
begin
Z := TMyDockZone.Create(Self, 0);
Z.FOrientation := TDockOrientation(StrToInt(ZoneElem.Attributes[sZoneOrientation]));
Z.FZoneLimit := StrToInt(ZoneElem.Attributes[sZoneLimit]);
Z.FParentZone := ParentZone;
Z.FID := StrToIntDef(ZoneElem.Attributes[sZoneId], 0);
CompName := ZoneElem.Attributes[sZoneControl];
if (CompName > '') and not Z.SetControlName(CompName) then begin
Z.Free;
raise Exception.Create(SControlNotFound);
end;
PrevZone := nil;
for I := 0 to ZoneElem.ElementCount - 1 do begin
ChildZone := DoLoadZone(ZoneElem.Elements[I], Z);
if I = 0 then
Z.FChildZones := ChildZone;
ChildZone.FPrevSibling := PrevZone;
if Assigned(PrevZone) then
PrevZone.FNextSibling := ChildZone;
PrevZone := ChildZone;
end;
Result := Z;
end;
begin
SS := TStringStream.Create('');
Elem := TXmlElement.Create;
try
SS.CopyFrom(Stream, Stream.Size);
Elem.AsString := SS.DataString;
PruneZone(FTopZone);
FLoading := True;
try
FTopXYLimit := StrToInt(Elem.Attributes[sTopXYLimit]);
FTopZone := DoLoadZone(Elem.FindElement(sZoneElement), nil);
ValidateIDs;
FCtlDockInfo.Clear;
Elem := Elem.FindElement(sDockInfoElement);
if Assigned(Elem) then with Elem do
for J := 0 to ElementCount - 1 do
try
Info := TCtlDockInfo.Create(Self, Elements[J]);
FCtlDockInfo.Add(Info);
except
on EAbort do ;
end;
finally
FLoading := False;
end;
finally
SS.Free;
Elem.Free;
end;
UpdateConstraints;
UpdateAll;
end;
procedure TMyDockTree.InsertControl(Control: TControl; InsertAt: TAlign;
DropCtl: TControl);
const
OrientArray: array[TAlign] of TDockOrientation = (doNoOrient, doHorizontal,
doHorizontal, doVertical, doVertical, doNoOrient);
MakeLast: array[TAlign] of Boolean = (False, False, True, False, True, False);
var
Sibling, Me: TMyDockZone;
InsertOrientation, CurrentOrientation: TDockOrientation;
NewWidth, NewHeight: Integer;
R: TRect;
begin
// Allow to insert hidden controls to avoid blinking at application startup
(*if not Control.Visible then Exit;*)
if (Control is TForm) then
TForm(Control).Position := poDesigned;
if FReplacementZone <> nil then
begin
FReplacementZone.FChildControl := Control;
FReplacementZone.Update;
end
else if FTopZone.FChildZones = nil then
begin
// Tree is empty, so add first child
R := FDockSite.ClientRect;
(*FDockSite.AdjustClientRect(R);*)
NewWidth := R.Right - R.Left;
NewHeight := R.Bottom - R.Top;
(*if FDockSite.AutoSize then
begin
if NewWidth = 0 then NewWidth := Control.UndockWidth;
if NewHeight = 0 then NewHeight := Control.UndockHeight;
end;*)
R := Bounds(R.Left, R.Top, NewWidth, NewHeight);
AdjustDockRect(Control, R);
Control.BoundsRect := R;
Me := TMyDockZone.Create(Self, UniqueZoneID);
FTopZone.FChildZones := Me;
Me.FParentZone := FTopZone;
Me.FChildControl := Control;
end
else if (InsertAt <> alNone) or not ReDockControl(Control) then begin
// Default to right-side docking
if InsertAt in [alNone, alClient] then InsertAt := alRight;
Me := FindControlZone(Control);
if Me <> nil then RemoveZone(Me);
Sibling := FindControlZone(DropCtl);
InsertOrientation := OrientArray[InsertAt];
if FTopZone.ChildCount = 1 then
begin
// Tree only has one child, and a second is being added, so orientation and
// limits must be set up
FTopZone.FOrientation := InsertOrientation;
case InsertOrientation of
doHorizontal:
begin
FTopZone.FZoneLimit := FTopZone.FChildZones.Width;
FTopXYLimit := FTopZone.FChildZones.Height;
end;
doVertical:
begin
FTopZone.FZoneLimit := FTopZone.FChildZones.Height;
FTopXYLimit := FTopZone.FChildZones.Width;
end;
end;
end;
Me := TMyDockZone.Create(Self, UniqueZoneID);
Me.FChildControl := Control;
if Sibling <> nil then CurrentOrientation := Sibling.FParentZone.FOrientation
else CurrentOrientation := FTopZone.FOrientation;
if InsertOrientation = doNoOrient then
InsertOrientation := CurrentOrientation;
// Control is being dropped into a zone with the same orientation we
// are requesting, so we just need to add ourselves to the sibling last
if InsertOrientation = CurrentOrientation then InsertSibling(Me, Sibling,
MakeLast[InsertAt])
// Control is being dropped into a zone with a different orientation than
// we are requesting
else InsertNewParent(Me, Sibling, InsertOrientation, MakeLast[InsertAt]);
end;
RemoveCtlDockInfo(Control);
{ Redraw client dock frames }
FDockSite.Invalidate;
UpdateConstraints;
UpdateAll;
end;
function TMyDockTree.ReDockControl(Control: TControl): Boolean;
var
Info: TCtlDockInfo;
Zone: TMyDockZone;
begin
Result := True;
Info := CtlDockInfo[Control];
if not Assigned(Info) then begin
Result := False;
Exit;
end;
Zone := TMyDockZone.Create(Self, UniqueZoneID);
Zone.FChildControl := Control;
if (Info.FSiblingZone = FTopZone) and (FTopZone.FOrientation <> Info.FParentOrient) then
InsertNewParent(Zone, nil, Info.FParentOrient, not Info.FInsertBefore)
else if (Info.FSiblingZone = FTopZone) then
InsertSibling(Zone, nil, not Info.FInsertBefore)
else if (Info.FParentOrient <> Info.FSiblingZone.FParentZone.FOrientation) then
begin
// Sibling zone is the only child of the top zone
if Info.FSiblingZone.FParentZone.ChildCount = 1 then
InsertNewParent(Zone, nil, Info.FParentOrient, not Info.FInsertBefore)
else InsertNewParent(Zone, Info.FSiblingZone, Info.FParentOrient, not Info.FInsertBefore)
end
// Sibling zone's parent zone's orientation is the same as needed orient
// Simply insert new sibling
else
InsertSibling(Zone, Info.FSiblingZone, not Info.FInsertBefore);
end;
procedure TMyDockTree.RemoveZone(Zone: TMyDockZone);
var
Sibling, LastChild, ParentZone: TMyDockZone;
ZoneChildCount: Integer;
ResetOrient: TDockOrientation;
begin
if Zone = nil then
raise Exception.Create(SDockTreeRemoveError + SDockZoneNotFound);
if Zone.FChildControl = nil then
raise Exception.Create(SDockTreeRemoveError + SDockZoneHasNoCtl);
ParentZone := nil;
ResetOrient := doNoOrient;
ZoneChildCount := Zone.FParentZone.ChildCount;
if ZoneChildCount = 1 then
begin
FTopZone.FChildZones := nil;
FTopZone.FOrientation := doNoOrient;
end
else if ZoneChildCount = 2 then
begin
ResetOrient := Zone.FParentZone.FOrientation;
// This zone has only one sibling zone
if Zone.FPrevSibling = nil then Sibling := Zone.FNextSibling
else Sibling := Zone.FPrevSibling;
if Sibling.FChildControl <> nil then
begin
ParentZone := Zone.FParentZone;
// Sibling is a zone with one control and no child zones
if Zone.FParentZone = FTopZone then
begin
ReplaceParents(Zone, Sibling, Assigned(Zone.FPrevSibling));
ReplaceParents(Sibling, Sibling, Assigned(Sibling.FPrevSibling));
StoreCtlDockInfo(Zone.FChildControl, Sibling, Sibling,
Zone.FParentZone.FOrientation, not Assigned(Zone.FPrevSibling));
// If parent is top zone, then just remove the zone
FTopZone.FChildZones := Sibling;
Sibling.FPrevSibling := nil;
Sibling.FNextSibling := nil;
Sibling.FZoneLimit := FTopZone.LimitSize;
end
else begin
ReplaceParents(Zone, Zone.FParentZone, Assigned(Zone.FPrevSibling));
ReplaceParents(Sibling, Zone.FParentZone, Assigned(Sibling.FPrevSibling));
StoreCtlDockInfo(Zone.FChildControl, Zone.FParentZone, Zone.FParentZone,
Zone.FParentZone.FOrientation, not Assigned(Zone.FPrevSibling));
// Otherwise, move sibling's control up into parent zone and dispose of sibling
Zone.FParentZone.FOrientation := doNoOrient;
Zone.FParentZone.FChildControl := Sibling.FChildControl;
Zone.FParentZone.FChildZones := nil;
Sibling.Free;
end;
end
else begin
// Sibling is a zone with child zones, so sibling must be made topmost
// or collapsed into higher zone.
if Zone.FParentZone = FTopZone then
begin
StoreCtlDockInfo(Zone.FChildControl, Sibling, Sibling,
Zone.FParentZone.FOrientation, not Assigned(Zone.FPrevSibling));
// Zone is a child of topmost zone, so sibling becomes topmost
Sibling.FZoneLimit := FTopXYLimit;
FTopXYLimit := FTopZone.FZoneLimit;
FTopZone.Free;
FTopZone := Sibling;
Sibling.FNextSibling := nil;
Sibling.FPrevSibling := nil;
Sibling.FParentZone := nil;
ParentZone := FTopZone;
end
else begin
ParentZone := Zone.FParentZone.FParentZone;
StoreCtlDockInfo(Zone.FChildControl, ParentZone,
ParentZone, Zone.FParentZone.FOrientation,
not Assigned(Zone.FPrevSibling));
// Zone's parent is not the topmost zone, so child zones must be
// collapsed into parent zone
Sibling.FChildZones.FPrevSibling := Zone.FParentZone.FPrevSibling;
if Sibling.FChildZones.FPrevSibling = nil then
Zone.FParentZone.FParentZone.FChildZones := Sibling.FChildZones
else
Sibling.FChildZones.FPrevSibling.FNextSibling := Sibling.FChildZones;
LastChild := Sibling.FChildZones;
LastChild.FParentZone := Zone.FParentZone.FParentZone;
repeat
LastChild := LastChild.FNextSibling;
LastChild.FParentZone := Zone.FParentZone.FParentZone;
until LastChild.FNextSibling = nil;
LastChild.FNextSibling := Zone.FParentZone.FNextSibling;
if LastChild.FNextSibling <> nil then
LastChild.FNextSibling.FPrevSibling := LastChild;
Zone.FParentZone.Free;
Sibling.Free;
end;
end;
end
else begin
ResetOrient := Zone.FParentZone.FOrientation;
ParentZone := Zone.FParentZone;
// This zone has multiple sibling zones
if Zone.FPrevSibling = nil then
begin
ReplaceSiblings(Zone);
StoreCtlDockInfo(Zone.FChildControl, Zone.FParentZone, Zone.FNextSibling,
Zone.FParentZone.FOrientation, True);
// First zone in parent's child list, so make next one first and remove
// from list
Zone.FParentZone.FChildZones := Zone.FNextSibling;
Zone.FNextSibling.FPrevSibling := nil;
Zone.FNextSibling.Update;
end
else begin
ReplaceSiblings(Zone);
StoreCtlDockInfo(Zone.FChildControl, Zone.FParentZone, Zone.FPrevSibling,
Zone.FParentZone.FOrientation, False);
// Not first zone in parent's child list, so remove zone from list and fix
// up adjacent siblings
Zone.FPrevSibling.FNextSibling := Zone.FNextSibling;
if Zone.FNextSibling <> nil then
Zone.FNextSibling.FPrevSibling := Zone.FPrevSibling;
Zone.FPrevSibling.FZoneLimit := Zone.FZoneLimit;
Zone.FPrevSibling.Update;
end;
end;
Zone.Free;
if Assigned(ParentZone) then
ParentZone.ResetChildren(True, ResetOrient);
UpdateConstraints;
UpdateAll;
end;
procedure TMyDockTree.ReplaceParents(Zone, NewParent: TMyDockZone;
ParentBefore: Boolean);
var
I: Integer;
begin
for I := 0 to FCtlDockInfo.Count - 1 do
with TCtlDockInfo(FCtlDockInfo[I]) do
if (FParentZone <> FSiblingZone) and (FSiblingZone = Zone) then begin
FParentZone := NewParent;
FSiblingZone := NewParent;
if (FInsertBefore = ParentBefore) and (FSiblingZone <> NewParent) then
FInsertBefore := not FInsertBefore;
end;
end;
procedure TMyDockTree.ReplaceSiblings(Zone: TMyDockZone);
var
I: Integer;
begin
for I := 0 to FCtlDockInfo.Count - 1 do
with TCtlDockInfo(FCtlDockInfo[I]) do
if (FParentZone <> FSiblingZone) and (FSiblingZone = Zone) then begin
if FInsertBefore and Assigned(Zone.FNextSibling) then
FSiblingZone := Zone.FNextSibling
else if FInsertBefore then begin
FSiblingZone := Zone.FPrevSibling;
FInsertBefore := False
end
// FInsertBefore = False
else if Assigned(Zone.FPrevSibling) then
FSiblingZone := Zone.FPrevSibling
else begin
FSiblingZone := Zone.FNextSibling;
FInsertBefore := True;
end;
end;
end;
procedure TMyDockTree.StoreCtlDockInfo(Control: TControl; ParentZone,
SiblingZone: TMyDockZone; ParentOrient: TDockOrientation;
InsertBefore: Boolean);
var
Info: TCtlDockInfo;
begin
Info := CtlDockInfo[Control];
if not Assigned(Info) then begin
Info := TCtlDockInfo.Create(Self, Control, ParentZone, SiblingZone,
ParentOrient, InsertBefore);
FCtlDockInfo.Add(Info);
end
else begin
Info.FParentZone := ParentZone;
Info.FSiblingZone := SiblingZone;
Info.FParentOrient := ParentOrient;
Info.FInsertBefore := InsertBefore;
end;
end;
function TMyDockTree.GetCtlDockInfo(const Control: TControl): TCtlDockInfo;
var
I: Integer;
begin
for I := 0 to FCtlDockInfo.Count - 1 do
if (FCtlDockInfo[I] as TCtlDockInfo).FControl = Control then begin
Result := TCtlDockInfo(FCtlDockInfo[I]);
Exit;
end;
Result := nil;
end;
procedure TMyDockTree.RemoveCtlDockInfo(Control: TControl);
var
I: Integer;
begin
for I := 0 to FCtlDockInfo.Count - 1 do
if (FCtlDockInfo[I] as TCtlDockInfo).FControl = Control then begin
FCtlDockInfo.Delete(I);
Break;
end;
end;
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde