Восстановление размеров плавающих окон
От: __Dimentiy Россия  
Дата: 10.02.05 11:33
Оценка:
Всем привет!

Вопрос такой:

Есть TPanel *ppanel и класс окна TMyForm *pform, экземпляры которого будут к этой панели пристыковываться.
Для автоматической стыковки сделано следующее:
panel->DockSite = true;
panel->UseDockManager = true;

form->DockKind = dkDock;

form->DragMode = dmAutomatic;
form->UseDockManager = true;

Всё остальное оставлено по умолчанию.

Суть вопроса заключается в следующем (вообщем объясните ламеру) как к примеру при загрузке приложения (создании новых окон)
втыкать эти окна в панель с нужным размером. Сейчас при стыковке область панели делиться ровно на половину и размер стыкуемых окон изменяется и становиться равным, можно изменить её после стыковки ручками, но при отстыковки и стыковки заново старое положение не запоминается.

Может надо всю эту операцию писать ручками? Если да, то натолкните на мысль в какую сторону копать, пожалуйста.

Спасибо! Удачи!
Re: Восстановление размеров плавающих окон
От: Аноним  
Дата: 10.02.05 13:45
Оценка:
Здравствуйте, __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

и посмотри как там сделано
Re: Восстановление размеров плавающих окон
От: ComposteR Россия  
Дата: 10.02.05 15:26
Оценка: 3 (1) +1
Здравствуйте, __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>>
-=[ComposteR]=-
Re[2]: Восстановление размеров плавающих окон
От: andropov  
Дата: 29.04.06 21:57
Оценка:
Здравствуйте, ComposteR, Вы писали:

CR>В свете выше перечисленных проблем, я использовал не очень красивый, но работающий обходной путь. Перед сохранением

CR>я замещаю все придоченные окна специально созданными временными панелями, сохраняю их позицию и дополнительно информацию
CR>о том, какая панель замещала какое окно. Затем при восстановлении я восстанавливаю сначала layout панелей, а потом
CR>возвращаю на места окна. Такой способ работает.

Подскажите, как правильно заменить придоченное окно панелью и наоборот, так, чтобы при этом не изменился layout?
Re: Восстановление размеров плавающих окон
От: ekamaloff Великобритания  
Дата: 01.05.06 08:16
Оценка:
Здравствуйте, __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
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.