Solved

Save and Restore position and state of a docked form

Posted on 2000-02-29
27
1,557 Views
Last Modified: 2011-10-03
Hi. I *REALLY* need to solve following problem as soon as possible (that's why there's 600!).

The situation is that I'm developing IDE / Devel. environment (very similar to delphi) and except main and editor forms all other forms are dockable to editor form borders. I want to save complete desktop - save position of forms is zero problem, but how to store the position and the state of docked forms ?

In short - After loading project, the desktop *MUST* be exactly the same, as when user saved project.
0
Comment
Question by:Aleq
  • 9
  • 9
  • 3
  • +6
27 Comments
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
Maybe the following could be a solution. Apply it for each component you want to save. Just ask if you need additional help.


function ComponentToString(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

procedure StringToComponent(Value: string; Comp: TComponent);
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      BinStream.ReadComponent(Comp);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Text := ComponentToString(RichEdit1);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  StringToComponent(Memo1.Lines.Text, RichEdit1);
end;
0
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
Hmmm... I think this won't work for docked forms....
0
 
LVL 17

Expert Comment

by:inthe
Comment Utility
Hi
im not tested this idea but for any properties that are published you could use the functions writecomponentres/readcomponentres to save the forms state/settings in a file:


procedure TForm1.SaveFormClick(Sender: TObject);
 var
  Str1 : TFileStream;
begin
  if SaveDialog1.Execute then
  begin
    Str1 := TFileStream.Create (SaveDialog1.FileName,
      fmOpenWrite or fmCreate);
    try
  Str1.WriteComponentRes (OutputForm.ClassName, OutputForm);     finally
      Str1.Free;
    end;
  end;
end;
 

 
procedure TForm1.LoadFormClick(Sender: TObject);
 var
  Str1: TFileStream;
  TempForm1: TOutputForm;
begin
  if OpenDialog1.Execute then
  begin
    Str1 := TFileStream.Create (OpenDialog1.FileName,
      fmOpenRead);
    try
      TempForm1 := TOutputForm.Create (Application);        Str1.ReadComponentRes (TempForm1);
      OutputForm.Free;
      OutputForm := TempForm1;
       OutputForm.Show;
    finally
      Str1.Free;
    end;
  end;
end;

0
 
LVL 1

Expert Comment

by:yk030299
Comment Utility
listen
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
listening...
0
 

Author Comment

by:Aleq
Comment Utility
**************************************************************************
Unfortunately, neigher EPSYLON's nor INTHE's solutions work... Both solutions makes an exception when there's any component placed on the form and restoring previously docked form makes it without border at (0,0) position. I'm helpless.

I'll raise points if somebody has any working solution ...
0
 

Expert Comment

by:perkley
Comment Utility
Hey, if you could send a sample project that works with Delphi 5, I will definately give a try at solving your problem.  Send to douglas@rexburg.com
0
 
LVL 12

Expert Comment

by:rwilson032697
Comment Utility
Listening
0
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
It's far from perfect but it's a start:


procedure TForm1.WriteForm(theform: TForm);
var ini: TIniFile;
begin
  ini := TIniFile.Create('.\DockedForms.ini');
  with theform do
  begin
    if Floating then
      ini.WriteString(Name, 'Floating', 'true')
    else
      ini.WriteString(Name, 'Floating', 'false');
    ini.WriteString(Name, 'Left', IntToStr(Left));
    ini.WriteString(Name, 'Top', IntToStr(Top));
    ini.WriteString(Name, 'Width', IntToStr(Width));
    ini.WriteString(Name, 'Height', IntToStr(Height));
  end;
  ini.Free;
end;

procedure TForm1.ReadForm(theform: TForm);
var ini: TIniFile;
    lst: TStringList;
begin
  ini := TIniFile.Create('.\DockedForms.ini');
  lst := TStringList.Create;
  with theform do
  begin
    ini.ReadSectionValues(Name, lst);
    if lst.Values['Floating'] = 'false' then
      ManualDock(Panel1);
    Left := StrToInt(lst.Values['Left']);
    Top := StrToInt(lst.Values['Top']);
    Width := StrToInt(lst.Values['Width']);
    Height := StrToInt(lst.Values['Height']);
  end;
  lst.Free;
  ini.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  WriteForm(Form2);
  WriteForm(Form3);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ReadForm(Form2);
  ReadForm(Form3);
end;
0
 

Author Comment

by:Aleq
Comment Utility
I've developped this, but It's far from perfect too.
I've realized, that forms should be docked manually in the same order as they were docked by mouse.
This solution works fine for max. 2 docked forms to one panel. 3 forms makes problems sometimes.

---------------------
type
     TDockPlace = (dpLeft, dpTop, dpRight, dpBottom);
     TDockState = record
                    Docked                               : Boolean;
                    Left, Top, ClientWidth, ClientHeight : Integer;
                    Width,Height                         : Integer;
                    DockPlace                            : TDockPlace;
                    DockOrientation                      : TDockOrientation;
                    Align                                : TAlign;
                  end;
---------------------

Panel.OnDockDrop event

  if (Source.Control is TForm) and (Sender is TPanel) then
    with (Source.Control as TForm) do
      Tag:=Integer(Source.DropAlign);

------------------------

procedure TMainF.SaveFormDockInfo(Form : TForm; var Info : TDockState);
begin
  Info.Docked:=not Form.Floating;
  Info.Left:=Form.Left;
  Info.Top:=Form.Top;
  Info.ClientWidth:=Form.ClientWidth;
  Info.ClientHeight:=Form.ClientHeight;
  Info.Width:=Form.Width;
  Info.Height:=Form.Height;
  if (Form.HostDockSite<>nil) and (Form.HostDockSite is TPanel) then
  begin
    if (Form.HostDockSite as TPanel).Name='LeftPanel' then Info.DockPlace:=dpLeft else
    if (Form.HostDockSite as TPanel).Name='TopPanel' then Info.DockPlace:=dpTop else
    if (Form.HostDockSite as TPanel).Name='RightPanel' then Info.DockPlace:=dpRight else
    if (Form.HostDockSite as TPanel).Name='BottomPanel' then Info.DockPlace:=dpBottom;
  end;
  Info.DockOrientation:=Form.DockOrientation;
end;

procedure TMainF.LoadFormDockInfo(Form : TForm; var Info : TDockState);
begin
  if Form.HostDockSite<>nil then
  begin // Undock before restoring previous state
    with Info do
      Form.ManualFloat(Rect(Left, Top, Left+Width, Top+Height));
  end;

  if Info.Docked then
  begin
    Case Info.DockPlace of
      dpLeft   : Form.ManualDock(LeftPanel,nil,TAlign(Form.Tag));
      dpTop    : Form.ManualDock(TopPanel,nil,TAlign(Form.Tag));
      dpRight  : Form.ManualDock(RightPanel,nil,TAlign(Form.Tag));
      dpBottom : Form.ManualDock(BottomPanel,nil,TAlign(Form.Tag));
    end;
    Form.Left:=Info.Left;
    Form.Top:=Info.Top;
    Form.ClientWidth:=Info.ClientWidth;
    Form.ClientHeight:=Info.ClientHeight;
  end else
  begin
    Form.Left:=Info.Left;
    Form.Top:=Info.Top;
    Form.ClientWidth:=Info.ClientWidth;
    Form.ClientHeight:=Info.ClientHeight;
  end;
  Form.DockOrientation:=Info.DockOrientation;
end;
0
 

Expert Comment

by:pcisar
Comment Utility
Take a look at Allen Bauer's article at http://community.borland.com/article/0,1410,21114,00.html and download reffered code from CodeCentral. It contains portions of Delphi IDE source code (yes, it is !) including some support for desktop save/restore.

Best regards
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Aleq, you never said whether the stuff provided until now works for you.

If it doesn't then say that here as soon as possible (before the given answer is automatically accepted!) because I have meanwhile managed ALL the stuff with docking (including a bug fix for the VCL). My project saves and restores now fine. Does yours?

Ciao, Mike
0
 

Author Comment

by:Aleq
Comment Utility
I'm very sory.  I was too busy finishing that IDE project so I completely forget about this thread.

Pcisar's question is unfortunately not acceptable, since the borland's article shows only idea, I miss there important  parts of the docking code.

Lischke: My project has now limited save/load desktop feature (based on the code presented by me (above)). It's far the perfect, but it works (at least partially). If you have complete solution, I would like to see it very much.
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Hi Aleq, here we go:

Actually, you have not specified if you want to store your data in registry or ini file. I personally prefer the registry and so my save and restore routines are designed. At first here is the code. Discussion follows it:

type // structure of a block of registry data for a viewer form
  TViewerData = record
    Placement: TWindowPlacement;
    UndockHeight,
    UndockWidth,
    TBHeight,
    LRWidth: Integer;
    Identifier: Cardinal;
    Kind: TViewerKind;
    Scale: Integer;
  end;

  TViewerEntry = record
    Viewer: TBaseForm;
    Data: TViewerData;
  end;

procedure TMainForm.ReadPreferences;

// read window size, position etc. as well as open "sub" windows and their docking state

var
  I: Integer;
  Key,
  SubKey: HKEY;
  Size,
  ValueType: DWORD;
  Placement: TWindowPlacement;
  ViewerData: TViewerData;

  //--------------- local functions -------------------------------------------

  procedure InitViewer(const Entry: TViewerEntry);

  // loads a single viewer form to the viewer sub key

  begin
    with Entry do
    begin
      SetWindowPlacement(Viewer.Handle, @Data.Placement);
      Viewer.UndockHeight := Data.UndockHeight;
      Viewer.UndockWidth := Data.UndockWidth;
      Viewer.TBDockHeight := Data.TBHeight;
      Viewer.LRDockWidth := Data.LRWidth;
      if Data.Kind = vkImage then TViewerForm(Viewer).Scale := Data.Scale;
      Viewer.Show(Data.Identifier, False);
    end;
  end;

  //---------------------------------------------------------------------------

  procedure LoadDockHost(Host: TForm);

  begin

  end;

  //--------------- end local functions ---------------------------------------

var
  Buffer: array[0..MAX_PATH] of Char;
  BufferSize: Cardinal;
  Result: Integer;
  Stream: TMemoryStream;
  Viewers: array of TViewerEntry;

begin
  if RegOpenKey(HKEY_CURRENT_USER, 'Software\Digital Publishing\DIB', Key) = ERROR_SUCCESS then
  try
    Size := SizeOf(MainSettings);
    RegQueryValueEx(Key, 'Settings', nil, @ValueType, @MainSettings, @Size);

    // load window settings
    Placement.Length := SizeOf(Placement);
    Size := Placement.Length;
    if RegQueryValueEx(Key, 'MainWindow', nil, @ValueType, @Placement, @Size) = ERROR_SUCCESS then
      SetWindowPlacement(Handle, @Placement);

    // read viewer subkey
    if RegOpenKey(Key, 'Viewers', SubKey) = ERROR_SUCCESS then
    begin
      // enumerate through all available values
      I := 0;
      repeat
        Size := SizeOf(ViewerData);
        BufferSize := MAX_PATH;
        Result := RegEnumValue(SubKey, I, Buffer, BufferSize, nil, nil, @ViewerData, @Size);
        if Result = ERROR_SUCCESS then
        begin
          // let the viewer forms dock first before loading their data, loading is done below as last step
          SetLength(Viewers, Length(Viewers) + 1);
          case ViewerData.Kind of
            vkCategory,
            vkImage:
              Viewers[High(Viewers)].Viewer := TViewerForm.Create(Self);
            vkCategoryTree:
              Viewers[High(Viewers)].Viewer := TCategoryForm.Create(Self);
          end;
          Viewers[High(Viewers)].Viewer.Name := Buffer;
          Viewers[High(Viewers)].Data := ViewerData;
        end;
        Inc(I);
      until Result <> ERROR_SUCCESS;
      RegCloseKey(SubKey);
    end;

    // enumerate dock host subkey, must be done after the forms have been created as
    // they must already exist before they can automatically be docked
    if RegOpenKey(Key, 'DockHosts', SubKey) = ERROR_SUCCESS then
    begin
      Stream := TMemoryStream.Create;
      I := 0;
      repeat
        Size := 0;
        BufferSize := MAX_PATH;
        Result := RegEnumValue(SubKey, I, Buffer, BufferSize, nil, nil, nil, @Size);
        if Result = ERROR_SUCCESS then
        begin
          // load stream from registry
          Stream.Size := Size;
          Stream.Position := 0;
          BufferSize := MAX_PATH;
          Result := RegEnumValue(SubKey, I, Buffer, BufferSize, nil, nil, Stream.Memory, @Size);
          case I of
            0: // the bottom dock panel
              begin
                BottomDockPanel.DockManager.LoadFromStream(Stream);
                Stream.Read(Size, 4);
                BottomDockPanel.Height := Size;
              end;
            1: // the right dock panel
              begin
                RightDockPanel.DockManager.LoadFromStream(Stream);
                Stream.Read(Size, 4);
                RightDockPanel.Width := Size;
              end;
          else
            // every other entry corresponds to a conjoin or tab host
          end;
        end;
        Inc(I);
      until Result <> ERROR_SUCCESS;

      Stream.Free;
      RegCloseKey(SubKey);
    end;

    // finally initialize viewer position and sizes
    for I := 0 to High(Viewers) do
      InitViewer(Viewers[I]);

  finally
    RegCloseKey(Key);
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TMainForm.WritePreferences;

var
  I: Integer;
  Key,
  SubKey: HKEY;
  Disposition: Integer;
  Placement: TWindowPlacement;

  //--------------- local functions -------------------------------------------

  procedure SaveViewer(Viewer: TBaseForm);

  // saves a single viewer form to the viewer sub key

  var
    ViewerData: TViewerData;

  begin
    with ViewerData do
    begin
      Placement.Length := SizeOf(Placement);
      GetWindowPlacement(Viewer.Handle, @Placement);
      UndockHeight := Viewer.UndockHeight;
      UndockWidth := Viewer.UndockWidth;
      TBHeight := Viewer.TBDockHeight;
      LRWidth := Viewer.LRDockWidth;
      Identifier := Viewer.Identifier;
      Kind := Viewer.ViewerKind;
      if Kind = vkImage then Scale := TViewerForm(Viewer).Scale;
    end;
    RegSetValueEx(SubKey, PChar(Viewer.Name), 0, REG_BINARY, @ViewerData, SizeOf(ViewerData));
  end;

  //---------------------------------------------------------------------------

  procedure SaveDockHost(Host: TForm);

  begin

  end;

  //--------------- end local functions ---------------------------------------

var
  Buffer: array[0..MAX_PATH] of Char;
  BufferSize: Cardinal;
  Result: Integer;

  Stream: TMemoryStream;

begin
  if RegCreateKeyEx(HKEY_CURRENT_USER, 'Software\Digital Publishing\DIB', 0, nil,
                    REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, Key, @Disposition) = ERROR_SUCCESS then
  try
    // write main application settings
    RegSetValueEx(Key, 'Settings', 0, REG_BINARY, @MainSettings, SizeOf(MainSettings));

    // write main window settings
    Placement.Length := SizeOf(Placement);
    GetWindowPlacement(Handle, @Placement);
    RegSetValueEx(Key, 'MainWindow', 0, REG_BINARY, @Placement, SizeOf(Placement));

    // write viewer subkey but clear it before storing new values
    if RegCreateKeyEx(Key, 'Viewers', 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, SubKey,
                      @Disposition) = ERROR_SUCCESS then
    begin
      if RegQueryInfoKey(SubKey, nil, nil, nil, nil, nil, nil, @I, nil, nil, nil, nil) = ERROR_SUCCESS then
      begin
        repeat
          Dec(I);
          if I > -1 then
          begin
            BufferSize := MAX_PATH;
            Result := RegEnumValue(SubKey, I, Buffer, BufferSize, nil, nil, nil, nil);
            if Result = ERROR_SUCCESS then RegDeleteValue(SubKey, Buffer);
          end
          else Result := 1;
        until (Result <> ERROR_SUCCESS) or (I = 0);
      end;
      for I := 0 to FViewers.Count - 1 do SaveViewer(FViewers[I]);
      RegCloseKey(SubKey);
    end;

    // write dock hosts
    if RegCreateKeyEx(Key, 'DockHosts', 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, SubKey,
                      @Disposition) = ERROR_SUCCESS then
    begin
      // delete old entries first
      if RegQueryInfoKey(SubKey, nil, nil, nil, nil, nil, nil, @I, nil, nil, nil, nil) = ERROR_SUCCESS then
      begin
        repeat
          Dec(I);
          if I > -1 then
          begin
            BufferSize := MAX_PATH;
            Result := RegEnumValue(SubKey, I, Buffer, BufferSize, nil, nil, nil, nil);
            if Result = ERROR_SUCCESS then RegDeleteValue(SubKey, Buffer);
          end
          else Result := 1;
        until (Result <> ERROR_SUCCESS) or (I = 0);
      end;

      Stream := TMemoryStream.Create;
      BottomDockPanel.DockManager.SaveToStream(Stream);
      Stream.Write(BottomDockPanel.Height, 4);
      RegSetValueEx(SubKey, 'BottomDock', 0, REG_BINARY, Stream.Memory, Stream.Size);
      Stream.Size := 0;
      RightDockPanel.DockManager.SaveToStream(Stream);
      Stream.Write(RightDockPanel.Width, 4);
      RegSetValueEx(SubKey, 'RightDock', 0, REG_BINARY, Stream.Memory, Stream.Size);
      Stream.Free;
      RegCloseKey(SubKey);
    end;
  finally
    RegCloseKey(Key);
  end;
end;


This code is the core I use. As in the Delphi sample for docking there's a right and a bottom dock panel. For these I have shown here the save and restore code. Additionally, there are floating tool windows (here TViewer) and floating dock hosts. The state and position of the first one is  also saved (because I wanted them to restore too even if they are not docked), while the dock host aren't shown here, because the code is very much the same as for the dock panels but must be invoked iteratively for each dock host.

The key point for restoring the docking state correctly is to create the forms which are to be docked BEFORE the dockmanager of the dock panels/hosts load their state. This is because while loading the dock tree the particular controls are searched and if they exist, they are docked. Otherwise the particular dock zone will be deleted.

A second point to be considered (and that is in my opinion also a bug) is that only the child controls of the parent form of the dock manager are searched for the controls to be docked. This means if you have floating forms then you need to created them manually and set their owner to the form where the dock panles sit on. Otherwise they are not found be the loader mechanism of the dock tree. An example:

TViewer is just a TForm. I create several instances all having as owner the main form. This way the dock panels will find them while loading the dock tree. More care must be taken when loading the dock tree for the dock hosts (conjoin and tab dock host). In this case the forms must be created as being owned by them if they are also docked to them. This does in no way harm or change the handling of the forms and is only necessary for loading.

Well, I hope I have explained it enough, so you understood what I did.

Ciao, Mike
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Ah, yes, I should mention that your event handler for docking (and CM_DOCKCLIENT etc.) must already be working when loading the dock state because the controls are docked via ManualDock which uses the same mechanism as the drag'n dock operation.

Ciao, Mike
0
 

Author Comment

by:Aleq
Comment Utility
I'm going to check this - it looks good...
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
.. and it works pretty well. I hope your next comment will not take as long as your last comment ;-))

Note: With the code above you don't need to store left, top etc. for a form separately. This is all handled by the window placement stuff. Only the separately held values for undock height and width as well as TBHeight and LRWidth need to be considered separately.

Btw: Here's the bug I had to fix in Controls.pas regarding docking:

procedure TControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
begin
  if (csDesigning in ComponentState) then
  begin
    Form := GetParentForm(Self);
    if (Form <> nil) and (Form.Designer <> nil) and
      Form.Designer.IsDesignMsg(Self, Message) then Exit;
  end
  else if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
  begin
    Form := GetParentForm(Self);
    if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
  end
  else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
  begin
    if not (csDoubleClicks in ControlStyle) then
      case Message.Msg of
        WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
          Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
      end;
    case Message.Msg of
      WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
      WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
        begin
          // 10-MAR-2000 ml:
          //   automatic docking prevents a left mouse button down to reach the app.
          //   therefore I replaced
          //   (if FDragMode = dmAutomatic then) by:
          if (FDragMode = dmAutomatic) and
             (FDragKind <> dkDock) then
          begin
            BeginAutoDrag;
            Exit;
          end;
          Include(FControlState, csLButtonDown);
        end;
      WM_LBUTTONUP:
        Exclude(FControlState, csLButtonDown);
    end;
  end
  else if Message.Msg = CM_VISIBLECHANGED then
    with Message do
      SendDockNotification(Msg, WParam, LParam);
  Dispatch(Message);
end;

This and a few other bugs have been sent to the Delphi bug list already.

Ciao, Mike
0
 

Author Comment

by:Aleq
Comment Utility
Hi. I've checked that code but I couldn't have managed to use your technique. Please look at http://www.volny.cz/aberka/temp/docktest.exe (<A HREF="http://www.volny.cz/aberka/temp/docktest.exe">http://www.volny.cz/aberka/temp/docktest.exe</A>)- it's archive with test project that should work, but does not. I would be very glad if you could look at it. Thanx
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Hi Aleq,

would you please send me the source for this test project (public@lischke-online.de). Aside that I don't like to open an executable from an unknown source I don't know how I can help you just from an application. Can you be more specific about what does not work?

Ciao, Mike
0
 

Author Comment

by:Aleq
Comment Utility
Sending to you. BTW that executable is self-extracting archive with the project. :-) The problem is, that when the project restores the previous state, some docked forms are not correctly docked (they don't change the size after panel resize, etc)).
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Hi Aleq,

sorry for being so late. I'm still over your docking example. Have you noticed that Form4 (the yellow one) behaves correctly while the other forms do not? This is very strange as all three forms are very similar and do not have any code in it.

Just to let you know I'm still investigating...

Ciao, Mike
0
 
LVL 10

Accepted Solution

by:
Lischke earned 800 total points
Comment Utility
Finally I found the problem. It has nothing to do with my code but there's again something in the VCL I would consider being a bug!

The LoadFromStream method of a dock manager recreates the dock tree's former structure and tries to place the former controls at their places as they were before. In this process the controls are (obviously) docked. But this means to set their parents and bounds correctly and also to include the controls into the dock managers internal list. The former works correctly but the latter fails if the control being docked is still invisible (and Form4 was created as being visible, the other forms not).

This rejection (which makes no sense since the dock manager must be able to handle invisible controls too because one can make a docked control invisble at any time) causes a strange inconsistency. The docked control is placed correctly (parent child relation and position) but no dock zone is created for the control and so the control is not handled as being docked but as would it have been normally placed onto a parent control.

To make a long story short: You must set the Visible property of your forms to true before you can dock them (in particular when loading a dock tree from stream).

Ciao, Mike
0
 
LVL 4

Expert Comment

by:Radler
Comment Utility
Listening...
0
 

Author Comment

by:Aleq
Comment Utility
It works great.
I think this solution should be awarded by +200 points (so, I'm increasing to 800). :-)
0
 

Author Comment

by:Aleq
Comment Utility
Adjusted points from 600 to 800
0
 

Author Comment

by:Aleq
Comment Utility
NO COMMENT :-)
0
 
LVL 10

Expert Comment

by:Lischke
Comment Utility
Hey, Aleq, thank you for the points (and the 'A grading too) :-)) When will you have the next tough question 8-* ?

Ciao, Mike
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now