Please see attached a simple framework for an application that can have multiple forms docked into a single PageControl.
Well, this was intended to be simple, but I got carried away. It's pretty much a fully fledged implementation, which possibly overcomplicates the main features...
If you don't want to use the provided framework (don't blame you), then any form you want can be docked into a PageControl simply using MyForm.ManualDock(MyPageControl);
To remove your form from a PageControl you do:
MyForm.Free;
MyFormsTabsheet.Free;
That's essentially it. You don't have to read the rest of this post :-)
For those who got this far the attached project is a simple Notepad++ style implementation. There's one Form only with the notepad functionality, the rest is used to manage the numerous instances of each notepad.
In my opinion, this isn't an MDI implementation. Only one docked form is able to viewed at a time - I have no provision for side by side views or arrangement of windows etc. If you need this, then use @Taazz's suggestion above, or one of the docking manager implementations I've listed in my first post...
So, what the example project provides is a mechanism for allowing multiple different forms to be docked into a PageControl. The only Notepad functionality itself is in the form to be docked. The rest of the app doesn't have a clue what each docked form actually does. The example project takes care of reading these docks to and from an inifile (inifile for simplicity). To change the preferred save mechanism, just change LoadSettings & SaveSettings.
Different Docks can be created using Lazarus Form Inheritance, and creating new docks, each descended from the provided dock base.
There's comments scattered throughout the code, most notably at the top of FormDockBase and DockNotepad.
Overview of attached example:
There are three units.
* FormMain contains the PageControl and the framework for handling Docked forms.
- FDocks: TList; // This is the array that stores each of the created Docks
- All the dock handling code is hidden away in a series of routines. Rather than call FDocks directly, code elsewhere in FormMain should call any one of these handling routines:
Procedure AddDock(oDock: TdckBase);
Procedure DeleteDock(oDock: TdckBase);
Function DockIndex(oDock: TdckBase): Integer;
Property Dock[iIndex: Integer]: TdckBase;
Property ActiveDock: TdckBase;
* FormDockBase contains the base template of each form that will be docked.
- TdckBase should not have any UI elements on it. Think of it as an abstract base class (though it isn't, it's just a base class)
- TdckBase knows it's going to be docked into a PageControl, and has assorted routines to help deal with this
- Any docked forms in use should be descended from TdckBase
* DockNotepad contains a simple example of a Form to be docked. This descends from TdckBase. All the UI controls and user functionality are embedded at this level
With all that in mind - here is the key code:
To create and dock a form:
oDock := TdckNotepad.Create(nil); // Nil as owner as we'll handle freeing this ourself
AddDock(oDock);
The important bits of AddDock() are:
oDock.ManualDock(pcMain);
oDock.TabSheet := TTabSheet(oDock.Parent);
oDock.TabSheet.Caption := oDock.Caption;
oDock.TabSheet.TabVisible := True;
oDock.TabSheet.PageIndex := pcMain.PageCount - 1;
pcMain.ActivePage := oDock.TabSheet;
oDock.Show;
FDocks.Add(oDock);
Here you see our array FDocks is being populated. pcMain is the name of the PageControl, and adding the form to the pagecontrol is actually quite simply using a TForm routine (not mine) called ManualDock();
There's issues with the attached project due to the attempted simplicity of the design:
* I don't like using inifiles. Used them for simplicity
* Watch the GDI Object count in Task Explorer as you add multiple forms :-) The TImageList shouldn't be stored in TdckNotepad, that was me being lazy here. Really I should have a single TImageList in say a Data Module somewhere, and dynamically link my Notepad toolbar to this TImageList. As you create more complex docks, I would recommend going down this path as Windows can't have a GDI Object count of >10000 per app (and Windows crashes spectacularly when an app hits this limit). (my own app is reasonably complex and gets to >3000 easily. I *really* must move my TImageLists away from the docks...)
* A Factory is really required if you have multiple types of docked forms. I've implemented a decent factory in my own app, but decided it added an unecassry level of complication to the attached example. Look in TfrmMain.LoadSettings & TfrmMain.SaveSettings for how I've implemented a simple version using TObject.ClassName...
* I dislike the fact that MainForm contains both the Docking Management code and general MainForm stuff. In my own app TfrmMain descends from TfrmDockingManager, and all the Docking management is positioned in this ancestor form. I could also have implemented a separate TFrame for all the Docking Management and used that. Again, the MainForm here was created with an attempt at simplicity, though as I said - it got complicated...
* A wierd issue is I'm getting is a GDB debugger STOP issue in the IDE after application close if I use the TOpenDialog. I've been seeing this issue intermittently in other apps, this is the first time I've been able to isolate this to the TOpenDialog. I'll chase that down independently now I know where it is. I don't think there is anything wrong with the code
procedure TdckNotepad.btnLoadClick(Sender: TObject);
Var
dlgOpen: TOpenDialog;
Begin
dlgOpen := TOpenDialog.Create(Self);
Try
dlgOpen.DefaultExt := '.txt';
dlgOpen.Filter := 'Text Files|*.txt';
dlgOpen.FileName := FFilename;
If dlgOpen.Execute Then
Begin
FFilename := dlgOpen.FileName;
memNotepad.Lines.LoadFromFile(FFilename);
FDirty := False;
RefreshUI;
End;
Finally
dlgOpen.Free;
End;
End;