Wisej.NET includes several built-in layout engines and allows developers to easily build custom layout engines. It allows the application to implement layouts of any complexity, exceeding what is available in the browser using plain CSS.
Web frameworks based on traditional HTML string concatenation and plain CSS layouts (blazor, angular, PHP, ASP.NET, JSP, ...) only support a fraction of the layouts available to a Wisej.NET application.
The task of arranging controls in their container is performed by layout engines. Every control's LayoutEngine property returns the current engine and can be overridden in a derived class. The engine is responsible for measuring the preferred size for AutoSize controls and for arranging the position and the size of the direct children of the container.
All controls use the DefaultLayout engine, it supports:
Docking. Children can dock to the parent using one of the DockStyles.
Anchoring. Children can anchor their sides to the parent using a combination of AnchorStyles.
Docking is applied to the child controls in inverse order "away from the viewer". Changing the order of the child controls changes how the docking uses the available space and the intersection between horizontal and vertical docked controls.
Controls dock using the container's DisplayRectangle area, which is reduced by the Padding property of the container.
Margins are not used by the DefaultLayout engine. If you need to increase the distance between docked controls, add docked Spacers.
Anchoring styles can be applied to any of the four sides of a control, or none.
When a control has no anchoring (AnchorStyles.None), it will "float" within its container, preserving its relative location. Likewise, if anchoring is not set only for the vertical sides or for the horizontal sides, the control "floats" vertically or horizontally.
To keep a control centered in its parent, center it and remove the anchoring.
The default initial value of the Anchor property is Top + Left.
Padding and Margins are irrelevant to the anchoring.
The flow layout engine is implemented for the FlowLayoutPanel. Child controls are arranged horizontally or vertically next to each other.
When using a FlowLayoutPanel in the designer, it extends all its children and adds a number of extension properties that are relevant only for the flow layout:
FlowBreak. When true, the control causes a flow break and wraps to either the next line or the next column, depending on the FlowDirection of the panel.
You can also set these values programmatically by calling flowLayoutPanel.SetFlowBreak(child, value) or flowLayoutPanel.SetFillWeight(child, value).
In the animation below, the green buttons have their FillWeight set to 1. On the left there is a FlowLayoutPanel set to flow horizontally, and on the right there is a FlowLayoutPanel set to flow vertically.
Margins are enforced in the FlowLayout engine. Changing the Margin property on a child control will increase the distance to the controls next to it.
The table layout engine is implemented for the TableLayoutPanel. Child controls are arranged in cells in a grid.
When using the TableLayoutPanel in the designer, it extends its children and adds a number of extension properties that are relevant only for the table layout:
Row, Column, Cell. Determine in which cell of the grid to place the control. Only 1 control can be in a specific cell.
RowSpan. Determines how may rows are occupied by the cell.
ColumnSpan. Determines how many columns are occupied by the cell.
This layout engine doesn't allow for wrapping but it supports growing. If you add a child programmatically, you can determine whether to add a new row or a new column when all the cells are already assigned by setting the GrowStyle property.
Use the RowStyles and ColumnStyles collections to determine the sizing modes of the cells. They can resize proportionally using a percentage, auto size to fit the content, or have a fixed size in pixels. Additionally, controls can dock or anchor inside the cell they are assigned to.
The animation below shows a TableLayoutPanel where button3 spans 2 columns and is anchored left and right and centered vertically in the cell. Adding new controls using the code in the snippet below automatically adds new rows when the cells in the last row are all occupied.
Margins are enforced in the TableLayout engine. Changing the Margin property on a child control will change the distance to the controls next to it.
The flex layout engine is implemented for the FlexLayoutPanel. It's actually two layout engines: HBoxLayout engine and VBoxLayout engine. This container arranges its children horizontally or vertically always filling the client area.
Controls can use Margin, MinimumSize, MaximumSize and a number of extension properties to customize the layout:
AlignX. Determines how to align child controls that cannot fill the container horizontally in a vertical FlexLayoutPanel because their width is constrained. It overrides the default HorizontalAlign value for the single control.
AlignY. Determines how to align child controls that cannot fill the container vertically in a horizontal FlexLayoutPanel because their height is constrained. It overrides the default VerticalAlign value for the single control.
The animation below shows two FlexLayoutPanels, the first uses the HBox layout and the second uses the VBox layout. Some of the buttons have their FillWeight set to 1, and button3 is set to align vertically.
Margins are enforced in the FlexLayout engine. Changing the Margin property on a child control will change the distance to the controls next to it.
You can build a custom layout engine by deriving from the Wisej.Web.Layout.LayoutEngine class and overriding the Control.LayoutEngine property in your container class.
You may also create just one instance (a singleton) of your layout engine and reuse it, instead of creating a new instance each time the container is instantiated.
A layout engine needs to implement only three methods:
InitLayout(child, specifiedBounds). It's optional, you can let the base implementation do its work. Since layout engines can be cached, this call allows the implementation to refresh any internal cache that it may keep related to a child control it operates on.
GetPreferredSize(container, proposedSize). It's optional, you can let the base implementation do its work. This method is used when the container uses auto sizing (AutoSize property) and needs to "measure" its children to determine its preferred size.
Layout(container, args). This is the method that arranges all the child controls in their container. The most simple layout engine does nothing and allows the controls to use their own Location and Size.
The sample below shows an easy custom layout implementation that always arranges the child controls in a cascading layout from the top left to bottom right.
The CascadeLayout engine above goes with the CascadingLayoutPanel below. It has a new property Gap that is used by the layout engine.
Depending on the value of the Gap property and the size of the container, this is the result.
Since 3.0
The designer's toolbar has a new option to arrange child controls without setting the Dock or the Anchor properties.
Clicking the AutoLayout button opens the AutoLayout floating panel:
Arranges the controls horizontally, using the available space proportionally.
Arranges the controls vertically, using the available space proportionally.
Docks the controls to the left of the containing area.
Docks the controls to the right of the containing area.
Docks the controls to the bottom of the containing area.
Docks the controls to the top of the containing area.
Resizes the controls to fill the containing area.
Toggles using the controls' margin when applying the auto layout.
Selects the horizontal alignment of the controls within the containing area.
Selects the vertical alignment of the controls within the containing area.
Sets the spacing between the controls in pixels.
Margins are also used by the designer to create proximity snap lines.
When you move a control close to another, you will see the proximity snap line according to the controls' margins.
The vertical snap line between the controls is the combination of the top control's Margin.Bottom and the bottom control's Margin.Top values. Using this feature correctly simplifies the work of UI developers and preserves UI/UX guidelines.
FillWeight. It's an arbitrary integer that determines whether the child control grows horizontally or vertically (depending on the value of FlowDirection) to use the remaining available space. The default is 0, which means the child control preserves its size. When using FillWeight you should also set the control's MinimumSize or it may shrink to 0.
FillWeight. It's an arbitrary integer that determines whether the child control grows horizontally or vertically (depending on the value of LayoutStyle) to use the remaining available space. The default is 0, which means the child control preserves its size. When using FillWeight you should also set the control's MinimumSize or it may shrink to 0.