面板概述

Panel 元素是控制元素渲染的组件 - 其大小和尺寸,它们的位置以及其子内容的排列。Avalonia 提供了许多预定义的 Panel 元素以及构建自定义 Panel 元素的能力。

Panel 类

Panel 是 Avalonia 中提供布局支持的所有元素的基类。派生的 Panel 元素用于在 XAML 和代码中定位和排列元素。

Avalonia 包括一套全面的派生面板实现,使得许多复杂的布局成为可能。这些派生类公开了属性和方法,使得大多数标准 UI 场景成为可能。无法找到满足其需求的子元素排列行为的开发人员可以通过重写 ArrangeOverrideMeasureOverride 方法来创建新的布局。有关自定义布局行为的更多信息,请参见 创建自定义面板。

面板公共成员

所有 Panel 元素都支持 Control 定义的基本大小和定位属性,包括 HeightWidthHorizontalAlignmentVerticalAlignmentMargin。有关 Control 定义的定位属性的其他信息,请参见 对齐、边距和填充概述。

Panel 公开了其他一些属性,在理解和使用布局时非常重要。Background 属性用于用 Brush 填充派生面板元素边界之间的区域。Children 表示 Panel 由哪些子元素组成的集合。

附加属性

派生面板元素广泛使用附加属性。附加属性是一种特殊形式的依赖属性,不具有常规的公共语言运行时(CLR)属性“包装程序”。附加属性在 XAML 中有一种专门的语法,可以在以下示例中看到。

附加属性的一个目的是允许子元素存储由父元素实际定义的属性的唯一值。此功能的一个应用是使子元素通知父元素它们希望在 UI 中如何呈现,这对于应用程序布局非常有用。

用户界面面板

在 Avalonia 中有几个面板类专门针对 UI 场景进行了优化:PanelCanvasDockPanelGridStackPanelWrapPanelRelativePanel。这些面板元素易于使用,功能丰富,对于大多数应用程序来说足够灵活和可扩展。

Canvas

Canvas 元素允许根据绝对 xy 坐标定位内容。元素可以在独特位置上绘制;或者,如果元素占用相同的坐标,则它们在标记中出现的顺序确定它们绘制的顺序。

Canvas 提供了任何 Panel 中最灵活的布局支持。HeightWidth 属性用于定义 Canvas 的区域,其中内部的元素被赋予相对于父 Canvas 区域的绝对坐标。四个附加属性,Canvas.LeftCanvas.TopCanvas.RightCanvas.Bottom,允许在 Canvas 中精细控制对象的位置,使开发人员可以精确地在屏幕上定位和排列元素。

Canvas 中的 ClipToBounds

Canvas 可以将子元素定位在屏幕上的任何位置,甚至可以在其定义的 HeightWidth 之外的坐标上。此外,Canvas 不受其子元素的大小影响。因此,子元素可能会在父 Canvas 的边界矩形之外超出其他元素。Canvas 的默认行为是允许子元素绘制超出父 Canvas 的边界。如果不希望出现这种行为,则可以将 ClipToBounds 属性设置为 true。这会使 Canvas 剪切到其自身的大小。Canvas 是唯一允许子元素在其边界之外绘制的布局元素。

定义和使用 Canvas

可以通过 XAML 或代码简单地实例化 Canvas。以下示例演示如何使用 Canvas 绝对定位内容。这段代码生成了三个 100 像素正方形。第一个正方形是红色的,它的左上角 (x, y) 位置指定为 (0, 0)。第二个正方形是绿色的,它的左上角位置是 (100, 100),刚好在第一个正方形的下方和右侧。第三个正方形是蓝色的,它的左上角位置是 (50, 50),因此包含了第一个正方形的右下象限和第二个正方形的左上象限。因为第三个正方形是最后一个被布局的,所以它看起来在其他两个正方形的上面——也就是说,重叠的部分采用第三个正方形的颜色。

C#

// Create the Canvas
myParentCanvas = new Canvas();
myParentCanvas.Width = 400;
myParentCanvas.Height = 400;

// Define child Canvas elements
myCanvas1 = new Canvas();
myCanvas1.Background = Brushes.Red;
myCanvas1.Height = 100;
myCanvas1.Width = 100;
Canvas.SetTop(myCanvas1, 0);
Canvas.SetLeft(myCanvas1, 0);

myCanvas2 = new Canvas();
myCanvas2.Background = Brushes.Green;
myCanvas2.Height = 100;
myCanvas2.Width = 100;
Canvas.SetTop(myCanvas2, 100);
Canvas.SetLeft(myCanvas2, 100);

myCanvas3 = new Canvas();
myCanvas3.Background = Brushes.Blue;
myCanvas3.Height = 100;
myCanvas3.Width = 100;
Canvas.SetTop(myCanvas3, 50);
Canvas.SetLeft(myCanvas3, 50);

// Add child elements to the Canvas' Children collection
myParentCanvas.Children.Add(myCanvas1);
myParentCanvas.Children.Add(myCanvas2);
myParentCanvas.Children.Add(myCanvas3);

XAML

<Canvas Height="400" Width="400">
  <Canvas Height="100" Width="100" Top="0" Left="0" Background="Red"/>
  <Canvas Height="100" Width="100" Top="100" Left="100" Background="Green"/>
  <Canvas Height="100" Width="100" Top="50" Left="50" Background="Blue"/>
</Canvas>

DockPanel(停靠面板)

DockPanel 元素使用子内容元素中设置的 DockPanel.Dock 附加属性来沿容器的边缘定位内容。当 DockPanel.Dock 设置为 TopBottom 时,它会将子元素放置在彼此的上方或下方。当 DockPanel.Dock 设置为 LeftRight 时,它会将子元素放置在彼此的左侧或右侧。LastChildFill 属性确定了在 DockPanel 的最后一个添加的子元素的位置。

您可以使用 DockPanel 来定位一组相关的控件,例如一组按钮。或者,您可以使用它来创建一个 "分隔窗格" 用户界面。

按内容大小调整大小

如果未指定其 HeightWidth 属性,DockPanel 将根据其内容大小进行调整。大小可以增加或减小以适应其子元素的大小。但是,当指定了这些属性并且没有足够的空间容纳下一个指定的子元素时,DockPanel 不会显示该子元素或后续子元素,并且不会测量后续子元素。

LastChildFill

默认情况下,DockPanel 元素的最后一个子元素将"填充"剩余未分配的空间。如果不希望出现这种行为,将 LastChildFill 属性设置为 false

定义和使用 DockPanel

以下示例演示如何使用 DockPanel 分割空间。五个 Border 元素作为父 DockPanel 的子级添加。每个元素使用 DockPanel 的不同定位属性来分隔空间。最后一个元素 "填充" 剩余未分配的空间。

C#

// Create the DockPanel
DockPanel myDockPanel = new DockPanel();
myDockPanel.LastChildFill = true;

// Define the child content
Border myBorder1 = new Border();
myBorder1.Height = 25;
myBorder1.Background = Brushes.SkyBlue;
myBorder1.BorderBrush = Brushes.Black;
myBorder1.BorderThickness = new Thickness(1);
DockPanel.SetDock(myBorder1, Dock.Top);
TextBlock myTextBlock1 = new TextBlock();
myTextBlock1.Foreground = Brushes.Black;
myTextBlock1.Text = "Dock = Top";
myBorder1.Child = myTextBlock1;

Border myBorder2 = new Border();
myBorder2.Height = 25;
myBorder2.Background = Brushes.SkyBlue;
myBorder2.BorderBrush = Brushes.Black;
myBorder2.BorderThickness = new Thickness(1);
DockPanel.SetDock(myBorder2, Dock.Top);
TextBlock myTextBlock2 = new TextBlock();
myTextBlock2.Foreground = Brushes.Black;
myTextBlock2.Text = "Dock = Top";
myBorder2.Child = myTextBlock2;

Border myBorder3 = new Border();
myBorder3.Height = 25;
myBorder3.Background = Brushes.LemonChiffon;
myBorder3.BorderBrush = Brushes.Black;
myBorder3.BorderThickness = new Thickness(1);
DockPanel.SetDock(myBorder3, Dock.Bottom);
TextBlock myTextBlock3 = new TextBlock();
myTextBlock3.Foreground = Brushes.Black;
myTextBlock3.Text = "Dock = Bottom";
myBorder3.Child = myTextBlock3;

Border myBorder4 = new Border();
myBorder4.Width = 200;
myBorder4.Background = Brushes.PaleGreen;
myBorder4.BorderBrush = Brushes.Black;
myBorder4.BorderThickness = new Thickness(1);
DockPanel.SetDock(myBorder4, Dock.Left);
TextBlock myTextBlock4 = new TextBlock();
myTextBlock4.Foreground = Brushes.Black;
myTextBlock4.Text = "Dock = Left";
myBorder4.Child = myTextBlock4;

Border myBorder5 = new Border();
myBorder5.Background = Brushes.White;
myBorder5.BorderBrush = Brushes.Black;
myBorder5.BorderThickness = new Thickness(1);
TextBlock myTextBlock5 = new TextBlock();
myTextBlock5.Foreground = Brushes.Black;
myTextBlock5.Text = "This content will Fill the remaining space";
myBorder5.Child = myTextBlock5;

// Add child elements to the DockPanel Children collection
myDockPanel.Children.Add(myBorder1);
myDockPanel.Children.Add(myBorder2);
myDockPanel.Children.Add(myBorder3);
myDockPanel.Children.Add(myBorder4);
myDockPanel.Children.Add(myBorder5);

XAML

<DockPanel LastChildFill="True">
  <Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top">
    <TextBlock Foreground="Black">Dock = "Top"</TextBlock>
  </Border>
  <Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top">
    <TextBlock Foreground="Black">Dock = "Top"</TextBlock>
  </Border>
  <Border Height="25" Background="LemonChiffon" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Bottom">
    <TextBlock Foreground="Black">Dock = "Bottom"</TextBlock>
  </Border>
  <Border Width="200" Background="PaleGreen" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Left">
    <TextBlock Foreground="Black">Dock = "Left"</TextBlock>
  </Border>
  <Border Background="White" BorderBrush="Black" BorderThickness="1">
    <TextBlock Foreground="Black">This content will "Fill" the remaining space</TextBlock>
  </Border>
</DockPanel>

网格(Grid)

Grid元素将绝对定位和表格数据控件的功能合并在一起,允许您轻松地定位和设置元素。 Grid允许您定义灵活的行和列分组,并提供了在多个Grid元素之间共享大小信息的机制。

列和行的尺寸行为

Grid内定义的列和行可以利用Star大小来按比例分配剩余空间。当行或列的高度或宽度被设置为Star时,该列或行将按比例获得剩余的可用空间。这与Auto不同,后者将根据列或行中内容的大小均匀分配空间。在使用XAML时,这个值用*2*来表示。在第一种情况下,行或列将获得一倍的可用空间,在第二种情况下,将获得两倍的空间,以此类推。通过将此技术与HorizontalAlignmentVerticalAlignment值设置为Stretch相结合,可以按百分比将布局空间分配为屏幕空间。Grid是唯一可以以这种方式分配空间的布局面板。

定义和使用网格

以下示例演示了如何构建类似于Windows开始菜单上的运行对话框中发现的UI。

C#

// Create the Grid.
grid1 = new Grid ();
grid1.Background = Brushes.Gainsboro;
grid1.HorizontalAlignment = HorizontalAlignment.Left;
grid1.VerticalAlignment = VerticalAlignment.Top;
grid1.ShowGridLines = true;
grid1.Width = 425;
grid1.Height = 165;

// Define the Columns.
colDef1 = new ColumnDefinition();
colDef1.Width = new GridLength(1, GridUnitType.Auto);
colDef2 = new ColumnDefinition();
colDef2.Width = new GridLength(1, GridUnitType.Star);
colDef3 = new ColumnDefinition();
colDef3.Width = new GridLength(1, GridUnitType.Star);
colDef4 = new ColumnDefinition();
colDef4.Width = new GridLength(1, GridUnitType.Star);
colDef5 = new ColumnDefinition();
colDef5.Width = new GridLength(1, GridUnitType.Star);
grid1.ColumnDefinitions.Add(colDef1);
grid1.ColumnDefinitions.Add(colDef2);
grid1.ColumnDefinitions.Add(colDef3);
grid1.ColumnDefinitions.Add(colDef4);
grid1.ColumnDefinitions.Add(colDef5);

// Define the Rows.
rowDef1 = new RowDefinition();
rowDef1.Height = new GridLength(1, GridUnitType.Auto);
rowDef2 = new RowDefinition();
rowDef2.Height = new GridLength(1, GridUnitType.Auto);
rowDef3 = new RowDefinition();
rowDef3.Height = new GridLength(1, GridUnitType.Star);
rowDef4 = new RowDefinition();
rowDef4.Height = new GridLength(1, GridUnitType.Auto);
grid1.RowDefinitions.Add(rowDef1);
grid1.RowDefinitions.Add(rowDef2);
grid1.RowDefinitions.Add(rowDef3);
grid1.RowDefinitions.Add(rowDef4);

// Add the Image.
img1 = new Image();
img1.Source = runicon;
Grid.SetRow(img1, 0);
Grid.SetColumn(img1, 0);

// Add the main application dialog.
txt1 = new TextBlock();
txt1.Text = "Type the name of a program, folder, document, or Internet resource, and Windows will open it for you.";
txt1.TextWrapping = TextWrapping.Wrap;
Grid.SetColumnSpan(txt1, 4);
Grid.SetRow(txt1, 0);
Grid.SetColumn(txt1, 1);

// Add the second text cell to the Grid.
txt2 = new TextBlock();
txt2.Text = "Open:";
Grid.SetRow(txt2, 1);
Grid.SetColumn(txt2, 0);

// Add the TextBox control.
tb1 = new TextBox();
Grid.SetRow(tb1, 1);
Grid.SetColumn(tb1, 1);
Grid.SetColumnSpan(tb1, 5);

// Add the buttons.
button1 = new Button();
button2 = new Button();
button3 = new Button();
button1.Content = "OK";
button2.Content = "Cancel";
button3.Content = "Browse ...";
Grid.SetRow(button1, 3);
Grid.SetColumn(button1, 2);
button1.Margin = new Thickness(10, 0, 10, 15);
button2.Margin = new Thickness(10, 0, 10, 15);
button3.Margin = new Thickness(10, 0, 10, 15);
Grid.SetRow(button2, 3);
Grid.SetColumn(button2, 3);
Grid.SetRow(button3, 3);
Grid.SetColumn(button3, 4);

grid1.Children.Add(img1);
grid1.Children.Add(txt1);
grid1.Children.Add(txt2);
grid1.Children.Add(tb1);
grid1.Children.Add(button1);
grid1.Children.Add(button2);
grid1.Children.Add(button3);

StackPanel

StackPanel(堆栈面板)可以沿着指定的方向"堆叠"元素。默认的堆叠方向是垂直的。可以使用Orientation属性来控制内容流向。

StackPanel vs. DockPanel

尽管DockPanel也可以"堆叠"子元素,但在某些用法场景下,DockPanelStackPanel的结果并不相似。例如,在DockPanel中,子元素的顺序会影响它们的大小,但在StackPanel中却不会。这是因为StackPanel在堆叠方向上测量为PositiveInfinity,而DockPanel只测量可用的大小。

定义和使用 StackPanel

以下示例演示了如何使用StackPanel创建一组垂直排列的按钮。要进行水平定位,请将Orientation属性设置为Horizontal

C#

// Define the StackPanel
myStackPanel = new StackPanel();
myStackPanel.HorizontalAlignment = HorizontalAlignment.Left;
myStackPanel.VerticalAlignment = VerticalAlignment.Top;

// Define child content
Button myButton1 = new Button();
myButton1.Content = "Button 1";
Button myButton2 = new Button();
myButton2.Content = "Button 2";
Button myButton3 = new Button();
myButton3.Content = "Button 3";

// Add child elements to the parent StackPanel
myStackPanel.Children.Add(myButton1);
myStackPanel.Children.Add(myButton2);
myStackPanel.Children.Add(myButton3);

WrapPanel

WrapPanel 用于将子元素从左到右依次排列,并在达到父容器边缘时将内容换行。内容可以水平或垂直定向。WrapPanel 对于简单的流式 UI 场景非常有用。它还可以用于将统一的大小应用于其所有子元素。

下面的示例演示了如何创建一个 WrapPanel 来显示 Button 控件,当它们到达容器边缘时进行换行。

C#

// 实例化一个新的 WrapPanel 并设置属性
myWrapPanel = new WrapPanel();
myWrapPanel.Background = System.Windows.Media.Brushes.Azure;
myWrapPanel.Orientation = Orientation.Horizontal;
myWrapPanel.Width = 200;
myWrapPanel.HorizontalAlignment = HorizontalAlignment.Left;
myWrapPanel.VerticalAlignment = VerticalAlignment.Top;

// 定义 3 个按钮元素。最后三个按钮的宽度为 75,因此第四个按钮换行到下一行。
btn1 = new Button();
btn1.Content = "Button 1";
btn1.Width = 200;
btn2 = new Button();
btn2.Content = "Button 2";
btn2.Width = 75;
btn3 = new Button();
btn3.Content = "Button 3";
btn3.Width = 75;
btn4 = new Button();
btn4.Content = "Button 4";
btn4.Width = 75;

// 使用 Children.Add 方法将按钮添加到父 WrapPanel 中。
myWrapPanel.Children.Add(btn1);
myWrapPanel.Children.Add(btn2);
myWrapPanel.Children.Add(btn3);
myWrapPanel.Children.Add(btn4);

XAML

<Border HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="2">
  <WrapPanel Background="LightBlue" Width="200" Height="100">
    <Button Width="200">Button 1</Button>
    <Button>Button 2</Button>
    <Button>Button 3</Button>
    <Button>Button 4</Button>
  </WrapPanel>
</Border>

嵌套面板元素

Panel 元素可以嵌套在彼此中,以产生复杂的布局。这在某些情况下非常有用,其中一个 Panel 对于 UI 的一部分是理想的,但可能无法满足 UI 的不同部分的需求。

您的应用程序支持嵌套的数量没有实际限制,但通常最好限制您的应用程序仅使用实际需要的那些面板来实现所需的布局。在许多情况下,Grid 元素可以代替嵌套面板,因为它作为布局容器非常灵活。通过使不必要的元素离开树,这可以通过保持性能来提高应用程序的性能。

Last updated