浅谈 WPF布局

咱们首先来了解一下图形化用户界面(Graphic User Interface)也就是咱们经常听到的GUI。举个简单的例子,一样是数据,咱们能够用控制台程序加格式控制符等输出,可是这些都不如GUI来的友好和方便。html

WPF相对于其它只能使用编程语言进行UI设计,具备专门用于UI设计的XAML,而且可以确保界面布局能恰倒好处的适应不一样的窗口尺寸。编程

咱们来查看Window和Page的源码,发现Window的间接基类ContentControl和Page类都使用了一个object类型的Content属性。因此它只能包含一个元素,可是咱们想放置多个元素怎么办呢?这个时候咱们就须要使用布局容器。编程语言

为何布局容器又能够添加多个元素呢?全部的WPF布局容器都派生自System.Windows.Controls.Panel这个抽象类。咱们从类层次结构图中就不难发现它所包含的一系列布局容器。咱们再来看看这个Panel类的源代码,就会发现该类有一个ContentProperty("Children")特性,因此咱们会在该类中找到一个UIElementCollection类型(该类实现了IList接口)的Children属性。只要是继承自UIElement的类,均可以添加到布局容器中。布局

下面让咱们来看看其中的一些布局容器以及与之相关联的控件属性。post

 

StackPanelurl

它是最简单的布局容器,容许子元素在单行或者单列以堆栈的形式放置。spa

    <StackPanel Orientation="Vertical" Background="LightCyan">
        <Label Background="LemonChiffon">A Button Stack</Label>
        <Button>Button A</Button>
        <Button>Button B</Button>
        <Button>Button C</Button>
    </StackPanel>

咱们运行会发现,StackPanel容器会充满整个窗口,由于它的HorizontalAlignment、VerticalAlignment属性值都是Stretch。设计

子元素是按照自上而下的方式排列的,由于咱们设置了Orientation属性(默认值就是Vertical,因此这里咱们能够不设置该属性);若是设置成Horizontal,就会从左到右排列。在默认状况下,每一个子元素的高度都适合与它们本身内容的高度。若是是Horizontal,每一个子元素的宽度都适合它们本身内容的长度。code

咱们拉伸运行的WPF程序,会发现StackPanel始终会充满整个窗口,子元素也会根据设置作相应的变化。orm

咱们能够设置子元素的一些属性配合布局容器的属性,来决定布局。常见的属性有:HorizontalAlignment、VerticalAlignment、Margin、MinWidth、MinHeight、MaxWidth、MaxHeight、Height、Width。

<Window x:Class="WPFDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="200" Height="350">
      
    <StackPanel Background="LightCyan">
        <Label Background="LemonChiffon">A Button Stack</Label>
        <Button HorizontalAlignment="Left">Button A</Button>
        <Button HorizontalAlignment="Center">Button B</Button>
        <Button HorizontalAlignment="Right">Button C</Button>
        <Button Margin="5" Height="40" Width="100" HorizontalAlignment="Right">Button D</Button>
        <Button MinWidth="320">Button E</Button>
    </StackPanel>
    
</Window>

运行这个程序,咱们就会发现A、B、C三个Button分别位于水平方向上的不一样位置;Button D距离四周的元素有5个单位的距离,而且指定了该按钮的高度和宽度;Button E指定了MinWidth,远大于Window的200,因此刚运行的时候左边被截断,须要咱们经过拉伸才可彻底看见。

 

Grid

顾名思义,Grid会以网格的形式对内容元素们进行布局。它具备以下的特色:

 · 能够经过RowDefinitions和ColumnDefinitions属性,它们分别是RowDefinition和ColumnDefinition集合。定义任意多的行和列。

 · 行的高度,列的宽度可使用绝对数值(double数值加单位后缀px、in、cm、pt,不过在设置的时候最好不要加单位),比例值(double数值后加一个*号),自动值(字符串Auto)来灵活设置。

 · 内部元素可使用Grid的附加属性Grid.Row、Grid.Column、Grid.RowSpan、Grid.ColumnSpan设置本身所在的行、列、纵向跨几行、横向跨几列。

 · 能够设置子元素的对齐方向。

    <Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True" Width="250" Height="100">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
            <RowDefinition Height="1.5*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock FontSize="20" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="0">2013 Products Shipped</TextBlock>
        <TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center">Quarter 1</TextBlock>
        <TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center">Quarter 2</TextBlock>
        <TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Center">Quarter 3</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">50000</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">100000</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">150000</TextBlock>
        <TextBlock FontSize="16" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="3">Total Units: 300000</TextBlock>
    </Grid>

在这个示例中,咱们还设置了ShowGridLines属性,它会生成虚线的边框。咱们能够修改该边框,具体能够查看陈希章老师的这篇文章:为WPF和Silverlight的Grid添加边框。

咱们还可使用UseLayoutRounding属性来控制布局舍入。

咱们还可使用GridSplitter来拖动分割窗口。

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="200"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Button>1</Button>
        <Button Grid.Column="2">2</Button>
        <Button Grid.Row="2">3</Button>
        <Button Grid.Column="2" Grid.Row="2">4</Button>

        <GridSplitter Grid.Column="1" Grid.Row="0" Grid.RowSpan="3" Width="2" VerticalAlignment="Stretch" HorizontalAlignment="Center" ShowsPreview="True"></GridSplitter>

    </Grid>

这是一个左右拖动的分割窗口,因此咱们把GridSplitter竖立放置铺满(VerticalAlignment="Stretch"),并跨行(Grid.RowSpan="3"),还要设置它的宽度Width(这样才可见);最后还要设置列的宽度为自动值Auto。咱们还看到ShowsPreview属性为True,这个是当咱们拖动分割条时,会有一个会射的阴影跟随鼠标运行,以显示预览。同理,我能够设置上下拖动的分割线,这里就不过多叙述了。

还有一个比较有意思的是SharedSizeGroup特性。

    <Grid Margin="3">
        <StackPanel>
            <StackPanel Grid.IsSharedSizeScope="True">
                <Grid Grid.Row="0" Margin="3" Background="LightYellow" ShowGridLines="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" SharedSizeGroup="TextLabel"></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" >Some text in between the two grids...</Label>
                <Grid Grid.Row="2" Margin="3" Background="LightYellow"  ShowGridLines="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" SharedSizeGroup="TextLabel"></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                    </Grid.ColumnDefinitions>

                    <Label Margin="5">Short</Label>
                    <TextBox Grid.Column="1" Margin="5">A text box</TextBox>
                </Grid>
            </StackPanel>
        </StackPanel>
    </Grid>

咱们能够设置须要共享尺寸的列ColumnDefinition或者行RowDefinition的SharedSizeGroup属性,把他们都设置成同一个值;而后再把Grid的IsSharedSizeScope附加属性设置到父容器上,并设置为True。(本例嵌套了2个StackPanel就是为了说明IsSharedSizeScope的设置方式,其实彻底没有必要嵌套2个StackPanel,直接就能够设置在Grid上)

还有一个更简单的网格布局容器,UniformGrid,它能够直接设置Rows、Columns属性,设置行列数;它没有Rows、Columns这些附加属性,是按照子元素的添加的顺序由左到右,由上到下排列的。

 

Canvas

直译就是画布的意思。它一般用于一些设计基本不会再有改动的小型布局。

Canvas布局容器是最轻量级的布局容器,它没有包含负责的布局逻辑,以改变其子元素的首选尺寸。

咱们能够设置Canvas.Left、Canvas.Top(或者Canvas.Right、Canvas.Buttom)附加属性来定位子元素的位置。

    <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" Canvas.ZIndex="1">(60,80)</Button>
        <Button Canvas.Left="70" Canvas.Top="120" Width="100" Height="50">(70,120)</Button>
        <Button Canvas.Left="80" Canvas.Top="150" Width="100" Height="50">(60,80)</Button>
    </Canvas>

默认状况下,全部子元素的都具备相同的ZIndex属性值0;若是子元素的ZIndex值相同,就按照他们在Canvas中添加的前后顺序进行显示。因此上一个示例中,最后一个Button会显示在倒数第二个Button上面;第三个由于咱们显示设置了ZIndex的值为1,大于默认值0,因此第三个在第四个上面。

 

这一节咱们讲了3个很是具备表明性的布局容器,固然须要作好布局是一件很是困难的事情,须要考虑全面。因此还须要咱们真正动手作的时候,不断的积累经验。这里只是一个简单的介绍布局容器。下一节,咱们会来介绍一下WPF的控件。