Exploring Creativity with Design / Graphics / Technology
Simple TriggerAction for Docking Using GridSplitters
GridSplitters work great if you want to provide a split view between two
views. By dragging the GridSplitter you can adjust the space allocated
to each view. As one view grows in size, the other reduces by the same
size. For most cases, this is exactly what is required. In my case, this
wasn’t enough. In addition to adjusting the splitter, I also wanted a
way to quickly collapse one view and allocate all the space to the other
view (a.k.a. Docking). The views were arranged horizontally like so:
To do the collapse action to one side, I added two buttons to the
GridSplitter’s ControlTemplate (shown below). One would collapse the
left-view and the other the right-view.
What you are seeing in the figure above is a Grid with 3 columns. The
middle column is assigned to the GridSplitter. This is an important
assumptionthat I make: that the GridSplitter occupies the center
column (or center row) and there are views to the either side in their
own columns (or rows). This assumption will be used in my custom
TriggerAction, called CollapseAction.
The CollapseAction for the GridSplitter buttons
The idea of collapse is to maximize one of the
views and assign zero space to the other view. This can be achieved by
adjusting the Widths of the ColumnDefinitions of the containing Grid (or
adjusting the Heights of the RowDefinitions). It is probably easier to
see it all in code, so here you go:
publicclassCollapseAction:TriggerAction<Button>{publicDockDirection{get;set;}protectedoverridevoidInvoke(objectparameter){// First find the nearest splittervarsplitter=FindVisual<GridSplitter>(AssociatedObject);if(splitter!=null){vargrid=FindVisual<Grid>(splitter);// Find nearest Gridif(grid!=null){ApplyDock(grid);}}}privatevoidApplyDock(Gridgrid){varcDef1=grid.ColumnDefinitions.FirstOrDefault();varcDef2=grid.ColumnDefinitions.LastOrDefault();varrDef1=grid.RowDefinitions.FirstOrDefault();varrDef2=grid.RowDefinitions.LastOrDefault();switch(Direction){caseDock.Left:cDef1.Width=newGridLength(0);cDef2.Width=newGridLength(1,GridUnitType.Star);break;caseDock.Right:cDef2.Width=newGridLength(0);cDef1.Width=newGridLength(1,GridUnitType.Star);break;caseDock.Top:rDef1.Height=newGridLength(0);rDef2.Height=newGridLength(1,GridUnitType.Star);break;caseDock.Bottom:rDef2.Height=newGridLength(0);rDef1.Height=newGridLength(1,GridUnitType.Star);break;}}privateTFindVisual<T>(FrameworkElementrelElt)whereT:FrameworkElement{varparent=VisualTreeHelper.GetParent(relElt);while(parent!=null&&!(parentisT)){parent=VisualTreeHelper.GetParent(parent);}returnparentasT;}}
The core of the work happens in
the ApplyDock() method. Depending on the Direction, I zero-size one
of the columns/rows and give all the layout-space to the other
column/row. The middle column/row is of fixed-size and is used by the
GridSplitter (as noted earlier). In the ControlTemplate I assign this
action to each of the buttons and set the Direction appropriately. This
can be seen in the following snippet of the GridSplitter’s
ControlTemplate. Note the actions assigned to the Buttons.
Below you can see the two configurations of docking to the
left and to the right. Note that you can still grab the GridSplitter and
drag to adjust the columns.
Subtle things to note about the GridSplitter
In the ControlTemplate, I have added two Buttons and set their
Cursor to Arrow. This is required because by default the
GridSplitter assigns a Cursor of SizeWE or SizeNS depending on the
alignment. When you move the mouse on the Buttons, you continue to
get this sizing cursor. This hinders with the usability of the
buttons and to aid in the usage, we set the Cursor of the buttons to
Arrow.
The ResizeBehavior has been explicitly set to PreviousAndNext.
This should make sense now given our earlier assumption.
Additionally, the ResizeDirection has also been set to Columns.