Repository navigation
Docking
DockHost (ZeppelinForms.Forms.Controls.Docking) holds panels the user arranges, the way
an IDE's are arranged. Panels sit side by side and as tabs, with splitters between them.
They can float in windows of their own over the layout, or be pinned to an edge, where
they slide out while they are in use. The user drags them around by their tabs, and the
whole arrangement is saved and restored as JSON.
var host = new DockHost();
var editor = new DockItem("editor", "Program.cs", new TextBox { IsMultiline = true });
var files = new DockItem("files", "Explorer", tree) { CanClose = false };
var output = new DockItem("output", "Output", log) { HideOnClose = true };
host.DockTo(editor); // a tab of the layout
host.DockTo(files, DockPosition.Left, size: 0.25f); // a quarter on the left
host.DockTo(output, DockPosition.Bottom, editor, 0.3f); // under the editor alone
Content = host;A DockItem is a panel: its Id, the Title and Icon of its tab, a ToolTip, and the
Content it shows. Whatever the item stands for, a document or a tool window, belongs to
the application and is kept in Tag.
- The content fills its panel, whatever its alignment says.
- Only the selected tab of a group is in the tree. The others are neither measured nor drawn, and they keep their state until they are selected again.
-
Idnames the item in a saved layout, and it is unique within its host. -
Statesays where the item is:Docked,Floating,AutoHiddenorHidden. -
IsActivesays whether the item was the last one used.
The flags belong to the user. CanClose, CanFloat and CanAutoHide remove the matching
buttons and menu items, but the host's methods still do what they are told. With
HideOnClose, closing an item hides it instead of taking it out of the host: that is how
a tool window behaves when a menu shows it again.
| Method | What it does |
|---|---|
DockTo(item, position, target, size) |
Tabbed makes the item a tab of the target's group. Left, Top, Right or Bottom puts it beside the target, taking size of the target's extent. Without a target, Tabbed adds the item to the group that was active last, and a side puts it beside the whole layout. |
Float(item, bounds) |
Puts the item in a floating window, in the host's coordinates. Without bounds, the window goes where the item last floated, or to the middle. |
AutoHide(item, side) |
Pins the item to an edge. Without a side, it goes to the edge nearest to where it was docked. |
Hide(item) |
Hides the item. It stays in Items, and the host remembers its place. |
Show(item) |
Brings a hidden item back to where it was: docked, floating or on its edge. An auto-hidden item slides out, and a tab is selected. Either way, the item becomes active. |
Close(item) |
Closes the item as its close button does. ItemClosing may cancel it; otherwise the item is hidden (with HideOnClose) or removed from the host. |
An item that is not in the host yet is added by any of these methods. An item belongs to one host at a time: once it is closed and removed, it is free to go to another.
A hidden item goes back to the group it left while that group is still in the layout. If the group is gone, the item goes to the side it was on. When several items leave the same group and come back one by one, they meet again in one group.
- Dragging a tab up or down off its strip turns the strip's drag into the host's. A compass appears over the group under the pointer: its middle makes the item a tab of that group, and its four sides split the group. Four more buttons sit on the host's own edges. Over a group's tabs, dropping the item makes it a tab without aiming for the middle button. Dropping it anywhere else floats it where it was dropped. Esc cancels the drag, wherever the focus is.
- A floating window is moved by its title bar and resized by its frame. Dragging it by the title shows the compass too, and the whole window docks where it is dropped. A double click on the title docks the window back.
- A double click on a tab floats the item.
- The pin in a group's header pins the whole group to its edge. Clicking an edge's button slides the panel out; a press anywhere else, or Esc, slides it back. The panel's own pin docks it back, and its inner edge resizes it.
- The menu of a tab offers Float (or Dock), Auto hide, Close and Close others, as far as the item's flags allow.
- The splitters move the shares of their two neighbours, and the other panels keep theirs.
The floating windows are elements inside the host, not windows of the system, so they look and behave the same on every platform, including the browser and a phone. A window always keeps its title bar inside the host, where it can still be grabbed.
-
ActiveItemChangedis raised when the item last used changes. That happens when a tab is clicked, when the content of a panel is pressed, or whenShowis called. The active group is outlined inAccentColor. -
ItemClosingis raised before an item closes. SetCancelto keep it, for example to ask about unsaved changes first. -
ItemClosedis raised after an item closes, whether it was hidden or removed. -
LayoutChangedis raised when the arrangement changes: an item moved, or a splitter or a window was dragged. This is the moment to save the layout, if it is saved as it goes.
string json = host.SaveLayout();
settings.DockLayout = json;
// at the next start, with the items made again
host.LoadLayout(settings.DockLayout, id => id switch
{
"editor" => new DockItem("editor", "Program.cs", editor),
"files" => new DockItem("files", "Explorer", tree),
_ => null, // an id the application no longer has is left out
});The JSON records the splits and their shares, the groups and their tabs, the floating
windows and their bounds, the pinned items and how far each slides out, the hidden items,
and the active item. Items are named by Id. On loading:
- an item is either the host's own item with that id, or the one
resolvemakes for it; - an id that neither of them knows is left out;
- an item of the host that the layout doesn't name is hidden.
The layout is read through before anything changes. Text that is not a layout throws a
FormatException, and the host stays as it was. The writer and the reader are written by
hand, without reflection, so they work the same after trimming and with AOT.
The panels' colors are styled properties of the host: SplitterColor,
SplitterHoverColor, PanelBorderColor, PanelBackgroundColor, AccentColor,
HeaderColor, ActiveHeaderColor, ButtonHoverColor, PreviewColor and CompassColor.
The themes set all of them, high contrast included. The tabs are a
TabStrip, and the theme's rules for the strip apply to them. The splitters'
width is SplitterThickness.
DockHost { AccentColor: #7C4DFF; HeaderColor: #F3F0FA; }The tear-off is a TabStrip feature, so an application's own strip can use it as well.
With CanDragOut set, a tab dragged up or down off the strip raises TabDraggedOut with
the tab and the pointer. The strip then lets go of the drag, and a handler that takes the
pointer carries it on until the button is released. A browser-like application opens a
window for the tab there.
ZeppelinForms — fully free, for everyone. This wiki is generated from
docs/wiki:
edit it there, not here.
Start
Building the UI
Looks
Behaviour
Tooling