Skip to content
SlayerDharok edited this page Jan 14, 2023 · 4 revisions

Grids are a layout container that arrange their children according to the row+column (cell) they're placed in. You must configure at least one RowDefinition and at least one ColumnDefinition. These definitions define sizing constraints that are used to arrange the cells' contents. Then you must specify the Row and Column zero-based index that each child resides in.

Definitions

Grids use a list of RowDefinitions and ColumnDefinitions to arrange its layout.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Length="45px" />
            <RowDefinition Length="75px" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Length="100px" />
            <ColumnDefinition Length="80px" />
        </Grid.ColumnDefinitions>

        <Border Row="0" Column="0" Background="Red" Content="Content of Cell=(0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of Cell=(0,1)" />
        <Border Row="1" Column="0" Background="Green" Content="Content of Cell=(1,0)" />
        <Border Row="1" Column="1" Background="MediumPurple" Content="Content of Cell=(1,1)" />
    </Grid>
</Window>

grid1

Each RowDefinition or ColumnDefinition must have a Length value. Lengths can be defined using one of these 3 formats:

Length Type Examples Description
Auto Length="Auto" Auto-sized definitions will compute the minimally-required size needed to display all the contents within that row or column.

For a RowDefinition, the Grid measures all children within that row, and sets the Row's Height to the maximum Height of those children. For a ColumnDefinition, the Grid measures all children within that column, and sets the Column's Width to the maximum Width of those children.
Pixel Length="100px"
Length="45"
Pixel-sized definitions are explicitly sized by a given number of pixels. Pixels must be a positive integer value, optionally suffixed with px
Weighted Length="*"
Length="0.4*"
Weighted-sized definitions consume a percentage of the grid's available space.

If you have multiple weighted definitions, they will each receive a proportional percentage of the remaining space.
Example: Row1's Length is 0.8*, Row2's Length is 1.3*. Row1 will receive 0.8/(0.8+1.3)=38.1% of the space, Row2 will receive 1.3/(0.8+1.3)=61.9%

Weighted definitions are allocated space after Auto and Pixel sized definitions.
<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="White" Height="300" Width="450">
        <Grid.RowDefinitions>
            <RowDefinition Length="Auto" />
            <RowDefinition Length="100px" />
            <RowDefinition Length="0.65*" />
            <RowDefinition Length="0.35*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Length="Auto" />
            <ColumnDefinition Length="80" />
            <ColumnDefinition Length="*" />
        </Grid.ColumnDefinitions>

        <Border Row="0" Column="0" Background="Red" Content="(0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="(0,1)" />
        <Border Row="0" Column="2" Background="Green" Content="(0,2)" />
        
        <Border Row="1" Column="0" Background="MediumPurple" Content="(1,0)" />
        <Border Row="1" Column="1" Background="Purple" Content="(1,1)" />
        <Border Row="1" Column="2" Background="LightGray" Content="(1,2)" />
        
        <Border Row="2" Column="0" Background="Magenta" Content="(2,0)" />
        <Border Row="2" Column="1" Background="Cyan" Content="(2,1)" />
        <Border Row="2" Column="2" Background="Navy" Content="(2,2)" />
        
        <Border Row="3" Column="0" Background="Gold" Content="(3,0)" />
        <Border Row="3" Column="1" Background="Crimson" Content="(3,1)" />
        <Border Row="3" Column="2" Background="Coral" Content="(3,2)" />
    </Grid>
</Window>

grid2

Size Constraints

You may specify minimum/maximum constraints on each RowDefinition or ColumnDefinition.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="White" Height="300" Width="200">
        <Grid.RowDefinitions>
            <RowDefinition Length="*" MaxHeight="70" />
            <RowDefinition Length="*" />
            <RowDefinition Length="*" MinHeight="190" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Length="*" />
        </Grid.ColumnDefinitions>

        <Border Row="0" Column="0" Background="Red" Content="(0,0)" />
        <Border Row="1" Column="0" Background="Orange" Content="(0,1)" />
        <Border Row="2" Column="0" Background="Magenta" Content="(0,2)" />
    </Grid>
</Window>

grid3

The Grid is explicitly set to Height="300". It contains 3 RowDefinitions, each requesting 1/3 of the height. Normally, each of these rows would receive 1/3*300=100px of height. But because there are MinHeight and MaxHeight constraints, the calculation changes as follows:

  • (There are currently 300px of remaining unallocated Height, and the sum of the weights is currently 3.0)
  • RowDefinitions with a MaxHeight are processed first (because if the Row's Height is truncated, it frees up more Height for the next Row to consume)
    • This row is processed: <RowDefinition Length="*" MaxHeight="70" />
    • It receives Math.Clamp(1.0 / 3.0 * 300, 0, 70) = 70px.
  • (There are now 300-70=230px of remaining unallocated Height, and the sum of the remaining weights is now 2.0)
  • RowDefinitions with a MinHeight are processed next (because if the Row's Height is increased to it's MinHeight, it consumes more Height than usual, leaving less leftover Height for the next Weighted Rows to consume)
    • This row is processed: <RowDefinition Length="*" MinHeight="190" />
    • It receives Math.Clamp(1.0 / 2.0 * 230, 190, int.MaxValue) = 190px.
  • (There are now 230-190=40px of remaining unallocated Height, and the sum of the remaining weights is now 1.0)
  • RowDefinitions without a MinHeight nor a MaxHeight are processed last
    • This row is processed: <RowDefinition Length="*" />
    • It receives Math.Clamp(1.0 / 1.0 * 40, 0, int.MaxValue) = 40px.

Alternative Syntax

For convenience, you can also specify several RowDefinitions or ColumnDefinitions using a single string value that is parsed to a list of values, by utilizing the RowLengths and ColumnLengths string properties instead of the RowDefinitions and ColumnDefinitions List properties.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="White" Height="300" Width="200" RowLengths="*[,70],*,*[190,]" ColumnLengths="*">
        <Border Row="0" Column="0" Background="Red" Content="(0,0)" />
        <Border Row="1" Column="0" Background="Orange" Content="(0,1)" />
        <Border Row="2" Column="0" Background="Magenta" Content="(0,2)" />
    </Grid>
</Window>

Values are delimited by a comma ,, and can optionally contain Minimum/Maximum size constraints inside Brackets [ ]

  • RowLengths="*[,70],*,*[190,]" parses to 3 RowDefinitions:
    • *[,70]
      • Length="*", MinHeight=null, MaxHeight="70"
    • *
      • Length="*", MinHeight=null, MaxHeight=null
    • *[190,]
      • Length="*", MinHeight="190", MaxHeight=null`

Complex example: ColumnLengths="Auto[20,50],1.2*[,200],16px,60,*,1.5*[80,300]"

RowSpan / ColumnSpan

Use the RowSpan or ColumnSpan properties to allow a child to span multiple cells.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="LightGray" RowLengths="70,60" ColumnLengths="140,140">
        <Border Row="0" Column="0" Background="Red" Content="Content of (0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of (0,1)" />
        <Border Row="1" Column="0" Background="Magenta" Content="Content of (1,0)" />
        <Border Row="1" Column="1" Background="MediumPurple" Content="Content of (1,1)" />
        
        <Border Row="0" Column="0" ColumnSpan="2" Background="LightBlue" Opacity="0.8" Content="Stretches (0,0) to (0,1), centered" HorizontalAlignment="Center" VerticalAlignment="Center" />
        <Border Row="1" Column="0" ColumnSpan="2" Background="Green" Opacity="0.8" Content="Stretches (1,0) to (1,1), aligned bottom" VerticalAlignment="Bottom" Margin="0,0,0,5" />
        <Border Row="0" Column="0" RowSpan="2" ColumnSpan="2" Background="Blue" Opacity="0.8" Content="Stretches (0,0) to (1,1), centered" 
                HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Window>

grid5

Warning - If a child spans multiple cells, the Grid measurement logic still treats it as if it only exists in a single cell. This may cause unexpected results if your RowDefinitions or ColumnDefinitions are using Length="Auto".

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="LightGray" RowLengths="50" ColumnLengths="Auto,Auto">
        <Border Row="0" Column="0" Background="Red" Content="Content of (0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of (0,1)" />
        <Border Row="0" Column="0" ColumnSpan="2" Background="MediumPurple" Content="Stretches from (0,0) to (0,1)" VerticalAlignment="Bottom" Margin="5" />
    </Grid>
</Window>

grid6

As a workaround, consider setting GridAffectsMeasure="false" on the child that spans multiple cells:

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="LightGray" RowLengths="50" ColumnLengths="Auto,Auto">
        <Border Row="0" Column="0" Background="Red" Content="Content of (0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of (0,1)" />
        <Border Row="0" Column="0" ColumnSpan="2" GridAffectsMeasure="False" Background="MediumPurple" Content="Stretches from (0,0) to (0,1)" VerticalAlignment="Bottom" Margin="5" />
    </Grid>
</Window>

grid7

Rendering Order

You can add multiple children to the same cell. The children are drawn in the order they were added, so children added last appear on top.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="LightGray" Padding="5" RowLengths="Auto" ColumnLengths="Auto">
        <Border Row="0" Column="0" Background="Red" Content="First Child of (0,0)" Margin="0,0,12,12" />
        <Border Row="0" Column="0" Background="LightBlue * 0.85" Content="Second Child of (0,0)" Margin="12,12,0,0" />
    </Grid>
</Window>

grid4

Cell Spacing

Use the RowSpacing and ColumnSpacing properties to apply padding between each row or column.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="LightGray" Padding="2" RowLengths="45,75,60" ColumnLengths="100,80" RowSpacing="8" ColumnSpacing="20">
        <Border Row="0" Column="0" Background="Red" Content="Content of Cell=(0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of Cell=(0,1)" />
        <Border Row="1" Column="0" Background="Green" Content="Content of Cell=(1,0)" />
        <Border Row="1" Column="1" Background="MediumPurple" Content="Content of Cell=(1,1)" />
        <Border Row="2" Column="0" Background="Magenta" Content="Content of Cell=(2,0)" />
        <Border Row="2" Column="1" Background="Coral" Content="Content of Cell=(2,1)" />
    </Grid>
</Window>

grid8

GridLines

Use the GridLinesVisibility, GridLineIntersectionHandling, GridLineMargin, HorizontalGridLineBrush, and VerticalGridLineBrush properties to manage the Grid's gridlines.

Type Property Possible Values Description
GridLinesVisibility GridLinesVisibility None, InnerHorizontal, TopEdge, BottomEdge, InnerVertical, LeftEdge, RightEdge, All, AllHorizontal, AllVertical Determines which grid lines, if any, will be visible. This is a Flags enum, so you may use bitwise operators to combine them, such as TopEdge | BottomEdge | LeftEdge if you only wanted gridlines on certain locations.
GridLineIntersection GridLineIntersectionHandling HorizontalThenVertical, VerticalThenHorizontal Determines the order in which grid lines are drawn, which affects how the intersection points of the gridlines will appear.
int GridLineMargin Can be used to reserve a defined amount of empty space around the gridlines. You can also think of this as a margin around each cell in the Grid
IFillBrush HorizontalGridLineBrush The brush used to draw horizontal gridlines.
IFillBrush VerticalGridLineBrush The brush used to draw vertical gridlines.

Example:

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="Cyan" Padding="2" RowLengths="45,75,60" ColumnLengths="100,80" RowSpacing="12" ColumnSpacing="12"
          GridLinesVisibility="All" GridLineMargin="3" GridLineIntersectionHandling="HorizontalThenVertical" HorizontalGridLineBrush="Purple" VerticalGridLineBrush="MediumPurple">
        <Border Row="0" Column="0" Background="Red" Content="Content of Cell=(0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of Cell=(0,1)" />
        <Border Row="1" Column="0" Background="Green" Content="Content of Cell=(1,0)" />
        <Border Row="1" Column="1" Background="MediumPurple" Content="Content of Cell=(1,1)" />
        <Border Row="2" Column="0" Background="Magenta" Content="Content of Cell=(2,0)" />
        <Border Row="2" Column="1" Background="Coral" Content="Content of Cell=(2,1)" />
    </Grid>
</Window>

grid9

The horizontal gridlines are 4px tall because RowSpacing-GridLineMargin*2=4.The vertical gridlines are 4px wide because ColumnSpacing-GridLineMargin*2=4. So the spacing properties define how much empty space is between consecutive rows or columns, and then the GridLineMargin is reserved on each edge of that space. All leftover space is used to fill in the gridline.

(Note: The GridLineMargin is not reserved along the outer edges of the Grid. If you want empty space along the outer edge, set Grid.Padding to a non-zero value instead.)

Selection

Use the SelectionMode, SelectionBackground, SelectionOverlay, and CanDeselectByClickingSelectedCell properties to manage Grid's selection capabilities. Use Grid.CurrentSelection.GetCells() method to get the cells that are currently selected (CurrentSelection may be null).

Type Property Values Description
GridSelectionMode SelectionMode None, Row, Column, Cell Determines what type of selection the user may make by clicking within the Grid.
IFillBrush SelectionBackground A brush that is drawn underneath selected cell(s). The content of the cell is drawn after this brush.
IFillBrush SelectionOverlay A brush that is drawn overtop of selected cell(s). The content of the cell is drawn before this brush. This brush should typically have some transparency.
bool CanDeselectByClickingSelectedCell If true, user may click on a selected cell to set the Grid's CurrentSelection back to null.
GridSelection CurrentSelection Retrieves the current selection of the Grid, if any (can be null)

Example: (SelectionMode="Cell")

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Background="Cyan" Padding="2" RowLengths="45,75" ColumnLengths="100,80" RowSpacing="12" ColumnSpacing="12" SelectionMode="Cell" SelectionOverlay="Yellow * 0.5">
        <Border Row="0" Column="0" Background="Red" Content="Content of Cell=(0,0)" />
        <Border Row="0" Column="1" Background="Orange" Content="Content of Cell=(0,1)" />
        <Border Row="1" Column="0" Background="Green" Content="Content of Cell=(1,0)" />
        <Border Row="1" Column="1" Background="MediumPurple" Content="Content of Cell=(1,1)" />
    </Grid>
</Window>

grid10

Try setting SelectionMode to Row:

grid11

GridSplitters

GridSplitters can be placed within a Grid, allowing the user to click and drag them to dynamically resize the Grid's rows or columns.

<Window xmlns="clr-namespace:MGUI.Core.UI.XAML;assembly=MGUI.Core"
        Left="200" Top="200" MinHeight="0" SizeToContent="WidthAndHeight" WindowStyle="None">
    <Grid Width="500" Height="500" Background="LightGray" RowLengths="200[100,],16,*[100,]" ColumnLengths="*[100,],12,200[100,]">
        <Border Row="0" Column="0" Background="Red" Content="Content of Cell=(0,0)" />
        <Border Row="2" Column="0" Background="Magenta" Content="Content of Cell=(2,0)" />
        <Border Row="0" Column="2" Background="Orange" Content="Content of Cell=(0,2)" />
        <Border Row="2" Column="2" Background="MediumPurple" Content="Content of Cell=(2,2)" />

        <GridSplitter Row="1" Column="0" ColumnSpan="3" />
        <GridSplitter Row="0" Column="1" RowSpan="3" />
    </Grid>
</Window>

grid12

It's common to have vertical GridSplitters span multiple rows via RowSpan, or horizontal GridSplitters span multiple columns via ColumnSpan). Also notice that the resizing still respected the MinHeight and MinWidth values applied to the rows and columns in the above example.

Clone this wiki locally