Feature Proposal: Make Grid Better
See original GitHub issueProposal: Grid - More gain, less pain
Summary
Make a version of Grid that is easier to use and manipulate with capabilities that are on par with the CSS Grid.
Rationale
XAML’s Grid is the most widely used Panel and is very versatile. However, it’s also one of the more complicated panels and can be cumbersome. It doesn’t lend itself well to easily positioning elements or simple adjustments (e.g. inserting a new row/column). Enhancing the Grid in a few simple ways will go a long way towards easing a developer’s job.
Functional Requirements
# | Feature | Priority |
---|---|---|
1 | Able to refer to rows / columns by either number or a name | Must |
2 | Able to define spans as relative numbers or using names | Must |
3 | Able to assign a friendly name to a region of the grid and associate child element’s with that region by name versus the row(span)/col(span) #'s | Could |
4 | Able to more succinctly define rows / columns, with options to be more explicit | Should |
5 | Support auto flow where some items may be explicitly assigned and the rest implicitly fill in by tree order (with control over how it auto fills) | Should |
6 | Support for defining auto-generated rows/columns that are implicitly created to position items with otherwise out-of-bounds grid indices | Should |
7 | Support alignment options for a general policy that applies to all the Grid’s children (individual elements can override) | Could |
8 | Support a ShowGridLines property to aid in visual debugging the layout | Could |
Usage Examples
Named Row / Column
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Name="bottomrow" Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Name="rightcol" Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Row="bottomrow" Grid.Column="rightcol"/>
</Grid>
A More Succinct Syntax
<Grid RowDefinitions="Auto, 1*, {Name=bottomrow Width=Auto}"
ColumnDefinitions="Auto, 1*, {Name=rightcol Width=Auto}">
<Button Grid.Row="bottomrow" Grid.Column="rightcol"/>
</Grid>
Defining Named Areas Specify a single Row (or Column) using just the name or the index. You can optionally provide a span by name or relative count with the comma syntax “fromRow,toRow” or “fromRow,#”.
<Grid ColumnDefinitions="{Name=leftcol Width=Auto}, 1*, {Name=rightcol Width=Auto}"
RowDefinitions="{Name=toprow Width=Auto}, {Name=body Width=1*}, {Name=bottomrow Width=Auto}">
<Grid.AreaDefinitions>
<AreaDefinition Name="imageArea" Row="toprow,bottomrow" Column="0"/>
<AreaDefinition Name="header" Row="toprow" Column="leftcol,rightcol"/>
<AreaDefinition Name="footer" Row="bottomrow" Column="leftcol,rightcol"/>
<Grid.AreaDefinitions>
<Image Grid.Area="imageArea"/>
<TextBlock Grid.Area="Header"/>
<Button Grid.Row="bottomrow" Grid.Column="rightcol"/>
</Grid>
More Succinctly Defined Areas
<Grid ColumnDefinitions="{Name=leftcol Width=Auto}, 1*, {Name=rightcol Width=Auto}"
RowDefinitions="{Name=toprow Width=Auto}, {Name=body Width=1*}, {Name=bottomrow Width=Auto}"
AreaDefinitions="{Name=imageArea Row=toprow,bottomrow Column=0},
{Name=header Row=toprow Column=leftcol,rightcol},
{Name=footer Row=bottomrow Column=leftcol,rightcol}">
<Image Grid.Area="imageArea"/>
<TextBlock Grid.Area="Header"/>
<Button Grid.Row="bottomrow" Grid.Column="rightcol"/>
</Grid>
Auto-flow Layout w/ Auto-generated Rows (or Columns)
<Grid ColumnDefinitions="240, 1*"
ColumnSpacing="18"
AutoFlow="Row"
AutoRowDefinitions="{MinHeight=80 Height=Auto}">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
</Grid.Resources>
<TextBlock Text="Id"/>
<TextBox x:Name="IdField"/>
<TextBlock Text="Name"/>
<TextBox x:Name="NameField"/>
<TextBlock Text="Address"/>
<TextBox x:Name="AddressField"/>
<!-- Inserting a new label + field or re-ordering them doesn't require
updating all the Column/Row assignments -->
</Grid>
The AutoFlow property determines how elements that aren’t explicitly assigned to an area / row / column will be placed. The default is ‘None’. A value of ‘Row’ causes the layout to attempt to sequentially fill the columns of a row before moving to the next row. It can leave gaps by not considering if elements seen later could have fit on earlier rows.
A value of ‘RowDense’, however, would attempt to fill in the earlier gaps with elements seen later. RowDense can affect the visual ordering and could adversely impact accessibility. Similar behavior applies to values of Column and ColumnDense.
Detailed Feature Design
Open Questions
Would a Grid with these capabilities (i.e. named areas and autoflow + autogenerated rows/columns) simplify the way that you currently use Grid? For what scenario? Which of the proposed enhancements would you find most useful?
- compact syntax for defining rows/columns,
- support for defining and using named columns and rows,
- autoflow and auto-generated rows/columns)
Issue Analytics
- State:
- Created 5 years ago
- Reactions:52
- Comments:44 (25 by maintainers)
Define the undefined behaviors
XAML’s Grid is the most widely used Panel and is very versatile. However, there are some undefined behaviors.
I hope we can define undefined behaviors.
<div id="toc"></div>
Reading Tips: All of the examples described in this article are not common usages for
Grid
. (Microsoft is a great company. It will never do strange things in the common situation.)Star Unit on Infinite space
Copy and paste the code below and run to view the result:
See Scenario1 in demo.
The 1st column is
100
-pixel fixed-width. The 2nd column is*
, and the 3rd one is2*
. Then what’s the visible width of the 2ndBorder
and the 3rdBorder
?Did you predicate the result? Although the 2nd and the 3rd column width proportion is 1:2, the final visible proportion is 1:1.
There are flaws here, because you may suspect that the 3rd column is already twice as much as the 2nd column, but the right side is blank and cannot be seen. So now, we remove the
Canvas
and useHorizontalAlignment="Right"
. The new code is shown below:See Scenario2 in demo.
After running, you will find that there is no white space on the far right, that is to say, the 2nd and 3rd columns do not have a 1:2 ratio - they are equal.
So where is the lost space? Let’s resize the window to check it.
Even if there is space left on the left, the right side begins to clip the element space! Can we say that the length of a missing * length has gone to the left? Obviously not. However, we can guess that the clipping of the right side of the element begins at the 1:2 ratio.
Star Unit at the Size Just Required
HorizontalAlignment="True"
helps us a lot to distinguish whether the right side really occupies space. So we continue the testing on the right-alignment.Now, we modify the 2nd column
Border
to span the 2nd and 3rd columns. The 3rd columnBorder
is placed into the 2nd column. (In other words, our 3rd column does not contain anyBorder
.)See Scenario3 in demo.
The new behavior did not show much surprise to us because we have seen the behavior last section. The 3rd column disappeared, and the 2nd column still lost the 1:2 ratio.
Narrow the window again.
Narrow
the window
again
to
view
the behavior
Why did the tomato
Border
suddenly appear when the window was narrowing? Why is there a blank space on the right side of the tomatoBorder
?If we have realized in the last post section that the right-side space is lost when it is right-aligned, why does the white space appear suddenly in the right-side again?
I tried to slightly increase the width of the second
Border
. Suddenly, I reproduced the strange behavior that I reproduced just now when resizing the window!The Proportion of Auto Size
Now, abandon the previous right-aligned test method and no longer use the
*
width to separate theGrid
. We useAuto
instead.See Scenario4 in demo.
Specifically, we have four
Border
, placed in three columns ofAuto
size. The firstBorder
spans three columns, and its size is longer than all the others, reaching 159. The remaining threeBorder
each occupy a column, with two sides of equal length and a slightly longer middle.How are the columns in the actual layout divided? Here is the column width that the designer shows for us:
Where do
46
,69
,46
come from? Could it be that the proportion of46:69
is the same as that of28:51
? However, the actual calculation result is not!What if this is a calculation error?
So let’s look at the other two sets of values for the three
Border
:50:50:50
and25:50:25
.▲
50:50:50
▲
25:50:25
In
50:50:50
, we eventually get the 1:1:1 proportion. But the ratio of column widths in25:50:25
is far from1:2:1
. That is, in fact, theGrid
does not calculate the column widths by the proportion to the size of the element.The same Element Size but Different Column Width
In the experiment in the previous section, we notice that the same size brought about the same final visible size regardless of the proportion. However, this conclusion still can be subverted.
Now, we will replace 3 columns with 4 columns, and the number of
Border
will be replaced with 6.See Scenario5 in demo.
Specifically, the first
Border
spans the first three columns, and the secondBorder
spans the last three columns, the same length as the longBorder
of the previous section. The third and sixthBorder
s are on two sides and are as short as the previousBorder
. The middle twoBorder
s are as long as the previousBorder
. Just like the picture that is shown below.What is the width of the columns laid out at this time?
▲ 32:65:65:39
Wait! Where did the 39 come from? If the equal-size
Border
in the previous section would get equal-sized column widths, then this will also subvert! In fact, even if the proportion of the column width to the proportion of elements is the same at this time, there are as infinitely as many solutions under this layout. WPF picks only one out of this infinite number of solutions - and it cannot explain itself!The conclusion of the Grid undefined behavior
In summary, the
Grid
layout has some unreasonable behaviors under special circumstances. I call them “the undefined behaviors”. These undefined behaviors are summarized in the following three points:*
unit column/row with multiple-span elementsHowever, you may think that I use the
Grid
in incorrect ways. However, as an API that exposes the behaviors, the behavior itself is also a part of the API. It should have clear traceable and documentable behavior instead of being explored and guess and failed by the user.Microsoft does not have any official documents that disclose these bizarre behaviors, and I have not found such behavior in any third-party references (this post is my own conclusion). I think that Microsoft did not publish this kind of documents because the behaviors are too bizarre to be documented!
I hope we can define this behaviors.
See The undefined behaviors of WPF Grid (the so-called bugs) - walterlv
IMHO a lot of the feature presented above to improve Grid, it really sounds like what you’re looking for is RelativePanel.
I do agree ColumnDefinitions should have a simple syntax, and that could easily be achieved with a string converter. Ie
<Grid ColumnDefinitions="1*,200,Auto,2*" />