.NET Multi-platform App UI (.NET MAUI) Grid 是一种布局,它将其子级组织为可以有比例大小或绝对大小的行和列。 默认情况下,Grid 包含一行和一列。 此外,还可以将 Grid 用作包含其他子布局的父布局。
不应将 Grid 与表混淆,它并不用于显示表格数据。 与 HTML 表不同,Grid 用于布局内容。 要显示表格数据,请考虑使用 ListView 或 CollectionView。
Grid 类定义以下属性:
Column
,类型为int
,是一个附加属性,用于指示父 Grid 中视图的列对齐方式。 此属性的默认值为 0。 验证回调可确保设置属性时,其值大于或等于 0。ColumnDefinitions
,类型为ColumnDefinitionCollection
,是定义网格列宽度的ColumnDefinition
对象列表。ColumnSpacing
,类型为double
,指示网格列之间的距离。 此属性的默认值为 0。ColumnSpan
,类型为int
,是一个附加属性,用于指示视图在父级 Grid 中跨越的总列数。 此属性的默认值为 1。 验证回调可确保设置属性时,其值大于或等于 1。Row
,类型为int
,是一个附加属性,用于指示父级 Grid 中视图的行对齐方式。 此属性的默认值为 0。 验证回调可确保设置属性时,其值大于或等于 0。RowDefinitions
,类型为RowDefinitionCollection
,是定义网格行高度的RowDefinition
对象列表。RowSpacing
,类型为double
,指示网格行之间的距离。 此属性的默认值为 0。RowSpan
,类型为int
,是一个附加属性,用于指示视图在父级 Grid 中跨越的总行数。 此属性的默认值为 1。 验证回调可确保设置属性时,其值大于或等于 1。
所有这些属性都由 BindableProperty 对象提供支持,这意味着这些属性可以作为数据绑定的目标并设置样式。
行和列
默认情况下,Grid 包含一行和一列:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridTutorial.MainPage">
<Grid Margin="20,35,20,20">
<Label Text="By default, a Grid contains one row and one column." />
</Grid>
</ContentPage>
在此示例中, Grid 包含一个自动定位在单个位置的子级 Label:
可以使用 RowDefinitions
和 ColumnDefinitions
属性定义 Grid 的布局行为,这两个属性分别是 RowDefinition
和 ColumnDefinition
对象的集合。 这些集合定义 Grid 的行和列特征,并且应针对 Grid 中的每一行包含一个 RowDefinition
对象,以及针对 Grid 中的每一列包含一个 ColumnDefinition
对象。
RowDefinition
类定义类型为 GridLength
的 Height
属性,而 ColumnDefinition
类定义类型为 GridLength
的 Width
属性。 GridLength
结构根据 GridUnitType
枚举指定行高或列宽,其中有三个成员:
Absolute
– 行高或列宽的值采用与设备无关的单位(XAML 中的数字)。Auto
– 根据单元格内容自动调整行高或列宽(XAML 中的Auto
)。Star
– 按比例分配剩余行高或列宽(在 XAML 中,数字后跟*
)。
Height
属性为 Auto
的 Grid 行约束该行中视图高度的方式与垂直 StackLayout 相同。 同样,Width
属性为 Auto
的列的工作方式与水平 StackLayout 非常相似。
注意
尽量确保将尽可能少的行和列设置为 Auto
大小。 每个自动调整大小的行或列都会导致布局引擎执行额外布局计算。 而是应在可能时使用固定大小的行和列。 或者,将行和列设置为使用 GridUnitType.Star
枚举值按比例占用空间量。
以下 XAML 演示如何创建包含三行和两列的 Grid:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.BasicGridPage"
Title="Basic Grid demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
在此示例中,Grid 的总高度等于页面的高度。 Grid 知道第三行的高度为 100 个与设备无关的单位。 它从自身高度中减去该高度,并根据星号前面的数字在第一行和第二行之间按比例分配剩余高度。 在此示例中,第一行的高度是第二行高度的两倍。
两个 ColumnDefinition
对象都跟 1*
一样将 Width
设置为 *
,这意味着屏幕的宽度在两列下方相等。
重要
RowDefinition.Height
属性的默认值为 *
。 同样,ColumnDefinition.Width
属性的默认值为 *
。 因此,在可以接受这些默认值的情况下,无需设置这些属性。
可以将子视图放置在包含 Grid.Column
和 Grid.Row
附加属性的特定 Grid 单元格中。 此外,要使子视图跨越多个行和列,请使用 Grid.RowSpan
和 Grid.ColumnSpan
附加属性。
以下 XAML 展示了相同的 Grid 定义,并将子视图定位在特定的 Grid 单元格中:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.BasicGridPage"
Title="Basic Grid demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<BoxView Color="Green" />
<Label Text="Row 0, Column 0"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Column="1"
Color="Blue" />
<Label Grid.Column="1"
Text="Row 0, Column 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Color="Teal" />
<Label Grid.Row="1"
Text="Row 1, Column 0"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="Purple" />
<Label Grid.Row="1"
Grid.Column="1"
Text="Row1, Column 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Grid.ColumnSpan="2"
Color="Red" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="Row 2, Columns 0 and 1"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</ContentPage>
注意
Grid.Row
和 Grid.Column
属性的索引值均从 0 开始,因此 Grid.Row="2"
指代的是第三行,而 Grid.Column="1"
指代的是第二列。 此外,这两个属性的默认值均为 0,因此无需在占用 Grid 的第一行或第一列的子视图上设置。
在此示例中,所有三个 Grid 行都被 BoxView 和 Label 视图占用。 第三行的高度为 100 个设备无关单位,前两行占据剩余空间(第一行的高度是第二行的两倍)。 两列的宽度相等,并将 Grid 分成两半。 第三行中的 BoxView 跨两列:
此外,Grid 中的子视图还可以共享单元格。 子级在 XAML 中显示的顺序是在 Grid 中放置子级的顺序。 在前面的示例中,Label 对象之所以仅可见,是因为它们呈现在 BoxView 对象之上。 如果 BoxView 对象呈现在 Label 对象之上,则它们将不可见。
等效 C# 代码如下:
public class BasicGridPage : ContentPage
{
public BasicGridPage()
{
Grid grid = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(2, GridUnitType.Star) },
new RowDefinition(),
new RowDefinition { Height = new GridLength(100) }
},
ColumnDefinitions =
{
new ColumnDefinition(),
new ColumnDefinition()
}
};
// Row 0
// The BoxView and Label are in row 0 and column 0, and so only need to be added to the
// Grid to obtain the default row and column settings.
grid.Add(new BoxView
{
Color = Colors.Green
});
grid.Add(new Label
{
Text = "Row 0, Column 0",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
});
// This BoxView and Label are in row 0 and column 1, which are specified as arguments
// to the Add method.
grid.Add(new BoxView
{
Color = Colors.Blue
}, 1, 0);
grid.Add(new Label
{
Text = "Row 0, Column 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 0);
// Row 1
// This BoxView and Label are in row 1 and column 0, which are specified as arguments
// to the Add method overload.
grid.Add(new BoxView
{
Color = Colors.Teal
}, 0, 1);
grid.Add(new Label
{
Text = "Row 1, Column 0",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 0, 1);
// This BoxView and Label are in row 1 and column 1, which are specified as arguments
// to the Add method overload.
grid.Add(new BoxView
{
Color = Colors.Purple
}, 1, 1);
grid.Add(new Label
{
Text = "Row1, Column 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 1);
// Row 2
// Alternatively, the BoxView and Label can be positioned in cells with the Grid.SetRow
// and Grid.SetColumn methods. Here, the Grid.SetColumnSpan method is used to span two columns.
BoxView boxView = new BoxView { Color = Colors.Red };
Grid.SetRow(boxView, 2);
Grid.SetColumnSpan(boxView, 2);
Label label = new Label
{
Text = "Row 2, Column 0 and 1",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
Grid.SetRow(label, 2);
Grid.SetColumnSpan(label, 2);
grid.Add(boxView);
grid.Add(label);
Title = "Basic Grid demo";
Content = grid;
}
}
在代码中,要指定 RowDefinition
对象的高度和 ColumnDefinition
对象的宽度,请使用 GridLength
结构的值,通常与 GridUnitType
枚举组合使用。
注意
Grid 还可定义 AddWithSpan 扩展方法,该方法将视图添加到具有指定行和列跨度的指定行和列处的 Grid
。
简化行和列定义
在 XAML 中,可以使用简化的语法指定 Grid 的行和列特征,这样就不必为每一行和每一列定义 RowDefinition
和 ColumnDefinition
对象。 实际上,可以将 RowDefinitions
和 ColumnDefinitions
属性设置为包含以逗号分隔的 GridUnitType
值的字符串,以便内置于 .NET MAUI 中的类型转换器从其创建 RowDefinition
和 ColumnDefinition
对象:
<Grid RowDefinitions="1*, Auto, 25, 14, 20"
ColumnDefinitions="*, 2*, Auto, 300">
...
</Grid>
在此示例中,Grid 有五行和四列。 第三行、第四行和第五行被设置为绝对高度,第二行根据其内容自动调整大小。 然后将剩余高度分配给第一行。
第四列被设置为绝对宽度,第三列根据其内容自动调整大小。 剩余宽度根据星号前的数字在第一列和第二列之间按比例分配。 在此示例中,第二列的宽度是第一列的两倍(因为 *
与 1*
相同)。
行和列之间的间距
默认情况下,Grid 行和列之间没有空格。 这可以通过分别设置 RowSpacing
和 ColumnSpacing
属性来更改:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.GridSpacingPage"
Title="Grid spacing demo">
<Grid RowSpacing="6"
ColumnSpacing="6">
...
</Grid>
</ContentPage>
此示例创建了 Grid,其行和列由大小为 6 个设备无关单位的空间分隔:
提示
可以将 RowSpacing
和 ColumnSpacing
属性设置为负值,以使单元格内容重叠。
等效 C# 代码如下:
public class GridSpacingPage : ContentPage
{
public GridSpacingPage()
{
Grid grid = new Grid
{
RowSpacing = 6,
ColumnSpacing = 6,
...
};
...
Content = grid;
}
}
保持同步
可以通过 HorizontalOptions
和 VerticalOptions
属性将 Grid 中的子视图定位在其单元格中。 可以将这些属性设置为 LayoutOptions
结构中的以下字段:
Start
Center
End
Fill
以下 XAML 创建了包含九个同等大小单元格的 Grid,并在每个单元格中放置具有不同对齐方式的 Label:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.GridAlignmentPage"
Title="Grid alignment demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<BoxView Color="AliceBlue" />
<Label Text="Upper left"
HorizontalOptions="Start"
VerticalOptions="Start" />
<BoxView Grid.Column="1"
Color="LightSkyBlue" />
<Label Grid.Column="1"
Text="Upper center"
HorizontalOptions="Center"
VerticalOptions="Start"/>
<BoxView Grid.Column="2"
Color="CadetBlue" />
<Label Grid.Column="2"
Text="Upper right"
HorizontalOptions="End"
VerticalOptions="Start" />
<BoxView Grid.Row="1"
Color="CornflowerBlue" />
<Label Grid.Row="1"
Text="Center left"
HorizontalOptions="Start"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="DodgerBlue" />
<Label Grid.Row="1"
Grid.Column="1"
Text="Center center"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="2"
Color="DarkSlateBlue" />
<Label Grid.Row="1"
Grid.Column="2"
Text="Center right"
HorizontalOptions="End"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Color="SteelBlue" />
<Label Grid.Row="2"
Text="Lower left"
HorizontalOptions="Start"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="1"
Color="LightBlue" />
<Label Grid.Row="2"
Grid.Column="1"
Text="Lower center"
HorizontalOptions="Center"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="2"
Color="BlueViolet" />
<Label Grid.Row="2"
Grid.Column="2"
Text="Lower right"
HorizontalOptions="End"
VerticalOptions="End" />
</Grid>
</ContentPage>
在此示例中,每行中的 Label 对象在垂直方向上的对齐方式完全相同,但使用不同的水平对齐方式。 或者,可以将其视为每列中的 Label 对象在水平方向上的对齐方式完全相同,但使用不同的垂直对齐方式:
等效 C# 代码如下:
public class GridAlignmentPage : ContentPage
{
public GridAlignmentPage()
{
Grid grid = new Grid
{
RowDefinitions =
{
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
},
ColumnDefinitions =
{
new ColumnDefinition(),
new ColumnDefinition(),
new ColumnDefinition()
}
};
// Row 0
grid.Add(new BoxView
{
Color = Colors.AliceBlue
});
grid.Add(new Label
{
Text = "Upper left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
});
grid.Add(new BoxView
{
Color = Colors.LightSkyBlue
}, 1, 0);
grid.Add(new Label
{
Text = "Upper center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Start
}, 1, 0);
grid.Add(new BoxView
{
Color = Colors.CadetBlue
}, 2, 0);
grid.Add(new Label
{
Text = "Upper right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Start
}, 2, 0);
// Row 1
grid.Add(new BoxView
{
Color = Colors.CornflowerBlue
}, 0, 1);
grid.Add(new Label
{
Text = "Center left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center
}, 0, 1);
grid.Add(new BoxView
{
Color = Colors.DodgerBlue
}, 1, 1);
grid.Add(new Label
{
Text = "Center center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 1);
grid.Add(new BoxView
{
Color = Colors.DarkSlateBlue
}, 2, 1);
grid.Add(new Label
{
Text = "Center right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
}, 2, 1);
// Row 2
grid.Add(new BoxView
{
Color = Colors.SteelBlue
}, 0, 2);
grid.Add(new Label
{
Text = "Lower left",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.End
}, 0, 2);
grid.Add(new BoxView
{
Color = Colors.LightBlue
}, 1, 2);
grid.Add(new Label
{
Text = "Lower center",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.End
}, 1, 2);
grid.Add(new BoxView
{
Color = Colors.BlueViolet
}, 2, 2);
grid.Add(new Label
{
Text = "Lower right",
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.End
}, 2, 2);
Title = "Grid alignment demo";
Content = grid;
}
}
嵌套网格对象
Grid 可用作包含嵌套子 Grid 对象或其他子布局的父布局。 嵌套 Grid 对象时,Grid.Row
、Grid.Column
、Grid.RowSpan
和 Grid.ColumnSpan
附加属性始终引用视图在其父 Grid 中的位置。
以下 XAML 展示了一个嵌套 Grid 对象的示例:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:GridDemos.Converters"
x:Class="GridDemos.Views.XAML.ColorSlidersGridPage"
Title="Nested Grids demo">
<ContentPage.Resources>
<converters:DoubleToIntConverter x:Key="doubleToInt" />
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment"
Value="Center" />
</Style>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="500" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<BoxView x:Name="boxView"
Color="Black" />
<Grid Grid.Row="1"
Margin="20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Slider x:Name="redSlider"
ValueChanged="OnSliderValueChanged" />
<Label x:DataType="Slider"
Grid.Row="1"
Text="{Binding Source={x:Reference redSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0}'}" />
<Slider x:Name="greenSlider"
Grid.Row="2"
ValueChanged="OnSliderValueChanged" />
<Label x:DataType="Slider"
Grid.Row="3"
Text="{Binding Source={x:Reference greenSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Green = {0}'}" />
<Slider x:Name="blueSlider"
Grid.Row="4"
ValueChanged="OnSliderValueChanged" />
<Label x:DataType="Slider"
Grid.Row="5"
Text="{Binding Source={x:Reference blueSlider},
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Blue = {0}'}" />
</Grid>
</Grid>
</ContentPage>
在此示例中,根 Grid 的第一行包含一个 BoxView,第二行包含一个子 Grid。 子 Grid 包含用于处理 BoxView 所显示颜色的 Slider 对象,以及显示每个 Slider 值的 Label 对象:
等效 C# 代码如下:
public class ColorSlidersGridPage : ContentPage
{
BoxView boxView;
Slider redSlider;
Slider greenSlider;
Slider blueSlider;
public ColorSlidersGridPage()
{
// Create an implicit style for the Labels
Style labelStyle = new Style(typeof(Label))
{
Setters =
{
new Setter { Property = Label.HorizontalTextAlignmentProperty, Value = TextAlignment.Center }
}
};
Resources.Add(labelStyle);
// Root page layout
Grid rootGrid = new Grid
{
RowDefinitions =
{
new RowDefinition { HeightRequest = 500 },
new RowDefinition()
}
};
boxView = new BoxView { Color = Colors.Black };
rootGrid.Add(boxView);
// Child page layout
Grid childGrid = new Grid
{
Margin = new Thickness(20),
RowDefinitions =
{
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
}
};
DoubleToIntConverter doubleToInt = new DoubleToIntConverter();
redSlider = new Slider();
redSlider.ValueChanged += OnSliderValueChanged;
childGrid.Add(redSlider);
Label redLabel = new Label();
redLabel.SetBinding(Label.TextProperty, Binding.Create(static (Slider slider) => slider.Value, converter: doubleToInt, converterParameter: "255", stringFormat: "Red = {0}", source: redSlider));
Grid.SetRow(redLabel, 1);
childGrid.Add(redLabel);
greenSlider = new Slider();
greenSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(greenSlider, 2);
childGrid.Add(greenSlider);
Label greenLabel = new Label();
greenLabel.SetBinding(Label.TextProperty, Binding.Create(static (Slider slider) => slider.Value, converter: doubleToInt, converterParameter: "255", stringFormat: "Green = {0}", source: greenSlider));
Grid.SetRow(greenLabel, 3);
childGrid.Add(greenLabel);
blueSlider = new Slider();
blueSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(blueSlider, 4);
childGrid.Add(blueSlider);
Label blueLabel = new Label();
blueLabel.SetBinding(Label.TextProperty, Binding.Create(static (Slider slider) => slider.Value, converter: doubleToInt, converterParameter: "255", stringFormat: "Blue = {0}", source: blueSlider));
Grid.SetRow(blueLabel, 5);
childGrid.Add(blueLabel);
// Place the child Grid in the root Grid
rootGrid.Add(childGrid, 0, 1);
Title = "Nested Grids demo";
Content = rootGrid;
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
{
boxView.Color = new Color(redSlider.Value, greenSlider.Value, blueSlider.Value);
}
}