Introduction
The
DockPanel suite of UI tools, created by Weifen Luo, is a great open-source solution to making user interfaces that mimic the Visual Studio tabbed/docking windows environment.
I wanted to make a Window menu for the menu bar of my form that mimics that found in the Visual Studio environment. So I decided to derive off the main
DockPanel
class and call it
StudioDockPanel
and then put my minor improvements in this code.
One of the improvements is to implement the New Horizontal Tab Group and the New Vertical Tab Group commands that appear on the menu bar of the Visual Studio environment. These commands allow you to have one set of tabs "stacked" on top of another.
Code
To start off, first I derived a class,
StudioDockPanel
off of the
DockPanel
class provided by Weifen Luo:
namespace MyProject
{
public class StudioDockPanel : DockPanel
{
}
}
Next, I add an internal function,
NewHorizontalTabGroup
, to it:
internal void NewHorizontalTabGroup()
{
if (this.DocumentDockWindow == null
|| this.ActiveDocument == null)
return;
IDockContent CurrentDocument = this.ActiveDocument;
DockPane CurrentPane = this.ActiveContent.DockHandler.Pane;
DockPane NewPane = DockPaneFactory.CreateDockPane(CurrentDocument,
CurrentPane, DockAlignment.Bottom, 0.5, true);
NewPane.Show();
}
The
DocumentDockWindow
is a property I added in order to quickly find which of the
DockPanel.DockWinodws
is the one holding the tabbed document views:
public DockWindow DocumentDockWindow
{
get
{
foreach (DockWindow Window in DockWindows)
if (Window.DockState == DockState.Document)
return Window;
return null;
}
}
It's very straightforward; it just searches through the
DockWindows
list and returns the one whose
DockState
is set to
DockState.Document
. This is a public property, so you can drop this in your current code.
Moving along, now let's see how adding a new vertical tab group goes:
internal void NewVerticalTabGroup()
{
if (this.DocumentDockWindow == null
|| this.ActiveDocument == null)
return;
IDockContent CurrentDocument = this.ActiveDocument;
DockPane CurrentPane = this.ActiveContent.DockHandler.Pane;
DockPane NewPane = DockPaneFactory.CreateDockPane(CurrentDocument,
CurrentPane, DockAlignment.Right, 0.5, true);
NewPane.Show();
}
Now, when you have a Window menu, you want to update the UI of the menu commands themselves so they cannot be chosen unless there are more than 1 tabbed documents open. Otherwise, there is no content available to add to a new pane! For this purpose, i implemented a
TwoOrMoreOpenDocumentWindows
property:
public bool TwoOrMoreOpenDocumentWindows
{
get
{
if (this.DocumentDockWindow == null)
return false;
return this.DocumentDockWindow.VisibleNestedPanes[0].Contents.Count > 1;
}
}
The
IsDocumentVisible
property tells you if there is an active document at all or not:
public bool IsDocumentVisible
{
get
{
return this.ActiveDocument != null;
}
}
Finally, there is a requirement to know if there is at least one pane docked to the side of the window. This is to update the user interface and enable/disable the appropriate menu commands (Float, Dock, Dock as Tabbed Document, AutoHide, Hide etc.). So this helps us to put in a
IsAtLeastOnePaneDocked
property:
public bool IsAtLeastOnePaneDocked
{
get {
foreach (DockWindow Window in DockWindows)
if (Window.DockState != DockState.Document
&& Window.VisibleNestedPanes.Count > 0)
return true;
return false;
}
}
And there we are. There are more updates and tips or tricks to come, as I make further enhancements to this library in order to bring it closer to UI standards for this type of environment.
Usage
To implement Window menu commands Float, Dock, Dock As Tabbed Document, AutoHide, and Hide, we do the following (the veriable names for the menu commands should be pretty self-explanatory):
private void FloatWindow_Click(object sender, EventArgs e)
{
if (this.MainDockPanel.ActiveContent == null)
return;
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.Float;
}
private void DockWindow_Click(object sender, EventArgs e)
{
if (this.MainDockPanel.ActiveContent == null)
return;
this.MainDockPanel.ActiveContent.DockHandler.DockTo(
this.MainDockPanel, DockStyle.Left);
}
private void DockWindowAsTabbedDocument_Click(object sender, EventArgs e)
{
if (this.MainDockPanel.ActiveContent == null)
return;
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.Document;
}
private void AutoHideWindow_Click(object sender, EventArgs e)
{
if (this.MainDockPanel.ActiveContent == null)
return;
switch (this.MainDockPanel.ActiveContent.DockHandler.DockState)
{
case DockState.DockLeft:
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.DockLeftAutoHide;
break;
case DockState.DockBottom:
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.DockBottomAutoHide;
break;
case DockState.DockRight:
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.DockRightAutoHide;
break;
case DockState.DockTop:
this.MainDockPanel.ActiveContent.DockHandler.DockState = DockState.DockTopAutoHide;
break;
default:
break;
}
}
private void HideWindow_Click(object sender, EventArgs e)
{
if (this.MainDockPanel.ActiveContent == null)
return;
this.MainDockPanel.ActiveContent.DockHandler.Hide();
}
private void AutoHideAll_Click(object sender, EventArgs e)
{
foreach (DockContent content in this.MainDockPanel.Contents)
{
switch (content.DockState)
{
case DockState.DockLeft:
case DockState.DockLeftAutoHide:
content.DockState = DockState.DockLeftAutoHide;
break;
case DockState.DockBottom:
case DockState.DockBottomAutoHide:
content.DockState = DockState.DockBottomAutoHide;
break;
case DockState.DockRight:
case DockState.DockRightAutoHide:
content.DockState = DockState.DockRightAutoHide;
break;
case DockState.DockTop:
case DockState.DockTopAutoHide:
content.DockState = DockState.DockTopAutoHide;
break;
default:
break;
}
}
}
private void NewHorizontalTabGroup_Click(object sender, EventArgs e)
{
this.MainDockPanel.NewHorizontalTabGroup();
}
private void NewVerticalTabGroup_Click(object sender, EventArgs e)
{
this.MainDockPanel.NewVerticalTabGroup();
}
private void ResetWindowLayout_Click(object sender, EventArgs e)
{
}
Finally, you'll want to update the Window menu commands appropriately:
private void UpdateWindowMenu(object sender, EventArgs e)
{
int itemCount = windowMenu.DropDownItems.Count;
if (windowMenu.DropDownItems[itemCount - 1] is ToolStripSeparator)
windowMenu.DropDownItems.RemoveAt(itemCount - 1);
this.CloseAllDocuments.Enabled =
this.MdiChildren.Length > 0;
this.AutoHideAll.Visible = this.MainDockPanel.IsAtLeastOnePaneDocked;
this.NewHorizontalTabGroup.Visible = this.NewHorizontalTabGroup.Enabled = this.MainDockPanel.IsDocumentVisible
&& this.MainDockPanel.TwoOrMoreOpenDocumentWindows;
this.NewVerticalTabGroup.Visible = this.NewVerticalTabGroup.Enabled = this.MainDockPanel.IsDocumentVisible
&& this.MainDockPanel.TwoOrMoreOpenDocumentWindows;
if (this.MainDockPanel.ActiveContent == null)
{
this.FloatWindow.Enabled =
this.DockWindow.Enabled =
this.AutoHide.Enabled =
this.DockWindowTabbedDocument.Enabled =
this.HideWindow.Enabled = false;
return;
}
switch (this.MainDockPanel.ActiveContent.DockHandler.DockState)
{
case DockState.Document:
case DockState.Unknown:
this.FloatWindow.Enabled = true;
this.DockWindow.Enabled = true;
this.DockWindowTabbedDocument.Enabled = false;
this.AutoHide.Enabled = false;
this.HideWindow.Enabled = false;
break;
case DockState.DockBottom:
case DockState.DockBottomAutoHide:
case DockState.DockLeft:
case DockState.DockLeftAutoHide:
case DockState.DockRight:
case DockState.DockRightAutoHide:
case DockState.DockTop:
case DockState.DockTopAutoHide:
this.FloatWindow.Enabled = true;
this.DockWindow.Enabled = false;
this.DockWindowTabbedDocument.Enabled = true;
this.AutoHide.Enabled = true;
this.HideWindow.Enabled = true;
break;
case DockState.Float:
this.FloatWindow.Enabled = false;
this.DockWindow.Enabled = true;
this.DockWindowTabbedDocument.Enabled = true;
this.AutoHide.Enabled = false;
this.HideWindow.Enabled = true;
break;
default:
break;
}
}
And there you have it.
Dr. Brian Hart obtained his Ph.D. in Astrophysics from the University of California, Irvine, in 2008. Under Professor David Buote, Dr. Hart researched the structure and evolution of the universe. Dr. Hart is an Astrodynamicist / Space Data Scientist with Point Solutions Group in Colorado Springs, CO, supporting Space Operations Command, United States Space Force. Dr. Hart is a Veteran of the U.S. Army and the U.S. Navy, having most recently served at Fort George G. Meade, MD, as a Naval Officer with a Cyber Warfare Engineer designator. Dr. Hart has previously held positions at Jacobs Engineering supporting Cheyenne Mountain/Space Force supporting tests, with USSPACECOM/J58 supporting operators using predictive AI/ML with Rhombus Power, and with SAIC supporting the Horizon 2 program at STARCOM. Dr. Hart is well known to the community for his over 150 technical publications and public speaking events. Originally from Minneapolis/Saint Paul, Minnesota, Dr. Hart lives in Colorado Springs with his Black Lab, Bruce, and likes bowling, winter sports, exploring, and swimming. Dr. Hart has a new movie coming out soon, a documentary called "Galaxy Clusters: Giants of the Universe," about his outer space research. The movie showcases the Chandra X-ray Observatory, one of NASA’s four great observatories and the world’s most powerful telescopes for detecting X-rays. The movie has been accepted for screening at the U.S. Air Force Academy ("USAFA" for short) Planetarium and will highlight how scientists use clusters of galaxies, the largest bound objects in the Universe, to learn more about the formation and evolution of the cosmos --- as well as the space telescopes used for this purpose, and the stories of the astronauts who launched them and the scientists who went before Dr. Hart in learning more about the nature of the Universe.