Virtual Developer Workshop: Containerized Development with Docker
I've lost count of how many times I've seen on the C# forum the question "how do I write an application with the look and feel of Visual Studio?" The reply usually comes back that they should use a commercial library.
However, after looking at the examples that are available, I thought that it shouldn't be too difficult to write one personally; this is the result.
I've also provided two example applications: one that creates and docks a large number of controls and another that is Notepad-like with docking panels.
Using the Library
The completed application for this walkthrough is provided as the "Tutorial" project.
I've going to assume that the reader has created a windows forms solution and added the docking controls libraries project. For details of how to do this see my previous article on MDI forms.
First, add a tool strip container menu strip, status strip, and tool strips to your application as required. Next, rebuild the project and then create an inherited user control by:
- Right-clicking your windows application project leaf in the solution explorer tree.
- Selecting 'add class' from the drop-down menu.
- Selecting 'inherited user control' from the list in the 'add new item' page.
- Typing the class name of your new control in the edit box below.
- Clicking 'add' and select 'DockingManagerControl' from the list.
Open your new user control in the forms designer and, on opening the toolbox, open the 'DockingControls' tab. Drag and drop the 'TabbedDocumentControl' onto your user control and set its dock style to 'Fill'.
Close your user control, rebuild your application, and then open your main form in the forms designer if it's not already open. Go to the toolbox and open the tab for your application. You will see your new control in the list. This should be dragged and dropped onto your main application; set its dock style to 'Fill'.
You've now done everything necessary to add the controls to your main form.
Controls are laid out in the following manner:
The DockingManagerControl is the top-level control, containing IDockingPanels (concrete class DockControlContainer) on the left, right, top, and bottom, which in turn contain IDockingControls (concrete class DockingControl) that contain the actual controls you specify.
First, you need to add a docking panel to contain the control you want to be dockable. Do this by calling the following inside of the constructor of your new user control (after the InitializeComponent call):
IDockingPanel rightPanel = this.Panels[DockingType.Right].InsertPanel(0);
Now that you have the panel to dock your control on, you can create a control (a textbox, for example) and dock it.
TextBox textBox = new TextBox(); textBox.Name = "Untitled"; textBox.Multiline = true; IDockingControl dockingControl = rightPanel.DockedControls.Add("Text", textBox );
Well, that's pretty much all you need to do to create a docking control.
To have a form-looking docking control—in other words, one with multiple controls on it—create a user control containing the form's controls and then dock that. For an example, look at the 'find' docking control in the Notepad example.
To add a control to the tabbed document control, use the following:
TextBox textBox2 = new TextBox(); textBox2.Multiline = true; // add the control and set its title _tabbedDocumentControl.Items.Add("Title", textBox2);
where _tabbedDocumentControl is the member name of the TabbedDocumentControl you added to your new user control.
UI Class Details
I'll start with the bottom level classes, and move my way upwards.
IDockingControl (concrete class DockingControl)
This is created by the IDockingPanel.Items.Add call and is a control that wraps and dock-fills any control you specify. It contains methods to dock the control (DockControl), float the control (FloatControl), and change the state. Cancel could be otherwise called Closed, but this would conflict with the standard Close() meaning in Windows systems. In fact, when a control is cancelled it still exists, but is invisible. DockedDimension is the height of the control when docked with other controls to the left and right, and width when the control is docked to the top and the bottom.
IDockingPanel (concrete class DockControlContainer)
This is created by the DockingManagerControl.Panels.Add call. A docking panel contains docking controls. It exposes a collection of all the IDockingControls it contains along with whether it is tabbed.
It also has a Dimension property that is the width of the control when docked left or right and the height of the control when docked to the top or the bottom. The LayoutControls method is used when not in Tabbed mode, and auto-arranges all the docking controls so their heights are equal.
The top level control. It has the Panels property that can be used to gain access to the inner docking panels. For instance, if you want to get the third panel from the left of the panels docked to the left, you use:
It also has a Renderer property that can be used to change the ToolStripRenderer used in the captions of the docking controls.
Contains serialization and deserialization methods to save and load the docking state of the DockingManagerControl.
Note: You should insert the call to Deserialize in either the OnLoad overridden method of your main form, after the call to base class, or in a handler for the Load event.
Provides the tabbed document functionality. These are self explanatory (for example, SelectedControl property returns the currently selected control).
This control supports the usual keyboard shortcuts of:
- Ctrl+Tab to move to the next document.
- Ctrl+Shift+Tab to move to the previous document.
- Ctrl+F4 to close the current document.
It also contains events for selection changed, a control being added, and a control being removed (or closed).
There are classes to encapsulate most behaviour that could be easily reused. For instance, the DragHandler class automates dragging and handles the escape keypress to cancel it.
One point of note is that MenuStrips and ToolStrips only work when the parent form is selected. This is somewhat annoying when having a floating window; it means you have to click twice on a button or menu item to get it to work as expected. The reason for this is that the WM_MOUSEACTIVATE message is eaten by the controls, not producing a corresponding MouseDown event. There are alternative implementations of ToolStrip and MenuStrip in the docking control library that fix the problem.
This library should be self explanatory and very easy to use. I recommend that anyone reading this article examine the implementation of the library because it does contain the solution to a number of interesting problems.
This library has been tested as much as it possible, but if anyone finds any bugs, I'll happily fix them and resubmit.