Some changes are needed to get the order of commands changed. In the Close() method of your DockablePanel, therefore, you add a few lines. You are not sure whether the user will realy close the DockableForm; this will depend on if he answers the messagebox question with yes or no. If he really closes the Form, it needs to be removed in the DockingManagers AllCarriers collection. So, you remove it just before you cause the _carrier Form to close. If the action is done, IsDisposed of the Form will be true, so you no longer have a carrier attached and you are going to fire ClosingPanel by using the OnClosingPanel() method just as you did before and you dispose your DockablePanel. If the user cancels the close command, the carrier again is attached to the AllCarriers collection in the DockingManager. Here is the code:
#region internal Methods (DockablePanel)
internal void Close() {
_dockingManager.RemoveCarrier(_carrierForm.Key );
_carrierForm.Close();
if (_carrierForm != null && !_carrierForm.IsDisposed) {
_dockingManager.AddCarrier(_carrierForm);
}else{
_carrierAttached = false;
OnClosingPanel();
if (!this.IsDisposed) {
this.Dispose();
}
}
}
#endregion
To be able to add or remove the carrier, you need to add two simple methods to the DockingManager.
#region internal methods (DockingManager)
internal void RemoveCarrier(string key) {
if( AllCarriers.ContainsKey(key)){
AllCarriers.Remove(key);
}
}
internal void AddCarrier(DockableForm _carrierForm) {
if (!AllCarriers.ContainsValue( _carrierForm)){
AllCarriers.Add(_carrierForm.Key, _carrierForm);
}
}
#endregion
The dockPanel _PanelClosing delegate needs to be reduced now, because the former lines where you removed the carrier in the AllCarriers collection are no longer needed here, because this is done just before in the Close() method of the DockablePanel. And the carrier was already closed there.
#region delegates (DockingManager)
private void dockPanel_PanelClosing(string key) {
DockablePanel dockPanel = GetDockingPanel(key);
if (dockPanel.DockingType != DockType.None) {
// when the control was docked and is closed now,
// we need to delete it in the DockedPanels List
this.Admin.RemoveDockedPanel(dockPanel.Key);
}
// Its closed so we remove it from AllDockPanels too
AllDockPanels.Remove(key);
// the carrier is already removed, we dont need the
// former code here anymore
}
#endregion
In the resize method of the Dockablepanel, you make sure that the carrier isn't already disposed when you want to resize it.
private void DockablePanel_Resize(object sender, EventArgs e) {
//. . .
if (_dockingType != DockType.None && _carrierForm != null &&
!_carrierForm.Disposing ){
// if we are docked we are not attached to the carrier so
// we need to size the carrierForm if we have closed the
// DockableForm we dont resize it even when before also
// closing the DockablePanel a Sizing action may occur
_carrierForm.Size = new Size(this.Width, this.Height);
}
}
Now, you have corrected the unwanted side effects the design has shown. You will need to do this from time to time during the design increases.
Creating Advanced Docking DockType.Center
You have really done a lot of work throughout this series, so now it's time to take the bull by the horns!
As you know by now, the first DockablePanel is always docked, by adding it to the SplitContainer. Therefore, docking to a DockingControler using DockType.Center needs to change the control you are using to add your DockablePanels. This may happen when adding the second DockablePanel, but this also may occur when you try to add the third DockablePanel. Additionally, you need to have an eye on what happens when removing a panel from the TabControl. As long as there are more than two DockablePanels added to the TabControl, you are simply removing the panels. When you have reached the point where only two DockablePanels are left, this changes.
Removing one DockablePanel then also removes the TabControl and adds the remaining panel to the SplitContainer again where the second slot is collapsed. This is necessary because a DockingControler with only one DockablePanel always needs to be the same, independent if you have just added your first panel to the DockingControler or if you had a DockingControler that contained a lot of panels in a TabControl, and you have removed all of them, so that there is only one panel left. The result always needs to be the same. If you have only one DockablePanel added to a DockingControler, it needs to be added into the first slot of a SplitContainer; slot 2 is collapsed. That's all. Now, go on to code this.
First, you have to add the TabControler field to your private fields so you can access it from everywhere in the DockingControler.
#region Fields (DockingControler)
private TabControl _tabControler;
private const int REG_HEIGHT = 22; // Height of a register card
The first time you add a DockablePanel to a DockingControler using DockType.Center, the ControlerType of the DockingControler is ControlerType.Vertical or ControlerType.Horizontal. When you already have a panel docked using Docktype.Center, the ControlerType is changed to ControlerType.Tabbed. You use this method for identification when you have to create the _tabControler and when you have only to create a new page, to add the DockablePanel to it and to add the TabPage itself then to the _tabControler. Therefore, in the switch statement of the AddPanel method where you already have prepared the space for your code, you add the following code.
#region internal Methods(DockingControler)
internal void AddPanel(DockablePanel dockPanel) {
//. . .
case DockType.Center:
if (_controlsType != ControlerType.Tabbed) {
CreateTabbedDockControler(dockPanel);
_controlsType = ControlerType.Tabbed;
} else {
AddNewTabPage(dockPanel);
_controlsType = ControlerType.Tabbed;
}
_dockedPanelsCount = _tabControler.TabCount;
break;
#endregion
When creating the _tabControler, at first you are suspending the layout for the whole action. The Tab pages are to be aligned to the bottom and the Controler itself is docked to the DockingControler using DockStyle.Fill. Then, you need to find out where the tabControler's parent, the DockingControler, is docked. Depending on that, the Padding of the DockingControler is adjusted and the Size of the tabControler too. Then, you check whether you already have one or two DockablePanels docked to the DockSplitContainer; removd them there and add them to a TabPage and to the _tabControler using the AddNewTabPage method. After you have added the already existing DockablePanels, you only need to add just that panel that you are just trying to dock. The empty DockSplitContainer is disposed, the _tbControler is added to the Controls collection of our DockingControler, and you are finished. That's what you will find line by line in the following code.
#region private Methods (DockingControler)
private void CreateTabbedDockControler(DockablePanel dockPanel) {
_tabControler = new TabControl();
_tabControler.SuspendLayout();
this.SuspendLayout();
_tabControler.Alignment = TabAlignment.Bottom;
_tabControler.Dock = DockStyle.Fill;
_tabControler.Name = "TabControler";
_tabControler.SelectedIndex = 0;
switch (this.Dock) {
case DockStyle.Top:
_tabControler.Location = new Point(0, 0);
this.Padding = new Padding(0, 0, 0, PAD_VER);
_tabControler.Size = new Size(this.Width,
this.Height - PAD_VER);
break;
case DockStyle.Bottom:
_tabControler.Location = new Point(0, PAD_VER);
this.Padding = new Padding(0, PAD_VER, 0, 0);
_tabControler.Size = new Size(this.Width,
this.Height - PAD_VER);
break;
case DockStyle.Left:
_tabControler.Location = new Point(0, 0);
this.Padding = new Padding(0, 0, PAD_HOR, 0);
_tabControler.Size = new Size(this.Width - PAD_HOR,
this.Height);
break;
case DockStyle.Right:
_tabControler.Location = new Point(PAD_HOR, 0);
this.Padding = new Padding(PAD_HOR, 0, 0, 0);
_tabControler.Size = new Size(this.Width - PAD_HOR,
this.Height);
break;
}
DockablePanel panel1 =
(DockablePanel)DockSplitContainer.Panel1.Controls[0];
AddNewTabPage(panel1);
DockSplitContainer.Panel1.Controls.Clear();
if (_dockedPanelsCount == 2) {
DockablePanel panel2 =
(DockablePanel)DockSplitContainer.Panel2.Controls[0];
AddNewTabPage(panel2);
DockSplitContainer.Panel2.Controls.Clear();
}
AddNewTabPage(dockPanel);
DockSplitContainer.Visible = false;
DockSplitContainer.Dispose();
this.Controls.Add(this._tabControler);
_tabControler.ResumeLayout(false);
this.ResumeLayout(false);
}
The AddNewTabPage() method creates a new TabPage object and add the Dockablepnel to its Controls collection. You store the DockType in the DockablePanels' DockingType property for later use. Also, the Name of theTabPage is set to the DockablePanel key. This is done for being able to have quicker access to a specific DockableControl that is added to one of the TabControlers Pages by simply checking the TabPages' names instead of running trough all Controls collections of every TabPage when searching for a specific DockablePanel. The TabPages Text is set to the caption of the DockablePanel so you can see in the Tab control which panel is docked.
#region private Methods (DockingControler)
private void AddNewTabPage(DockablePanel dockPanel) {
TabPage newPage = new TabPage();
newPage.Controls.Add(dockPanel);
dockPanel.DockingType = DockType.Center;
newPage.Location = new Point(0, 0);
newPage.Name = dockPanel.Key;
newPage.Padding = new Padding(0);
newPage.Size = new Size(_tabControler.Width,
_tabControler.Height - REG_HEIGHT);
newPage.Text = dockPanel.Caption;
newPage.UseVisualStyleBackColor = true;
_tabControler.Controls.Add(newPage);
}
Closing a DockablePanel docked using DockType.Center
Now, you are able to dock your DockablePanel to the DockingControler by adding it to a TabControl. There are two actions missing in these mechanics. One is a method to close the DockablePanel when it is docked this way; the other is to add code for removing the DockablePanel from its TabControl again when undocking a Panel from its DockingControler. You already have the code for all DockTyes; only DockType Center is missing. Both demands need to remove the DockablePanel from the TabControl. Therefore, you are able to use the same code in this segment of code. So, you use the RemovePanelFromTabControler() method to get this done and into the dockPanel_PrepareClosing as well as into the dockPanel_Undock delegate you insert this method in the relevant codepart.
#region delegates (DockingControler)
private void dockPanel_PrepareClosing(DockablePanel dockPanel){
//. . .
case DockType.Center:
RemovePanelFromTabControler(dockPanel);
break;
//. . .
}
private void dockPanel_Undock(DockablePanel dockPanel) {
//. . .
case DockType.Center:
RemovePanelFromTabControler(dockPanel);
break;
//. . .
#endregion
Let me discuss the code for preparing to close or remove a DockablePanel. You are looping through the TabControl, looking for the page that is to be closed and, when found, you remove the TabPage, so it is no longer part of the TabControl. Then, you remove the DockablePanel from the TabPage. This way, the TabPage is without any connection so it will be disposed automatically. If there is only one TabPage remaining in the TabControl, you need to change back from a TabControl to a SplitContainer to contain the DockablePanel. (I have described these mechanics already).
#region private Methods (DockingControler)
private void RemovePanelFromTabControler(DockablePanel dockPanel) {
for (int i = 0; i < _tabControler.TabPages.Count; i++) {
if (_tabControler.TabPages[i].Controls.Contains(dockPanel)) {
TabPage tabToRemove = _tabControler.TabPages[i];
_tabControler.TabPages.Remove(tabToRemove);
// remove from TabPage
if (tabToRemove.Controls.Contains(dockPanel)) {
tabToRemove.Controls.Remove(dockPanel);
}
break;
}
}
_dockedPanelsCount = _tabControler.TabPages.Count;
// if we have only one we untab the control so we need
// the panel itself
if (_dockedPanelsCount == 1) {
ChangeTabControlerToMultiDockControler();
}
}
#endregion
In the following method, you simply remove the last remaining DockablePanel from the TabPage, correct the Docktype of the remaining DockablePanel, getting the correct new ControlerType of your DockingControler, and then dispose the TabControl and create the DocksplitContainer by usage of the already existing CreateMultiDockControler() method.
#region private methods (DockingControler)
private void ChangeTabControlerToMultiDockControler() {
DockablePanel remaining;
this.Visible = false;
// we get the remaining panel
if (_tabControler.TabPages.Count > 0) {
TabPage tabPage = _tabControler.TabPages[0];
remaining = (DockablePanel)tabPage.Controls[0];
// which way the controler is docked
DockStyle ctlStyle = this.Dock;
ControlerType ctlType;
if (ctlStyle == DockStyle.Left || ctlStyle ==
DockStyle.Right) {
ctlType = ControlerType.Vertical;
}
else {
ctlType = ControlerType.Horizontal;
}
// now we delete the _tabControler
_tabControler.TabPages[0].Controls.Clear();
_tabControler.TabPages.Clear();
this.Controls.Clear();
_tabControler.Visible = false;
_tabControler.Dispose();
// we add it to a normal DockSplitControler
_controlsType = ctlType;
remaining.DockingType = GetDockType(this.Dock);
// correct the DockType of the remaining DockablePanl
CreateMultiDockControler(ctlType, remaining);
this.Visible = true;
}
}
Here is the compiled result.
[ResultAdvancedReady.JPG]
Figure 9: The DockingManager showing different Docking methods.
Conclusion
You have finished the Advanced docking methods now. The next articles will contain how to pin and unpin this panel and how to use hooking the mouse to open and close the DockingControler when hovering over a buttonStrip. I hope you enjoy these lessons and will be happy to meet you in the next article.