WPF 用不同的容器(container)安排布局。每个容器有各自的布局逻辑——有些容器以堆栈方式布置元素,另一些容器在网格中不可见的单元格中排列元素,等等。在 WPF 中非常抵制基于坐标的布局,而是注重创建更灵活的布局,使布局能够适应内容的变化、不同的语言以及名种窗口尺寸。
3.1 理解WPF中的布局
在 WPF 问世之前,Windows 开发人员使用刻板的基于坐标的布局将控件放到正确位置。在 WPF 中,这种方式虽然可行,但已经极少使用。大多数应用程序将使用类似于 Web 的流(flow)布局;
在使用流布局模型时,控件可以扩大,并将其他控件挤到其他位置,能创建与显示分辨率和窗口大小无关的、在不同的显示器上正确缩放的用户界面;当窗口内容发生变化时,界面可调整自身,并且可以自如地处理语言的切换。
3.1.1 WPF布局原则
WPF 窗口只能包含单个元素。为在 WPF 窗口中放置多个元素并创建更贴近实用的用户界需要在窗口上放置一个容器,然后在这个容器中添加其他元素。造成这一限制的原因是 Window 类继承自 ContentControl 类,在第 6 章中将进一步分析ContentControl类。
WPF布局应该尽可能遵守的原则:
- 不应显式设定元素(如控件)的尺寸。元素应当可以改变尺寸以适合它们的内容。例如,当添加更多的文本时按钮应当能够扩展。可通过设置最大和最小尺寸来限制可以接受的控件尺寸范围。
- 不应使用屏幕坐标指定元素的位置。元素应当由它们的容器根据它们的尺寸、顺序以及(可选的)其他特定于具体布局容器的信息进行排列。如果需要在元素之间添加空白空间,可使用 Margin 属性。以硬编码方式设定尺寸和位置是极其不当的处理方式,因为这会限制本地化界面的能力,并且会使界面更难处理动态内容。
- 布局容器的子元素“共享”可用的空间。如果空间允许,布局容器会根据每个元素的内容尽可能为元素设置更合理的尺寸。它们还会向一个或多个子元素分配多余的空间。
- 可嵌套的布局容器。典型的用户界面使用 Grid 面板作为开始,Grid 面板是 WPF 中功能最强大的容器,Grid 面板可包含其他布局容器,包含的这些容器以更小的分组排列元素,比如带有标题的文本框、列表框中的项、工具栏上的图标以及一列按钮等。
3.1.2 布局过程
WPF 布局包括两个阶段:测量(measure)阶段和排列(arrange)阶段。
在测量阶段,容器遍历所有子元素,并询问子元素它们所期望的尺寸。
在排列阶段,容器在合适的位置放置子元素。
当然,元素未必总能得到最合适的尺寸,有时容器没有足够大的空间以适应所含的元素。在这种情况下,容器为了适应可视化区域的尺寸,就必须剪裁不能满足要求的元素(通常可通过设置最小窗口尺寸来避免这种情况)。
布局容器不能提供任何滚动支持。滚动是由特定的内容控件ScrollViewer提供的,ScrollViewer 控件几乎可用于任何地方。在第6章中将学习 ScrollViewer 控件的相关内容。
3.1.3 布局容器
所有 WPF 布局容器都是派生自 System.Windows.Controls.Panel 抽象类的面板

Panel 类添加了少量成员,包括三个公有属性。
| 名称 | 说明 |
|---|---|
| Background | 该属性是用于为面板背景着色的画刷。如果想接收鼠标事件,就必须将该属性设置为非空值(如果想接收鼠标事件,又不希望显示固定颜色的背景,那么只需要将背景色设置为透明即可)。在第6章中将学习基本面刷的更多内容(并将在第12 章中学习更多高级的画刷) |
| Children | 该属性是在面板中存储的条目集合。这是第一级条目,换句话说,这些条目自身也可以包含更多的条目 |
| IsItemsHost | 该属性是一个布尔值,如果面板用于显示与ItemsControl控件关联的项(例如,TreeView 控件中的节点或列表框中的列表项),该属性值为true。在大多数情况下,甚至不需要知道列表控件使用后台面板来管理它所包含的条目的布局。但如果希望创建自定义的列表,以不同方式放置子元素(例如,以平铺方式显示图像的 ListBox 控件),该细节就变得很重要了。在第 20 章中将使用这种技术 |
Panel 类还包含几个内部属性,如果希望创建自己的容器,就可以使用它们。最特别的是,可重写继承自 FrameworkElement 类的 MeasureOverride( )和 ArangeOverride( )方法,以修改当组织子元素时面板处理测量阶段和排列阶段的方式。第18章将介绍如何创建自定义面板。
就Panel基类本身而言没有什么特别的,但它是其他更多特殊类的起点。WPF 提供了大量可用于安排布局的继承自 Panel 的类。与所有 WPF 控件和大多数可视化元素一样,这些类位于 System.Windows.Controls 名称空间中。
| 名称 | 说明 |
|---|---|
| StackPanel | 在水平或垂直的堆栈中放置元索。这个布局容器通常用于更大、更复杂窗口中的一些小区域 |
| WrapPanel | 在一系列可换行的行中放置元素。在水平方向上,WrapPanel面板从左向右放置条日,然后在随后的行中放置元素。在垂直方向上,WrapPanel面板在自上而下的列中放置元素,并使用附加的列放置剩余的条目 |
| DockPanel | 根据容器的整个边界调整元素 |
| Grid | 根据不可见的表格在行和列中排列元素,这是最灵活、最常用的容器之一 |
| UniformGrid | 在不可见但是强制所有单元格具有相同尺寸的表中放置元素,这个布局容器不常用 |
| Canvas | 使用固定坐标绝对定位元素。这个布局容器与传统 Windows 窗体应用程序最相似,但没有提供锚定或停靠功能。因此,对于尺寸可变的窗口,该布局容器不是合适的选择。如果选择的话,需要另外做一些工作 |
除这些核心容器外,还有几个更专业的面板,在各种控件中都可能遇到它们。这些容器包括专门用于包含特定控件子元素的面板——如 TabPanel 面板(在 TabPanel 面板中包含多个选项卡)、ToolbarPanel 面板(工具栏中的多个按钮)以及 ToolbarOverflowPanel 面板(Toolbar 控件的溢出菜单中的多个命令)。
还有 VirtualizingStackPanel 面板,数据绑定列表控件使用该面板以大幅降低开销;还有InkCanvas 控件,该控件和Canvas 控件类似,但该控件支持处理平板电脑(TabletPC)上的手写笔(stylus)输入(例如,根据选择的模式,InkCanvas 控件支持使用指针绘制范围,以选择屏幕上的元素。也可通过普通计算机和鼠标使用 InkCanvas 控件,尽管这有点违反直觉)。本章将介绍 InkCanvas,第 19 章将详细介绍 VirtualizingStackPanel。
3.2 使用StackPanel面板进行简单布局
StackPanel 面板是最简单的布局容器之一。该面板简单地在单行或单列中以堆栈形式放置其子元素。
默认是水平排列控件(一行一行的),可以通过设置Orientations属性修改排列方式
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<StackPanel Orientation="Horizontal">
<Label>A Button Stack</Label>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
</StackPanel>
</Window>
3.2.1 布局属性
| 名称 | 说明 |
|---|---|
| HorizontalAlignment | 当水平方向上有额外的空间时,该属性决定了子元素在布局容器中如何定位。可选用 Center、Left、Right 或 Stretch 等属性值 |
| VerticalAlignment | 当垂直方向上有额外的空间时,该属性决定了子元素在布局容器控件中如何定位。可选用 Center、Top、Bottom 或 Stretch 等属性值 |
| Margin | 该属性用于在元素的周围添加一定的空间。Margin属性是System.Windows.Thicknecss结构的一个实例,该结构具有分别用于为顶部、底部、左边和右边添加空间的独立组件 |
| MinWidth 和 MinHeight | 这两个属性用于设置元素的最小尺寸。如果一个元素对于其他布局容器来说太大,该元素将被剪裁以适合容器 |
| MaxWidth 和 MaxHeight | 这两个属性用于设置元素的最大尺寸。如果有更多可以使用的空间,那么在扩展子元素时就不会超出这一限制,即使将 HorizontalAlignment和 VerticalAlignmcnt 属性设置为 Stretch 也同样如此 |
| Width 和 Height | 这两个属性用于显式地设置元素的尺寸。这一设置会重写为HorizontalAlignment 和VerticalAlignment 属性设置的 Stretch 值。但不能超出 MinWidth、MinHeight、MaxWidth和 MaxHeight 属性设置的范围 |
所有这些属性都从 FrameworkElement基类继承而来,所以在 WPF 窗口中可使用的所有图形小组件都支持这些属性。
3.2.2 对齐方式
StackPanel 面板也有自己的 HorizontalAlignment 和 VerticalAlignment 属性。这两个属性默认都被设置为 Stretch,所以 StackPanel 面板完全充满它的容器。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<StackPanel>
<Label HorizontalAlignment="Center">A Button Stack</Label>
<Button HorizontalAlignment="Left">Button 1</Button>
<Button HorizontalAlignment="Right">Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
</StackPanel>
</Window>
3.2.3 边距
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<StackPanel>
<Label >A Button Stack</Label>
<Button Margin="15,15,15,15">Button 1</Button>
<Button Margin="3">Button 2</Button>
<Button Margin="3">Button 3</Button>
<Button x:Name="y_button" Margin="3">Button 4</Button>
</StackPanel>
</Window>
y_button.Margin = new Thickness(10, 10, 10, 10);//通过Thickness对象来控制
3.2.4 最小尺寸、最大尺寸以及显式的设置尺寸
每个元素都提供了 Heigbt和 Width 属性,用于显式地指定元素大小。但这种设置一般不是一个好主意。
相反,如有必要,应当使用最大尺寸和最小尺寸属性,将控件限制在正确范围内。
在 WPF 中显式地设置尺寸之前一定要三思。在良好的布局设计中,不必显式地设置尺寸。如果确实添加了尺寸信息,那就冒险创建了一种更不稳定的布局,这种布局不能适应变化(例如不能适应不同的语言和不同的窗口尺寸),而且可能剪裁内容。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<StackPanel Margin="3">
<Label Margin="30" HorizontalAlignment="Center">A Button Stack</Label>
<Button Margin="3" MaxWidth="200" MinWidth="100">Button 1</Button>
<Button Margin="3" MaxWidth="200" MinWidth="100">Button 2</Button>
<Button Margin="3" MaxWidth="200" MinWidth="100">Button 3</Button>
<Button Margin="3" MaxWidth="200" MinWidth="100">Button 4</Button>
</StackPanel>
</Window>
我们可以通过使用样式这种更简便的方法设置那些在多个元素中是标准化的属性,使用样式是一种允许重复使用(甚至自动应用)属性设置的特性。第 11章将介绍样式的相关内容。
当 StackPancl调整按钮的尺寸时,需要考虑以下几部分信息:
- 最小尺寸。每个按钮的尺寸始终不能小于最小尺寸。
- 最大尺寸。每个按钮的尺寸始终不能超过最大尺寸(除非执行错误操作,使最大尺寸比最小尺寸还小)。
- 内容。如果按钮中的内容需要更大的宽度,StackPanel容器会尝试扩展按钮(可以通过检查 Desiredsized 属性确定所需的按钮大小,该属性返回最小宽度或内容的宽度,返回两者中较大的那个)。
- 容器尺寸。如果最小宽度大于 StackPanel 面板的宽度,按钮的一部分将被剪裁掉。否则,不允许按钮比 StackPanel面板更宽,即使不能适合按钮表面的所有文本也同样如此。
- 水平对齐方式。因为默认情况下按钮的 HorizontalAlignment 属性值设置为 Stretch,所以 StackPanel 面板将尝试放大按钮以占满 StackPanel 面板的整个宽度。
某些情况下,可能希望使用代码检查窗口中某个元素的尺寸。这时使用 Height和 Width 属性是没有用的,因为这两个属性指示的是您所期望的尺寸设置,可能和实际的演染尺寸不同。在理想情况下,应让元素的尺寸适应它们的内容,根本不用设置 Height和 Width 属性。但是,可以通过读取 ActualHeight和 ActualWidth 属性得到用于渲染元素的实际尺寸。需要记住的是当窗口大小发生变化或其中的内容改变时,这些值可能会改变。
为使窗口能自动改变大小,需要删除 Height 和Width 属性,并将 Window.SizeToContent 属性设置为WidthAndeight。即Window.SizeToContent="WidthAndHeight"
这时窗口就会扩大自身的尺寸,从而足以容纳包含的所有内容。
通过将SizeToContent 属性设置为 Width或 Height,还可使窗口只能在一个方向上改变自身的尺寸。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Window.SizeToContent="WidthAndHeight"
Title="MainWindow" >
</Window>
3.2.5 Border控件
Border 控件不是布局面板,而是非常便于使用的元素,经常与布局面板一起使用。Border 类只能包含一段嵌套内容(通常是布局面板),并为其添加背景或在其周围添加边框。
| 名称 | 说明 |
|---|---|
| Background | 使用 Brush 对象设置边框中所有内容后面的背景。可使用固定颜色背景,也可使用其他更特殊的背景 |
| BorderBrush 和 BroderThickness | 使用 Brush 对象设置位于 Border 对象边缘的边框的颜色,并设置边框的宽度。为显示边框,必须设置这两个属性 |
| CorerRadius | 该属性可使边框具有雅致的圆角。CornerRadius的值越大,圆角效果就越明显 |
| Padding | 该属性在边框和内部的内容之间添加空间(与此相对,Margin 属性在边框之外添加空间) |
第6章将介绍有关画刷和颜色的详情,它们可用于设置 BorderBrush 和 Background 属性。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Border Margin="5" Padding="5" Background="LightCyan" BorderBrush="SteelBlue" BorderThickness="3,5,3,5" CornerRadius="3">
<StackPanel Margin="3">
<Label Margin="30" HorizontalAlignment="Center" MaxWidth="100" MinWidth="50" >A Button Stack</Label>
<Button Margin="3" MaxWidth="100" MinWidth="50">Button 1</Button>
<Button Margin="3" MaxWidth="100" MinWidth="50">Button 2</Button>
<Button Margin="3" MaxWidth="100" MinWidth="50">Button 3</Button>
<Button Margin="3" MaxWidth="100" MinWidth="50">Button 4</Button>
</StackPanel>
</Border>
</Window>
从技术角度看,Border 是装饰元素(decorator),装饰元素是特定类型的元素,通常用于在对象周围添加某些种类的图形装饰。
所有装饰元素都继承自System.Windows.Controls.Decorator 类。大多数装饰元素设计用于特定控件。例如,Button控件使用ButtonChrome 装饰元素,以获取其特有的圆角和阴影背景效果;而 ListBox 控件使用 ListBoxChrome 装饰元素。
还有两个更通用的装饰元素,当构造用户界面时它们非常有用:在此讨论的 Border 元素以及将在第 12 章中研究的 Viewbox 元素。
3.3 WrapPanel和DockPanel面板
WrapPanel 和 DockPanel 面板,它们是 WPF 提供的两个更简单的布局容器。这两个布局容器通过不同的布局行为对 StackPanel 面板进行补充。
3.3.1 WrapPanel面板
WrapPanel 面板在可能的空间中,以一次一行或一列的方式布置控件。默认情况下,WrapPanel.Orientation 属性设置为 Horizontal;控件从左向右进行排列,再在下一行中排列。但可将 WrapPanel.Orientation 属性设置为 Vertical,从而在多个列中放置元素。

与 StackPanel 面板类似,WrapPanel面板实际上主要用来控制用户界面中一小部分的布局细节,并非用于控制整个窗口布局。例如,可能使用 WrapPanel 面板以类似工具栏控件的方式将所有按钮保持在一起。
WrapPanel面板是唯一个不能通过灵活使用 Grid 面板代替的面板
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<WrapPanel Margin="3">
<Button VerticalAlignment="Top">Top Button</Button>
<Button MinHeight="60">Tall Button 2</Button>
<Button VerticalAlignment="Bottom">Bottom Button</Button>
<Button>Stretch Button</Button>
<Button VerticalAlignment="Center">Centered Button</Button>
</WrapPanel>
</Window>
3.3.2 DockPanel面板
DockPanel 面板沿着一条外边缘来拉伸所包含的控件。理解该面板最简便的方法是,考虑一下位于许多 Windows 应用程序窗口顶部的工具栏。这些工具栏停靠到窗口顶部。

与 StackPanel 面板类似,被停靠的元素选择它们布局的一个方面。例如,如果将一个按钮停靠在 DockPanel 面板的顶部,该按钮会被拉伸至 DockPanel 面板的整个宽度,但根据内容和 MinHeight 属性为其设置所需的高度。而如果将一个按钮停靠到容器左边,该按钮的高度将被拉伸以适应容器的高度,而其宽度可以根据需要自由增加。
子元素通过Dock的附加属性来选择停靠的边,可将该属性设置为 Left、Right、Top 或 Bottom。放在 DockPanel 面板中的每个元素都会自动捕获该属性。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">Top Button</Button>
<Button DockPanel.Dock="Bottom">Bottom Button</Button>
<Button DockPanel.Dock="Left">Left Button</Button>
<Button DockPanel.Dock="Right">Right Button</Button>
</DockPanel>
</Window>
3.3.3 嵌套布局容器
需求:创建一个标准对话框,在其右下角具有 OK按钮和 Cancel 按钮,并且在窗口的剩余部分是一块较大的内容区域。
步骤:
-
创建水平StackPanel 面板,用于将 OK 按钮和 Cancel 按钮放置在一起。
-
在 DockPanel 面板中放置 StackPanel 面板,将其停靠到窗口底部。
-
将 DockPanelLastChildFill属性设置为 true,以使用窗口剩余的部分填充其他内容。在此可以添加另一个布局控件,或者只添加一个普通的 TextBox 控件(本例中使用的是 TextBox 控件)。
-
设置边距属性,提供一定的空白空间。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
<Button Margin="10,10,2,10" Padding="3">OK</Button>
<Button Margin="2,10,10,10" Padding="3">Cancel</Button>
</StackPanel>
<TextBox DockPanel.Dock="Top" Margin="10">This is a test.</TextBox>
</DockPanel>
</Window>

3.4 Grid面板
在 Visual Studio 中为窗口添加新的XAML 文档时,会自动添加 Grid 标签作为顶级容器,并嵌套在 Window 根元素中。
Grid 面板将元素分隔到不可见的行列网格中。尽管可在一个单元格中放置多个元素(这时这些元素会相互重叠),但在每个单元格中只放置一个元素通常更合理。当然,在 Grid 单元格中的元素本身也可能是另一个容器,该容器组织它所包含的一组控件。
尽管 Grid 面板被设计成不可见的,但可将 Grid.ShowGridLines 属性设置为 true,从而更清晰地观察 Gird 面板。这一特性并不是真正试图美化窗口,反而是为了方便调试,设计该特性旨在帮助理解 Grid 面板如何将其自身分割成多个较小的区域。这一特性十分重要,因为通过该特性可准确控制 Grid 面板如何选择列宽和行高。
-
首先,选择希望使用的行和列的数量。
-
然后,为每个包含的元素指定恰当的行和列,从而在合适的位置放置元素。
-
Grid 面板通过使用对象填充 Grid.ColumnDefinitions和 Grid.RowDefinitions集合来创建网格和行。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Grid.ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0">Top Left</Button>
<Button Grid.Row="1" Grid.Column="0">Middle Left</Button>
<Button Grid.Row="2" Grid.Column="2">Bottom Right</Button>
<Button Grid.Row="2" Grid.Column="1">Bottom Middle</Button>
</Grid>
</Window>

3.4.1 调整行和列
Grid 面板支持以下三种设置尺寸的方式
- 绝对设置尺寸方式。使用设备无关单位准确地设置尺寸。这是最无用的策略,因为这种策略不够灵活,难以适应内容大小和容器大小的改变,而且难以处理本地化。
<ColumnDefinition Width="100"></ColumnDefinition>
- 自动设置尺寸方式。每行和每列的尺寸刚好满足需要。这是最有用的尺寸设置方式。按比例设置尺寸方式。
<ColumnDefinition Width="Auto"></ColumnDefinition>
- 按比例将空间分割到一组行和列中。这是对所有行和列的标准设置。
- 第一列的宽度是第二列宽度的2倍,系统会将布局分为2+1份,第一行占2份,第二行占1份
<ColumnDefinition Width="2*"></ColumnDefinition><ColumnDefinition Width="1*"></ColumnDefinition>
通过代码y额可以很方便地与 ColumnDefinition 和 RowDefinition 对象进行交互。
只需要知道Width 和 Height 属性是 GridLength 类型的对象即可。
为创建表示特定尺寸的 GridLength 对象,只需要为 GridLength 类的构造函数传递一个合适的数值即可。
为了创建一个表示按比例设置尺寸(*)的 GridLength 对象,可为 GridLength 类的构造函数传递数值作为第一个参数,并传递GridUnitType.Start 作为第二个参数。
为了要指定使用自动设置尺寸方式,可使用静态属性GridLength.Auto
3.4.2 布局舍入
WPF 使用分辨率无关的测量系统。尽管该测量系统为使用各种不同的硬件提供了灵活性,但有时也会引入一些问题。
其中一个问题是元素可能被对齐到子像素(subpixel)边界,换句话说,使用没有和物理像素准确对齐的小数坐标定位元素。可通过为相邻的布局容器提供非整数尺寸强制发生这个问题。
当创建按比例设置尺寸的 Grid 面板时就可能会发生该问题。
假设使用一个包含两列且具有 200 像素的 Grid 面板。如果将该面板均匀分成两个按比例设置尺寸的列,那么意味着每列为 100 像素宽。但是如果这个 Grid 面板的宽度为 175 像素,就不能很清晰地分割成两列,并且每列为87.5像素。这意味着第二列会和原始的像素边界稍有些错位。这通常不是问题,但是如果该列包含一个形状元素、一个边框或一幅图像,那么该内容的显示可能是模糊的,因为 WPF 会使用反锯齿功能“混合”原本清晰的像素边界边缘。
如果这个问题影响到布局,可以采用一种方法很方便地解决该问题。只需要将布局容器的UseLayoutRounding 属性设置为true,即:<Grid UseLayoutRounding="True">
到现在学了Grid的连个属性:<Grid Grid.ShowGridLines="True" UseLayoutRounding="True">,ShowGridLines是为了显示布局虚线、UseLayoutRounding是为了使布局容器中的所有内容对齐到最近的像素边界,使得布局边缘更加清晰
3.4.3 跨越行和列
可以使用另外两个附加属性使元素跨越多个单元格,这两个附加属性是 RowSpan 和 ColumnSpan。这两个属性使用元素将会占有的行数和列数进行设置。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Grid.ShowGridLines="True" UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">Top Left</Button>
<Button Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">Middle Left</Button>
<Button Grid.Row="2" Grid.Column="2">Bottom Right</Button>
<Button Grid.Row="2" Grid.Column="1">Bottom Middle</Button>
</Grid>
</Window>

3.4.4 分割窗口
每个 Windows 用户都见过分割条,能将窗口的一部分与另一部分分离的可拖动分割器。
例如,当使用 Windows 资源管理器时,会看到一系列文件夹(在左边)和一系列文件(在右边)。
可拖动它们之间的分割条来确定每部分占据窗口的比例。

在 WPF 中,分割条由 GridSpliter 类表示,它是 Grid 面板的功能之一。通过为 Grid 面板添加 GridSpliter 对象,用户就可以改变行和列的尺寸。
使用分割窗口布局时的一些基本原则:
- GridSpliter 对象必须放在 Grid 单元格中。可与已经存在的内容一并放到单元格中,这时需要调整边距设置,使它们不相互重叠。更好的方法是预留一列或一行专门用于放置 GridSplitter 对象,并将预留行或列的 Heigh 或 Width 属性的值设置为 Auto。
- GridSplitter 对象总是改变整行或整列的尺寸(而非改变单个单元格的尺寸)。为使GridSplitter 对象的外观和行为保持一致,需要拉伸 GridSpliter 对象使其穿越整行或整列,而不是将其限制在单元格中。为此,可使用前面介绍过的 RowSpan 或 ColumnSpan属性。如果不使用该设置,GridSplitter 对象会显示在顶行(放置它的行)中,即使这样,拖动分割条时也会改变整列的尺寸。
- 最初,GridSplitter 对象很小不易看见。为了使其更可用,需要为其设置最小尺寸。对于竖直分割条,需要将VerticalAlignment 属性设置为Stretch(使分割条填满区域的整个高度),并将 Width 设置为固定值(如 10 个设备无关单位)。对于水平分割条,需要设置 HorizontalAlignment 属性来拉伸,并将 Height 属性设置为固定值。
- GridSplitter 对齐方式还决定了分割条是水平的(用于改变行的尺寸)还是竖直的(用于改变列的尺寸)。对于水平分割条,需要将 VerticalAlignment 属性设置为 Center(这也是默认值),以指明拖动分割条改变上面行和下面行的尺寸。对于竖直分割条,需要将 HorizontalAlignment属性设置为 Center,以改变分割条两侧列的尺寸。
使用 GridSplitter 对象的 ResizeDirection 和ResizeBehavior 属性修改其尺寸调整行为。
为了可以成功地创建GridSplitter对象,务必为 VerticalAlignment、HorizontalAlignment以及 Width属性(或 Height 属性)提供相应的属性值。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Grid.ShowGridLines="True" UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="100"></ColumnDefinition>
<ColumnDefinition Width="Auto" ></ColumnDefinition>
<ColumnDefinition MinWidth="50" ></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Margin="3" >Left</Button>
<Button Grid.Row="0" Grid.Column="2" Margin="3" >Right</Button>
<Button Grid.Row="1" Grid.Column="0" Margin="3" >Left</Button>
<Button Grid.Row="1" Grid.Column="2" Margin="3" >Right</Button>
<GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Width="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ShowsPreview="False">
</GridSplitter>
</Grid>
</Window>

在声明 GridSplitter 对象时,将 ShowsPreview 属性设置为False(ShowsPreview="False"),即当把分割条从一边拖到另一边时,会立即改变列的尺寸。但是如果将ShowsPreview 属性设置为 true,当拖动分割条时就会看到一个灰色的阴影跟随鼠标指针,用于显示将在何处进行分割。并且直到释放了鼠标键之后列的尺寸才改变。
如果 GridSplitter 对象获得了焦点,也可以使用箭头键改变相应的尺寸。ShowsPreview 不是唯一可设置的 GridSpliter 属性。如果希望分割条以更大的幅度(如每次 10个单位)进行移动,可调整 DragIncrement 属性。如果希望控制列的最大尺寸和最小尺寸,只需要在 ColummnDefinitions 部分设置合适的属性。
可以改变 GridSplitter 对象的填充方式,使其不只是具有阴影的灰色矩形。技巧是使用Background 属性应用填充,该属性接受简单的颜色或更复杂的画刷。
如果 Grid 面板只有一行或一列,可忽略 RowDefinitions 部分。而且,对于没有明确设置行位置的元素,假定其 Grid.Row 属性值为 0,并被放到第一行中。没有提供 Grid.Column属性值的元素被放置到第一列中。
Grid 面板通常包含多个 GridSpliter 对象。然而,可以在一个 Grid 面板中嵌套另一个 Grid面板;而且,如果确实在 Grid 面板中嵌套了 Grid 面板,那么每个 Grid 面板可以有自己的GridSplitter 对象。这样就可以创建被分割成两部分(如左边窗格和右边窗格)的窗口,然后将这些区域(如右边的窗格)进一步分成更多的部分。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Grid.ShowGridLines="True" UseLayoutRounding="True">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="100"></ColumnDefinition>
<ColumnDefinition Width="Auto" ></ColumnDefinition>
<ColumnDefinition MinWidth="50" ></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Margin="3" Grid.Row="0">Top Left</Button>
<Button Margin="3" Grid.Row="1">Bottom Left</Button>
</Grid>
<GridSplitter Grid.Column="1" Width="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ShowsPreview="False"></GridSplitter>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Margin="3">Top Right</Button>
<Button Grid.Row="2" Margin="3">Bottom Right</Button>
<GridSplitter Grid.Row="1" Height="3" HorizontalAlignment="Stretch" VerticalAlignment="Center" ShowsPreview="False"></GridSplitter>
</Grid>
</Grid>
</Window>

3.4.5 共享尺寸组
Grid 面板包含一个行列集合,可以明确地按比例确定行和列的尺寸,或根据其子元素的尺寸确定行和列的尺寸。
还有另一种确定一行或一列尺寸的方法——与其他行或列的尺寸相匹配。这是通过称为“共享尺寸组”(shared size groups)的特性实现的。
共享尺寸组的目标是保持用户界面独立部分的一致性。例如,可能希望改变一列的尺寸以适应其内容,并改变另一列的尺寸使其与前面一列改变后的尺寸相匹配。然而,共享尺寸组的真正优点是使独立的 Grid 控件具有相同的比例。
可使用共享尺寸组来同步具有列标题的不同网格。每列的宽度可由列中的内容决定,列标题将共享这一宽度。甚至可在列标题中放置一个GridSplitter对象,用户可通过拖动该 GridSplitter对象来改变列标题以及列标题下的整个列。
对于整个应用程序来说,共享尺寸组并不是全局的,因为多个窗口可能在无意间使用相同名称。
若要共享一个组,需要在包含 Grid 对象之前明确地将Grid.IsSharedSizeScope 附加属性设置为 true。
需求:窗口具有两个 Grid对象——一个位于窗口顶部(有三列),另一个位于窗口底部(有两列)。
第一个 Grid 面板的最左边一列按比例地改变其尺寸,以适应其包含的内容(一个较长的文本字符串)。
第二个 Grid 面板的最左边一列和第一个 Gird 面板的最左边一列的宽度完全相同,但包含的内容较少。
不管在第一个 Grid 面板的第一列中放置了多少内容,第二个 Grid面板中的第一列总是和第一个 Gird 面板中的第一列保持同步。
使用共享尺寸组实现。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Grid.ShowGridLines="True" UseLayoutRounding="True" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="3" Background="LightYellow" ShowGridLines="True" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="beyondyy"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Margin="5">A very long bit of text</Label>
<Label Grid.Column="1" Margin="5">More text</Label>
<TextBox Grid.Column="2" Margin="5" >A text box</TextBox>
</Grid>
<Label Grid.Row="1">This is a label</Label>
<Grid Grid.Row="2" Margin="3" Background="LightGreen" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="beyondyy"></ColumnDefinition>
<ColumnDefinition ></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Margin="5">Short</Label>
<TextBox Grid.Column="1" Margin="5" >A text box</TextBox>
</Grid>
</Grid>
</Window>
可使用共享尺寸组来同步具有列标题的不同网格。每列的宽度可由列中的内容决定,列标题将共享这一宽度。甚至可在列标题中放置一个GridSplitter对象,用户可通过拖动该 GridSplitter对象来改变列标题以及列标题下的整个列。
3.4.6 UniformGrid面板
与 Grid 面板不同,UniformGrid 面板不需要(甚至不支持)预先定义的列和行。相反,通过简单地设置 Rows 和Columns 属性来设置其尺寸。每个单元格始终具有相同的大小,因为可用的空间被均分。最后,元素根据定义的顺序被放置到适当的单元格中。UniformGrid 面板中没有 Row 和 Column 附加属性,也没有空白单元格。
与 Grid 面板相比,UniformGrid 面板很少使用。Grid 面板是用于创建简单乃至复杂窗口布局的通用工具。UniformGrid 面板是一种更特殊的布局容器,主要用于在刻板的网格中快速地布局元素(例如,为特定游戏构建播放面板)。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<UniformGrid Rows="2" Columns="2">
<Button>Top Left</Button>
<Button>Top Right</Button>
<Button>Bottom Left</Button>
<Button>Bottom Right</Button>
</UniformGrid>
</Window>

3.5 使用Canvas面板进行基于坐标的布局
Canvas 面板允许使用精确的坐标放置元素,如果设计数据驱动的富窗体和标准对话框,这并非好的选择;但如果需要构建其他一些不同的内容(例如,为图形工具创建绘图表面),Canvas 面板可能是个有用的工具。
Canvas 面板还是最轻量级的布局容器。这是因为 Canvas 面板没有包含任何复杂的布局逻辑,用以改变其子元素的首选尺寸。Canvas 面板只是在指定的位置放置其子元素,并且其子元素具有所希望的精确尺寸。
为在 Canvas 面板中定位元素,需要设置 Canvas.Left 和 Canvas.Top 附加属性。
Canvas.Left属性设置元素左边和 Canvas 面板左边之间的单位数,Canvas.Top 属性设置子元素顶边和Canvas 面板顶边之间的单位数。同样,这些数值也是以设备无关单位设置的,当将系统 DPI设置为 96 dpi时,设备无关单位恰好等于通常的像素。
可使用 Canvas.Right 属性来确定元素和 Canvas 面板右边缘间的距离(而不是 Canvas.Left 属性);可使用 Canvas.Bottom 属性来确定元素和 Canvas 面板底部边缘的距离(而不是 Canvas.Top 属性)。不能同时使用 Canvas.Right 和 Canvas.Lef 属性,也不能同时使用 Canvas.Top 和Canvas.Bottom属性。
可使用 Width 和 Height 属性明确设置子元素的尺寸。 Canvas 面板没有自己的布局逻辑(并且当需要精确控制组合元素如何排列时,经常会使用 Canvas 面板)。
如果没有设置 Width 和 Height 属性,元素将变得足够大以适应其内容。
如果改变窗口的大小,Canvas 面板就会拉伸以填满可用空间,但 Canvas 面板上的控件不会改变其尺寸和位置。
Canvas 面板不包含任何锚定和停靠功能,这两个功能是在 Windows 窗体中使用坐标布局提供的。造成该问题的部分原因是为了保持 Canvas 面板的轻量级,另一个原因是为了防止以不当目的使用 Canvas 面板(例如,确定标准用户界面的布局)
如果与其他元素一起使用 Canvas 面板,可能希望将它的 ClipToBounds 属性设置为 true。这样,如果 Canvas 面板中的元素被拉伸超出 Canvas 面板的边界,将在 Canvas 面板的边缘处剪裁这些子元素(这样可以阻止它们与窗口中的其他元素重叠)。
其他所有布局容器总是剪裁它们的子元素以适应其尺寸,而不考虑ClipToBounds 的设置。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Canvas>
<Button Canvas.Left="10" Canvas.Top="10">(10,10)</Button>
<Button Canvas.Left="120" Canvas.Top="30">(120,30)</Button>
<Button Canvas.Left="60" Canvas.Top="80" Width="50" Height="50">(60,80)</Button>
<Button Canvas.Left="70" Canvas.Top="120" Width="100" Height="50">(70,120)</Button>
</Canvas>
</Window>

3.5.1 Z顺序
如果 Canvas 面板中有多个互相重叠的元素,可通过设置 Canvas.ZIndex 附加属性来控制它们的层叠方式。
添加的所有元素通常都具有相同的 ZIndex 值(ZIndex=0)。如果元素具有相同的 ZIndex 值,就按它们在 Canvas.Children 集合中的顺序进行显示,这个顺序依赖于元素在 XAML标记中定义的顺序。在标记中靠后位置声明的元素(如按钮(70,120))会显示在前面声明的元素(如按钮(120,30))的上面。
可通过增加任何子元素的 ZIndex 值来提高层次级别(例如:Canvas.ZIndex="1")。因为具有更高 ZIndex 值的元素始终显示在较低 ZIndex 值的元素的上面。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Canvas>
<Button Canvas.Left="10" Canvas.Top="10">(10,10)</Button>
<Button Canvas.Left="120" Canvas.Top="30">(120,30)</Button>
<Button Canvas.Left="60" Canvas.Top="80" Canvas.ZIndex="1" Width="50" Height="50">(60,80)</Button>
<Button Canvas.Left="70" Canvas.Top="120" Width="100" Height="50">(70,120)</Button>
</Canvas>
</Window>

如果需要通过代码来改变元素的位置,Zmdex属性是非常有用的。只需要调用**Canvas.SetZIndex()**方法,并传递希望修改的元素和希望使用的新 ZIndex 值即可。但并不存在 BringToFront( )或 SendToBack()方法,要实现这一行为,需要跟踪最高和最低的 ZIndex 值。
3.5.2 InkCanvas元素
和 Canvas 面板一样,InkCanvas 元素定义了4个附加属性(Top、Left、Bottom 和 Right),可将这4个附加属性应用于子元素,以根据坐标进行定位。
InkCanvas 类不是派生自 Canvas 类,甚至也不是派生自 Panel 基类,而是直接派生自FrameworkElement类。
InkCanvas 元素的主要目的是用于接收手写笔输入。手写笔是一种在平板 PC 中使用的类似于钢笔的输入设备,然而,InkCanvas 元素同时也可使用鼠标进行工作,就像使用手写笔一样。因此,用户可使用鼠标在 InkCanvas 元素上绘制线条,或者选择以及操作 InkCanvas 中的元素。
InkCanvas 元素实际上包含两个子内容集合。一个是 Children 集合,它保存任意元素,就像 Canvas 面板一样。每个子元素可根据 Top、Left、Bottom 和 Right 属性进行定位。另一个是 Strokes 集合,它保存 System.Windows.Ink.Stroke 对象,该对象表示用户在 InkCanvas元素上绘制的图形输入。用户绘制的每条直线或曲线都变成独立的Stroke 对象。得益于这两个集合,可使用 InkCanvas 让用户使用存储在 Strokes 集合中的笔画(stroke)为保存在 Children 集合中的内容添加注释。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="500">
<Canvas>
<InkCanvas Name="inkCanvas" Background="LightCyan" EditingMode="Ink">
<Image Source="E:\normal_desk\desk\1018\1.bmp" InkCanvas.Top="10" InkCanvas.Left="10" Width="450" Height="400"></Image>
</InkCanvas>
</Canvas>
</Window>

EditingMode还有很多的属性可以进行设置InkCanvas元素
| 名称 | 说明 |
|---|---|
| Ink | InkCanvas元素允许用户绘制批注,这是默认模式。当用户用鼠标或手写笔绘图时,会绘制笔画 |
| GestureOnly | InkCanvas元素不允许用户绘制笔画批注,但会关注预先定义的特定姿势(例如在某个方向拖动手写笔或涂画内容)。能识别的姿势的完整列表由Systcm.Windows.Ink.ApplicationGesture 枚举给出 |
| InkAndGesture | InkCanvas元素允许用户绘制笔画批注,也可以识别预先定义的姿势 |
| EraseByStroke | 当单击笔画时,InkCanvas元素会擦除笔画。如果用户使用手写笔,可使用手写笔的底端切换到该模式(可使用只读的ActiveEditingMode 属性确定当前编辑模式,也可通过改变EditingModelInverted 属性来改变手写笔的底端使用的工作模式) |
| EraseByPoint | 当单击笔画时,InkCanvas元素会擦除笔画中被单击的部分(笔画上的一个点) |
| Select | InkCanvas面板允许用户选择保存在 Children 集合中的元素。要选择一个元素,用户必须单击该元素或拖动 “套索”选择该元素。一旦选择一个元素,就可以移动该元素、改变其尺寸或将其删除 |
| None | InkCanvas元素忽略鼠标和手写笔输入 |
InkCanvas 元素会引发多种事件
- 当编辑模式改变时会引发 ActiveEditingModeChanged 事件在 GestureOnly 或 InkAndGesture 模式下删除姿势时会引发 Gesture 事件
- 绘制完笔画时会引发StrokeCollected 事件
- 擦除笔画时会引发 StrokeErasing 事件和 StrokeErased 事件
- 在 Select 模式下选择元素或改变元素时会引发 SelectionChanging 事件、SelectionChanged 事件、SelectionMoving事件、SelectionMoved 事件、SelectionResizing 事件和 SelectionResized 事件。其中,名称以“img”结尾的事件表示动作将要发生,但可以通过设置 EventArgs 对象的 Cancel属性取消事件。在 Select 模式下,InkCanvas 元素可为拖动以及操作内容提供功能强大的设计界面。
Select 模式十分有趣,但并不适合用于构建绘图工具。第 14 章将列举创建自定义绘图界面的更好示例。
3.6 布局示例
3.6.1 列设置
- 首先定义网格的行和列。行定义足够简单,只需要将每行的尺寸设置为所含内容的高度。这意味着所有行都将使用最大元素的高度,在该例中,最大的元素是第三列中的 Browse 按钮。
- 接下来需要创建列。第一列和最后一列的尺寸要适合其内容(分别是标签文本和 Browse 按钮)。中间列占用所有剩余空间,这意味着当窗口变大时,该列的尺寸会增加,这样可有更大的空间显示选择的文件夹(如果希望拉伸不超过一定的最大宽度,在定义列时可使用 MaxWidth 属性,就像对单个元素使用 MaxWidth 属性一样)。Grid 面板需要一定的最小空间,要足以容纳整个标签文本、浏览按钮以及在中间列中有一定的像素以显示文本框。如果缩小包含窗口使其小于这一最小空间,就会将一些内容剪栽掉。与通常的情形一样,使用窗口的 MinWidth 和 MinHeight 属性防止这种情况的发生是有意义的。
- 最后需要在恰当的单元格中放置元素。需要仔细考虑边距和对齐方式,每个元素需要基本的边距(3个单位较恰当)以在其周围添加一些空间。此外,标签和文本框在垂直方向上需要居中,因为它们没有 Browse 按钮高。最后,文本框需要使用自动设置尺寸模式,这样它会被拉伸以充满整列。
使用了 Grid 控件,该窗口是非常灵活的。没有任何-个元素(标签、文本框以及按钮)是通过硬编码来定位和设置尺寸的。因此,可通过简单地修改 ColumnDefinition 元素来快速改变整个网格。甚至,如果添加了包含更长标签文本的行(迫使第一列更宽),就会调整整个网格使其保持一致,包括已经添加的行。如果希望在两行之间添加元素----例如,添加分割线以区分窗口的不同部分——可保持网格的列定义不变,但使用 ColumnSpan 属性拉伸某个元素,使其覆盖更大的区域。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="380">
<Grid Margin="3,3,10,3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Margin="3" VerticalAlignment="Center">Home:</Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="3" Height="Auto" VerticalAlignment="Center"></TextBox>
<Button Grid.Row="0" Grid.Column="2" Margin="3" Padding="2">Browse</Button>
<Label Grid.Row="1" Grid.Column="0" Margin="3" VerticalAlignment="Center">Username:</Label>
<TextBox Grid.Row="1" Grid.Column="1" Margin="3" Height="Auto" VerticalAlignment="Center"></TextBox>
<Button Grid.Row="1" Grid.Column="2" Margin="3" Padding="2">Login</Button>
<Label Grid.Row="2" Grid.Column="0" Margin="3" VerticalAlignment="Center">Password:</Label>
<PasswordBox Grid.Row="2" Grid.Column="1" Margin="3" Height="Auto" VerticalAlignment="Center"></PasswordBox>
<Button Grid.Row="2" Grid.Column="2" Margin="3" Padding="2">Login</Button>
</Grid>
</Window>

3.6.2 动态内容
对于不同的地域,在用户界面中显示的文本需要翻译成不同的语言会出现问题。
在老式的基于坐标的应用程序中,改变窗口中的文本会造成混乱,部分原因是少量英语文本翻译成许多语言后会变得特别大。尽管允许改变元素的尺寸以适应更大的文本,但这样做经常使整个窗口失去平衡。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="380">
<Grid Margin="3,3,10,3">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0">
<Button Name="yy_Prev" Margin="10,10,10,3">Prev</Button>
<Button Name="yy_Next" Margin="10,3,10,10">Next</Button>
<CheckBox Name="yy_LongText" Margin="10,10,10,10" Checked="yy_LongText_Checked" Unchecked="yy_LongText_Unchecked">Show Long Text</CheckBox>
</StackPanel>
<TextBox Grid.Row="0" Grid.Column="1" Margin="0,10,10,10" x:Name="yy_TextBox" TextWrapping="WrapWithOverflow" Grid.RowSpan="2">Text...</TextBox>
<Button Grid.Row="1" Grid.Column="0" Name="yy_Close" Margin="10,3,10,10">Close</Button>
</Grid>
</Window>
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void y_btnAnswer_Click(object sender, RoutedEventArgs e)
{
}
private void yy_LongText_Checked(object sender, RoutedEventArgs e)
{
this.yy_TextBox.Text = "This is a test that demonstrates how buttons adapt themselves to fit the content they contain when they aren't explicitly sized.This behavior makes localization much easier, as the text can be adjusted without affecting the layout of the buttons.";
}
private void yy_LongText_Unchecked(object sender, RoutedEventArgs e)
{
this.yy_TextBox.Text = "This is a test that demonstrates how buttons adapt themselves to fit the content they contain " +
"\twhen they aren't explicitly sized." +
"\tThis behavior makes localization much easier, " +
"\nas the text can be adjusted without affecting the layout of the buttons.";
}
}
}



3.6.3 组合式用户界面
多个面板可以组合操作,可通过隐藏和显示单个面板实现切换功能,主要通过Visibility属性来实现,例如:yy_panel.Visibility = Visibility.Collapsed;
Visibility属性是UIElement基类的一部分,该属性有三个值,来自System.Windows.Visibility枚举
| 值 | 说明 |
|---|---|
| Visible | 元素在窗口中正常显示 |
| Collapsed | 元素不显示,也不占用任何空间 |
| Hidden | 元素不显示,但仍为其保留空间(换句话说,会在元素可能显示的地方保留空白空间)。如果需要隐藏和显示元素,又不希望改变窗口布局和窗口中剩余元素的相对位置,使用该设置是非常方便的 |
可使用 Visibility 属性动态调整各种界面。例如,可制作在窗口一边显示的可折叠窗格。需要完成的全部工作就是在几种布局容器中包含窗格的所有内容,并恰当地设置其 Visibility属性。剩余的内容会重新排列以适应余下的空间。
3.7 小结
本章详细介绍了 WPF 布局模型,并讨论了如何以堆栈、网格以及其他排列方式放置元素。可使用嵌套的布局容器组合创建更复杂的布局,可结合使用 GridSplitter 对象创建可变的分割窗口。本章一直非常关注这一巨大变化的原因–WPF 布局模型在保持、加强以及本地化用户界面方面所具有的优点。
布局内容远不止这些。接下来的几章还将列举更多使用布局容器组织元素分组的示例,还将学习允许在窗口中排列内容的几个附加功能:
- 特殊容器。可以使用 ScrollViewer、TabItem 以及 Expander 控件滚动内容、将内容放到单独的选项卡中以及折叠内容。与布局面板不同,这些容器只能包含单一内容。不过,可以很容易地组合使用这些容器和布局面板,以便准确实现所需的效果。第6章将尝试使用这些容器。
- Viewbox。Viewbox 是另一种特殊容器,可以改变图形内容(如图像和矢量图形)的尺寸,而且 Viewbox 控件内置了缩放功能。在第 12章中,您将首次接触到 Viewbox 容器。
- 文本布局。WPF 新增了用于确定大块格式化文本布局的新工具。可使用浮动图形和列表,并且可以使用分页、分列以及更复杂、更智能的换行功能来获得非常完美的结果。第28章将介绍文本布局的方式。
2299

被折叠的 条评论
为什么被折叠?



