WPF佈局

概述

學習了下WPF裏面的佈局,參考書是《WPF揭祕》,如下是筆記編程

WPF佈局

佈局是WPF界面開發中一個很重要的環節。所謂佈局,即肯定全部控件的大小和位置,是一種遞歸進行的父元素(Panel)和子元素交互的過程,爲了同時知足父元素和子元素的須要,WPF採用了一種包含測量(Measure)和排列(Arrange)兩個步驟的解決方案。子元素最終所佔用的空間和位置是由父元素肯定的(RenderSize),可是父元素會先參考子元素的意見(DesiredSize)。下面來看看子元素怎樣給出意見(控制尺寸、控制位置、變換)以及父元素怎樣作決定app

控制尺寸

1. 高度和寬度異步

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*高ide

FrameworkElement還有一些與尺寸有關的只讀屬性:DesiredSize、ActualWidth和ActualHeight、RenderSize;DesiredSize是基於以上屬性計算出來的,由父元素(Panel)在佈局過程當中使用的;RenderSize則是佈局結束後元素的尺寸,ActualWidth和ActualHeight與之相同。因爲佈局操做是異步的,RenderSize的值會晚於Height、Width等基本屬性的值,因此依賴RenderSize是不可靠的;UIElement中有一個強制完成佈局的方法UpdateLayout(),但因爲它會影響性能,並且不能保證正在使用的元素會被正常渲染,因此通常不用佈局

2. Margin和Padding性能

FrameworkElement.Margin:控制元素邊界外的空間學習

Control.Padding:控制元素邊界內的空間ui

 

3. Visibilitythis

Visible:元素可見,並參與佈局spa

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)或者擁有子元素的控件上

變換(Transform)

 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(面板)

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>
View Code

後臺代碼以下:

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);
            }
        }
    }
}
View Code

兩步佈局過程

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;
    }
}
View Code
相關文章
相關標籤/搜索