学习了下WPF里面的布局,参考书是《WPF揭秘》,如下是笔记html
布局是WPF界面开发中一个很重要的环节。所谓布局,即肯定全部控件的大小和位置,是一种递归进行的父元素(Panel)和子元素交互的过程,为了同时知足父元素和子元素的须要,WPF采用了一种包含测量(Measure)和排列(Arrange)两个步骤的解决方案。子元素最终所占用的空间和位置是由父元素肯定的(RenderSize),可是父元素会先参考子元素的意见(DesiredSize)。下面来看看子元素怎样给出意见(控制尺寸、控制位置、变换)以及父元素怎样作决定编程
1. 高度和宽度app
FrameworkElement元素会根据内容大小调整尺寸(这里有一个例外,若是Window不设置SizeToContent的话,会根据屏幕分辨率设置本身的大小),它同时有Width(默认值Double.NaN,XAML里能够指定为Auto,意思就是和内容同样大)、Height(默认值同Width)、MinWidth(默认值0)、MinHeight(默认值0)、MaxWidth(默认值Double.PositiveInfinity,XAML里面能够写Infinity)、MaxHeight(默认值同MaxWidth)控制宽高,显然若是Width和Height在Min*和Max*范围内的时候,它们的优先级要比Min*以及Max*高异步
FrameworkElement还有一些与尺寸有关的只读属性:DesiredSize、ActualWidth和ActualHeight、RenderSize;DesiredSize是基于以上属性计算出来的,由父元素(Panel)在布局过程当中使用的;RenderSize则是布局结束后元素的尺寸,ActualWidth和ActualHeight与之相同。因为布局操做是异步的,RenderSize的值会晚于Height、Width等基本属性的值,因此依赖RenderSize是不可靠的;UIElement中有一个强制完成布局的方法UpdateLayout(),但因为它会影响性能,并且不能保证正在使用的元素会被正常渲染,因此通常不用ide
2. Margin和Padding布局
FrameworkElement.Margin:控制元素边界外的空间post
Control.Padding:控制元素边界内的空间性能
3. Visibility学习
Visible:元素可见,并参与布局ui
Collapsed:元素不可见而且不参与布局
Hidden:元素不可见可是参与布局
不一样父元素(Panel)有不一样的方法肯定子元素的位置,可是有一些方法是子元素共有的
1. Alignment
子元素(FrameworkElement)能够经过设置Alignment(默认值Stretch)控制怎样使用父元素分配给它的多余的空间;“多余的空间”很重要,由于若是父元素按照子元素的大小给它分配空间的话,这两个属性就不起做用了
好比Canvas就没有给它的子元素分配多余的空间,因此设置HorizontalAlignment和VerticalAlignment不起做用
再好比StackPanel(Orientation属性值这里默认是Vertical,表示子元素垂直排列)只为子元素在水平方向上分配了多余空间,垂直方向上根据尺寸分配,因此设置HorizontalAlignment能够起做用,而设置VerticalAlignment不起做用
2. Content Alignment
Control元素还能够经过设置HorizontalContentAlignment和VerticalContentAlignment控制本身的内容元素怎样对齐
3. FlowDirection
FrameworkElement能够经过设置此属性改变此元素的内容流动的方向(LeftToRight和RightToLeft),能够做用在面板(Panel)或者拥有子元素的控件上
WPF元素还能够经过变换来改变尺寸和位置,有两种变换,RenderTransform和LayoutTransform
RenderTransform(继承自UIElement):在布局结束以后应用
LayoutTransform:在布局前应用
UIElement还有一个属性RenderTransformOrigin表示变换的原点,使用相对定位,(0,0)表示左上角,(1,1)表示右下角,显然RenderTransformOrigin只用于RenderTransform;LayoutTransform没有原点的概念是由于它要参与布局,被变换元素的位置由父元素的布局规则控制
1. RotateTransform
控制变换的属性:Angle(旋转角度)、CenterX和CenterY(旋转中心点);CenterX和CenterY使用的是绝对定位(像素无关单位),能够与RenderTransformOrigin组合起来使用,在缩放变换(ScaleTransform)和倾斜变换(SkewTransform)中都是这样
2. ScaleTransform
控制变换的属性:ScaleX(水平方向的缩放因子)、ScaleY(垂直方向的缩放因子)、CenterX和CenterY(缩放的中心点)
3. SkewTransform
控制变换的属性:AngleX(水平倾斜的角度)、AngleY(垂直倾斜的角度)、CenterX和CenterY(倾斜的中心点)
4. TranslateTransform
控制变换的属性:X(水平偏移量)、Y(垂直偏移量);与上面三种变换不一样的是,TranslateTransform做为LayoutTransform应用时不起做用
5. MatrixTransform
控制变换的属性:Matrix(3×3仿射变换矩阵),上面的4种变换均可以经过定义Matrix实现,而且能够直接在XAML里用一个字符串设置,好比下图的变换实现的是水平和垂直方向上放大两倍的效果
6. TransformGroup
能够组合多个变换
Panel有一个ZIndex附加属性,ZIndex值大的元素会呈如今ZIndex值小的元素上方
WPF内置的经常使用面板有:Canvas、StackPanel、WrapPanel、DockPanel、Grid,还有一些大多数时候在控件内部使用的轻量级面板
1. 经常使用面板
经常使用面板里只记录一下GridSplitter(实际不是Panel类),Grid中能够经过GridSplitter交互改变行列尺寸,哪一个单元格尺寸会被影响取决于GridSplitter的对齐值HorizontalAlignment(默认是Right)和VerticalAlignment(默认是Stretch),《WPF揭秘》里有张图,贴在这里,另外ResizeDirection和ResizeBehavior属性也会影响GridSplitter改变单元格尺寸的行为
2. TabPanel
TabControl的默认样式用它来处理TabItem的布局;TabPanel仅支持从左往右的排列,从上往下的换行,当换行发生时它会平均拉伸元素,使全部的行占据面板的所有宽度
3. ToolBarOverflowPanel
仅支持从左往右的排列、从上往下的换行,默认样式的ToolBar就是用它来显示没法在主区域显示的元素,有一个WrapWidth属性
4. ToolBarTray
仅支持ToolBar子元素,它会以水平的方式排列ToolBar,而且能够拖动ToolBar生成其余行,或者压缩或扩展相邻的ToolBar
5. UniformGrid
子元素按先行后列的顺序添加,而且行列的大小都是*(平均大小)
6. VirtualizingStackPanel
不一样于以上的轻量级面板,当绑定大量数据的时候,VirtualizingStackPanel是首选,由于它会临时抛弃显示范围以外的元素以提升性能,ListBox的默认样式使用的就是这个面板
当父元素不能知足子元素尺寸需求的时候,子元素可能会拒绝在太小的空间呈现,这种状况下就会发生内容溢出
父元素(Panel)在处理内容溢出的时候,有如下几种策略:
1. Clipping(剪辑)
UIElement用ClipToBounds属性控制本身是否剪辑超出边界的内容,可是WPF内置面板中只有Canvas支持这个属性,其余诸如Grid等面板设置这个属性也没有用
另外Grid等面板中的子元素经过变换(Transform)超出边界的部分也会被剪辑
想要不被剪辑,看这里
再看这里
还有
不过貌似也没啥用
2. Scrolling(滚屏)
把须要滚屏的元素做为ScrollViewer的子元素便可实现滚屏,可是不要为该元素设置宽度或高度,由于ScollViewer须要根据子元素的内容大小设置合适的水平和垂直滚动范围
3. Scaling(缩放)
为了在给定空间中缩听任意元素(ScaleTransform搞不定),可使用Viewbox,有两个重要属性:Stretch(控制子元素怎样在Viewbox的边界内缩放)、StretchDirection(控制是须要缩小仍是放大子元素)。须要注意的是,Viewbox的缩放是在布局以后发生的
4. 其余
还有两种策略是换行(Wrapping)和截断(Trimming),换行是WrapPanel用的策略,截断则是TextBlock和AccessText中内联文本使用的策略
《WPF揭秘》里一个布局实例,本身实现了一下,主要利用Grid的共享尺寸属性SharedSizeGroup,须要注意一点,只有将父级Grid的Grid.IsSharedSizeScope设置为True,它的范围内的尺寸共享才能生效
效果以下:
XAML代码以下:
<Window x:Class="VSUIDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow"> <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="File" /> <MenuItem Header="Edit" /> <MenuItem Header="View" /> <MenuItem Header="Project" /> <MenuItem Header="Build" /> <MenuItem Header="Debug" /> <MenuItem Header="Team" /> <MenuItem Header="Tool" /> <MenuItem Header="Test" /> <MenuItem Header="Structure" /> <MenuItem Header="Analysis" /> <MenuItem Header="Window" /> <MenuItem Header="Help" /> </Menu> <StackPanel DockPanel.Dock="Right" Orientation="Horizontal"> <StackPanel.LayoutTransform> <RotateTransform Angle="90" /> </StackPanel.LayoutTransform> <Button x:Name="toolboxButton" Content="Toolbox" MouseEnter="toolboxButton_MouseEnter" /> <Button x:Name="solutionButton" Margin="2,0" Content="Solution Explorer" MouseEnter="solutionButton_MouseEnter" /> </StackPanel> <Grid Grid.IsSharedSizeScope="True"> <Grid x:Name="layer0Grid" Panel.ZIndex="0" MouseEnter="layer0Grid_MouseEnter"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Border Grid.ColumnSpan="2" Background="BlueViolet"> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="36" Text="Start Page" /> </Border> <GroupBox Grid.Row="1" Margin="2" BorderThickness="2" Header="Recent Projects"> ... </GroupBox> <GroupBox Grid.Row="1" Grid.RowSpan="3" Grid.Column="1" Margin="2" BorderThickness="2" Header="Online Articles"> <ListBox> <ListBoxItem Content="Article #1" /> <ListBoxItem Content="Article #2" /> <ListBoxItem Content="Article #3" /> <ListBoxItem Content="Article #4" /> </ListBox> </GroupBox> <GroupBox Grid.Row="2" Margin="2" BorderThickness="2" Header="Getting Started"> ... </GroupBox> <GroupBox Grid.Row="3" Margin="2" BorderThickness="2" Header="Headlines"> ... </GroupBox> </Grid> <Grid x:Name="toolboxLayerGrid" Visibility="Collapsed"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto" SharedSizeGroup="ToolboxGroup" /> </Grid.ColumnDefinitions> <GridSplitter Grid.Column="1" Width="3" HorizontalAlignment="Left" /> <Grid x:Name="toolboxGrid" Grid.Column="1" Margin="3,0,0,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Grid Background="LightBlue"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="35" /> </Grid.ColumnDefinitions> <TextBlock VerticalAlignment="Center" FontSize="18" Text="Toolbox" TextTrimming="CharacterEllipsis" /> <Button x:Name="toolboxLayerPinButton" Grid.Column="1" Click="toolboxLayerPinButton_Click"> <Image x:Name="toolboxImage" Width="24" Height="24" Source="Resource/Image/pin_float.png" /> </Button> </Grid> <ListBox Grid.Row="1" FontSize="16"> <ListBoxItem Content="Button" /> <ListBoxItem Content="CheckBox" /> <ListBoxItem Content="Label" /> <ListBoxItem Content="ComboBox" /> <ListBoxItem Content="ListBox" /> </ListBox> </Grid> </Grid> <Grid x:Name="solutionLayerGrid" Visibility="Collapsed"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto" SharedSizeGroup="SolutionGroup" /> </Grid.ColumnDefinitions> <GridSplitter Grid.Column="1" Width="3" HorizontalAlignment="Left" /> <Grid x:Name="solutionGrid" Grid.Column="1" Margin="3,0,0,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Grid Background="LightBlue"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="35" /> </Grid.ColumnDefinitions> <TextBlock VerticalAlignment="Center" FontSize="18" Text="Solution Explorer" TextTrimming="CharacterEllipsis" /> <Button x:Name="solutionLayerPinButton" Grid.Column="1" Click="solutionLayerPinButton_Click"> <Image x:Name="solutionImage" Width="24" Height="24" Source="Resource/Image/pin_float.png" /> </Button> </Grid> <Border Grid.Row="1" Background="White"> <ToolBar> <Image Source="Resource/Image/copy.png" /> <Image Margin="2,0" Source="Resource/Image/paste.png" /> <Image Margin="2,0" Source="Resource/Image/refresh.png" /> </ToolBar> </Border> <TreeView Grid.Row="2"> <TreeViewItem Header="My Solution"> <TreeViewItem Header="Project #1" /> <TreeViewItem Header="Project #2" /> <TreeViewItem Header="Project #3" /> </TreeViewItem> </TreeView> </Grid> </Grid> </Grid> </DockPanel> </Window>
后台代码以下:
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; namespace VSUIDemo { public partial class MainWindow : Window { private ColumnDefinition _cloneToolboxGrid; private ColumnDefinition _cloneSolutionGrid; private ColumnDefinition _cloneToToolboxLayerGrid; public MainWindow() { InitializeComponent(); _cloneToolboxGrid = new ColumnDefinition { SharedSizeGroup = "ToolboxGroup" }; _cloneSolutionGrid = new ColumnDefinition { SharedSizeGroup = "SolutionGroup" }; _cloneToToolboxLayerGrid = new ColumnDefinition { SharedSizeGroup = "SolutionGroup" }; } private void toolboxButton_MouseEnter(object sender, MouseEventArgs e) { toolboxLayerGrid.Visibility = System.Windows.Visibility.Visible; toolboxLayerGrid.SetValue(Grid.ZIndexProperty, 2); if (solutionButton.Visibility == System.Windows.Visibility.Visible) solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed; else solutionLayerGrid.SetValue(Grid.ZIndexProperty, 1); } private void solutionButton_MouseEnter(object sender, MouseEventArgs e) { solutionLayerGrid.Visibility = System.Windows.Visibility.Visible; solutionLayerGrid.SetValue(Grid.ZIndexProperty, 2); if (toolboxButton.Visibility == System.Windows.Visibility.Visible) toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed; else toolboxLayerGrid.SetValue(Grid.ZIndexProperty, 1); } private void layer0Grid_MouseEnter(object sender, MouseEventArgs e) { if (toolboxButton.Visibility == System.Windows.Visibility.Visible) toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed; if (solutionButton.Visibility == System.Windows.Visibility.Visible) solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed; } private void toolboxLayerPinButton_Click(object sender, RoutedEventArgs e) { if (toolboxButton.Visibility == System.Windows.Visibility.Visible) { toolboxImage.Source = new BitmapImage(new Uri("Resource/Image/pin_fix.png", UriKind.Relative)); toolboxButton.Visibility = System.Windows.Visibility.Collapsed; layer0Grid.ColumnDefinitions.Add(_cloneToolboxGrid); if (solutionButton.Visibility == System.Windows.Visibility.Collapsed) toolboxLayerGrid.ColumnDefinitions.Add(_cloneToToolboxLayerGrid); } else { toolboxImage.Source = new BitmapImage(new Uri("Resource/Image/pin_float.png", UriKind.Relative)); toolboxButton.Visibility = System.Windows.Visibility.Visible; toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed; layer0Grid.ColumnDefinitions.Remove(_cloneToolboxGrid); if (solutionButton.Visibility == System.Windows.Visibility.Collapsed) toolboxLayerGrid.ColumnDefinitions.Remove(_cloneToToolboxLayerGrid); } } private void solutionLayerPinButton_Click(object sender, RoutedEventArgs e) { if (solutionButton.Visibility == System.Windows.Visibility.Visible) { solutionImage.Source = new BitmapImage(new Uri("Resource/Image/pin_fix.png", UriKind.Relative)); solutionButton.Visibility = System.Windows.Visibility.Collapsed; layer0Grid.ColumnDefinitions.Add(_cloneSolutionGrid); if (toolboxButton.Visibility == System.Windows.Visibility.Collapsed) toolboxLayerGrid.ColumnDefinitions.Add(_cloneToToolboxLayerGrid); } else { solutionImage.Source = new BitmapImage(new Uri("Resource/Image/pin_float.png", UriKind.Relative)); solutionButton.Visibility = System.Windows.Visibility.Visible; solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed; layer0Grid.ColumnDefinitions.Remove(_cloneSolutionGrid); if (toolboxButton.Visibility == System.Windows.Visibility.Collapsed) toolboxLayerGrid.ColumnDefinitions.Remove(_cloneToToolboxLayerGrid); } } } }
1. 测量阶段
该阶段决定子元素但愿占用多大的尺寸。能够经过重写MeasureOverride()来实现本身的逻辑,重写MeasureOverride()方法时,必须调用每一个子元素的Measure()方法,传入边界值做为参数,能够传入一个无限大的边界( uiElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); ),这样子元素就会根据全部内容大小肯定DesiredSize;另外须要注意的是MeasureOverride()返回父元素自身所占用的尺寸,能够根据全部子元素的占用尺寸计算获得,不能返回一个无限大的尺寸
2. 排列阶段
该阶段为每个控件指定边界。能够经过重写ArrangeOverride()实现本身的逻辑。重写ArrangeOverride()方法时,必须调用每一个子元素的Arrange()方法,传入一个定义尺寸和位置的Rect对象做为参数
3. 自定义面板
主要是重写以上两个阶段的逻辑,下面是《WPF编程宝典》的一个例子,本身实现了一下
效果以下:
后台代码也贴在这里:
// 自定义一个从左至右排列、从上往下换行的面板,而且提供一个附加属性能够指示在哪一个子元素前换行 public class MyWrapPanel : Panel { // 定义一个指示在哪一个子元素前换行的附加属性 public static readonly DependencyProperty LineBreakBeforeProperty; static MyWrapPanel() { var metadata = new FrameworkPropertyMetadata { AffectsMeasure = true, AffectsArrange = true }; LineBreakBeforeProperty = DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(MyWrapPanel), metadata); } public static void SetLineBreakBefore(UIElement element, bool value) { element.SetValue(LineBreakBeforeProperty, value); } public static bool GetLineBreakBefore(UIElement element) { return (bool)element.GetValue(LineBreakBeforeProperty); } protected override Size MeasureOverride(Size availableSize) { var totalWidth = 0.0; var totalHeight = 0.0; var rowHeight = 0.0; var rowWidth = 0.0; foreach (UIElement uiElement in this.Children) { uiElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); // 当行宽超过了可用空间的宽度或者子元素设置了换行的附加属性时换行 if (rowWidth + uiElement.DesiredSize.Width >= availableSize.Width || GetLineBreakBefore(uiElement)) { // 面板的总宽度是全部行的最大宽度 totalWidth = Math.Max(totalWidth, rowWidth); // 面板的高度是全部行的高度之和 totalHeight += rowHeight; // 换行后重置行高和行宽 rowHeight = 0.0; rowWidth = 0.0; } else { // 每一行的宽度是全部子元素宽度之和 rowWidth += uiElement.DesiredSize.Width; // 每一行的高度都是这一行中全部子元素的最大高度 rowHeight = Math.Max(rowHeight, uiElement.DesiredSize.Height); } } // 加上最后一行的高度 totalHeight += rowHeight; return new Size(totalWidth, totalHeight); } protected override Size ArrangeOverride(Size finalSize) { var x = 0.0; var y = 0.0; var rowHeight = 0.0; foreach (UIElement uie in this.Children) { // 若是该子元素将要超出边界或者设置了换行,则换一行从头显示 if (x + uie.DesiredSize.Width >= finalSize.Width || GetLineBreakBefore(uie)) { x = 0.0; y += rowHeight; // 重置行高 rowHeight = 0.0; } uie.Arrange(new Rect(x, y, uie.DesiredSize.Width, uie.DesiredSize.Height)); rowHeight = Math.Max(rowHeight, uie.DesiredSize.Height); x = x + uie.DesiredSize.Width; } return finalSize; } }
参考出处:http://www.cnblogs.com/ShengM/p/4118207.html