技术开发 频道

WPF企业应用基础:布局全接触

      【IT168 技术文档】这篇文章主要是对WPF布局系统做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,再怎么强大的功能也会显得这个软件很脆弱且没有投资价值。

  一. 摘要

  本文以总分总的形式展开介绍:首先对WPF Panel做一个总体认识、然后讲解各Panel基本用法(分别用XAML和C#两种方式实现同一个功能,便于大家学习)、布局综合应用、自定义布局控件以及最后的总结,希望对大家有所帮助。

  二. 本文提纲

  · 1.摘要

  · 2.本文提纲

  · 3.总体介绍

  · 4.Canvas

  · 5.StackPanel

  · 6.WrapPanel

  · 7.DockPanel

  · 8.Grid

  · 9.UniformGrid

  · 10.ViewBox

  · 11.Border

  · 12.ScrollViewer

  · 13.布局综合应用

  · 14.自定义布局控件

  · 15.本文总结

  · 16.系列进度

  · 17.相关代码

  三. 总体介绍

  WPF的布局控件都在System.Windows.Controls.Panel这个基类下面,使用 Panel 元素在WPF应用程序中放置和排列子对象。它具体包括哪些布局控件以及如何使用这些布局控件(分别用XAML和C#两种方式实现同一个功能)、如何开发自定义的布局控件,也就是本文所要讨论的范畴:

1

  Panel具体继承关系详见下面类图:

1

  如上图,公共属性太多了,就简单介绍几个常见的属性:Margin是元素与其停放父元素的间距;Padding是指在本元素内部的元素内容与边缘的距离;FlowDirection属性标示元素的内容显示方向;Panel.ZIndex是相对于显示屏的Z轴坐标,用于调整层叠元素的显示先后;RenderTransform和LayoutTransform用来将缩放和旋转的变换应用到某个元素上。

  一个Panel 的呈现是测量和排列Children子元素、然后在屏幕上绘制它们的过程。所以在布局的过程中会经过一系列的计算,那么Children 越多,执行的计算次数就越多。如果不需要较为复杂的 Panel(如 Grid和自定义复杂的Panel),则可以使用构造相对简单的布局(如 Canvas、UniformGrid等),这种布局可带来更好的性能。 如果有可能,我们应尽量避免不必要地调用 UpdateLayout方法。

  每当Panel内的子元素改变其位置时,布局系统就可能触发一个新的处理过程。对此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差。

  换句话说,布局是一个递归系统,实现在屏幕上对元素进行大小调整、定位和绘制,然后进行呈现。具体如下图,要实现控件0的布局,那么先要实现0的子控件01,02...的布局,要实现01的布局,那么得实现01的子控件001,002...的布局,如此循环直到子控件的布局完成后,再完成父控件的布局,最后递归回去直到递归结束,这样整个布局过程就完成了.

1

  布局系统为 Children 集合的每个成员完成两个处理过程:测量处理过程(Measure)和排列处理过程(Arrange)。每个子 Panel 均提供自己的 MeasureOverride 和 ArrangeOverride 方法,以实现自己特定的布局行为。

  四. Canvas

  Canvas比较简单,只是一个存储元素的容器,它不会自动调整内部元素的排列及大小。不指定元素位置,元素将默认显示在画布的左上方。Canvas的主要用途是用来画图。Canvas默认不会自动裁减超过自身范围的内容,即溢出的内容会显示在Canvas外面,这是因为默认 ClipToBounds="False";我们可以通过设置ClipToBounds="True来裁剪多出的内容。

2

  要实现的效果如下图(用XAML和C#实现同一效果):

2

  XAML代码实现:

<Window    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    

xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"    x:Class="WPFLayoutDemo.CanvasDEMO"    x:Name="Window"  
  Title
="CanvasDEMO"    WindowStartupLocation="CenterScreen"     Width="640"
Height
="480">      
  
<Canvas Margin="0,0,0,0" Background="White">        
  
<Rectangle Fill="Red"                 Stroke="Azure"                 Width="209"                 Height="159"            
     Canvas.Left
="310" Canvas.Top="181"/>        
  
<Ellipse Fill="Azure"                 Stroke="Green"              
   Width
="258"
Height
="97"                 Panel.ZIndex="1"                 Canvas.Left="165"
Canvas.Top
="145"/>      
</Canvas>
</Window>

    C#代码实现:

namespace WPFLayoutDemo{  
  
public partial class CanvasDEMOCodeBehind    {        
public CanvasDEMOCodeBehind()        {            
this.InitializeComponent();      
     Canvas canv
= new Canvas();        
    
//把canv添加为窗体的子控件              this.Content = canv;          
canv.Margin
= new Thickness(0, 0, 0, 0);          
canv.Background
= new SolidColorBrush(Colors.White);                      
  
//Rectangle          
Rectangle r
= new Rectangle();            r.Fill = new SolidColorBrush(Colors.Red);        
    r.Stroke
= new SolidColorBrush(Colors.Red);        
    r.Width
= 145;      
   r.Height
= 126;          
  r.SetValue(Canvas.LeftProperty, (
double)124);        
   r.SetValue(Canvas.TopProperty, (
double)122);          
canv.Children.Add(r);          
  
//Ellipse        
   Ellipse el
= new Ellipse();            el.Fill = new SolidColorBrush(Colors.Azure);            
el.Stroke
= new SolidColorBrush(Colors.Azure);          
el.Width
= 121;        
   el.Height
= 100;          
  el.SetValue(Canvas.ZIndexProperty,
1);            
el.SetValue(Canvas.LeftProperty, (
double)195);            
el.SetValue(Canvas.TopProperty, (
double)191);          
  canv.Children.Add(el);      
  }  
}
}

  

  五. StackPanel

  StackPanel就是将子元素按照堆栈的形式一一排列,通过设置面板的Orientation属性设置了两种排列方式:横排(Horizontal默认的)和竖排(Vertical)。纵向的StackPanel默认每个元素宽度与面板一样宽,反之横向亦然。如果包含的元素超过了面板空间,它只会截断多出的内容。 元素的Margin属性用于使元素之间产生一定得间隔,当元素空间大于其内容的空间时,剩余空间将由HorizontalAlignment和VerticalAlignment属性来决定如何分配。其他属性,大家可以看看如下类图:

  要实现的效果如下图(用XAML和C#实现同一效果):

  XAML代码实现:

<Window    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
  
x:Class
="WPFLayoutDemo.StackPanelDEMO"    x:Name="Window"  
Title
="StackPanelDEMO"  
WindowStartupLocation
="CenterScreen"     Width="640" Height="480">  
<StackPanel Margin="0,0,0,0" Background="White" Orientation="Vertical">    
    
<Button Content="Top of Stack"/>  
    
<Button Content="Middle of Stack"/>    
  
<Button Content="Bottom Of Stack"/>    </StackPanel>
</Window>

   C#代码实现:

namespace WPFLayoutDemo{  
  
public partial class StackPanelDEMOCodeBehind
   {      
public StackPanelDEMOCodeBehind()        {          
  this.InitializeComponent();            StackPanel sp
= new StackPanel();  
          
//把sp添加为窗体的子控件            this.Content = sp;          
  sp.Margin
= new Thickness(0, 0, 0, 0);        
   sp.Background
= new SolidColorBrush(Colors.White);          
sp.Orientation
= Orientation.Vertical;      
      
//Button1      
      Button b1
= new Button();            b1.Content = "Top of Stack";            sp.Children.Add(b1);          
//Button2          
  Button b2
= new Button();            b2.Content = "Middle of Stack";            sp.Children.Add(b2);          
  
//Button3          
  Button b3
= new Button();            b3.Content = "Bottom of Stack";            sp.Children.Add(b3);        
}    
}
}

   

  六. WrapPanel

  WrapPanel是一个非常简单的面板,从左至右按顺序位置定位子元素,如果排满断开至下一行。后续排序按照从上至下或从右至左的顺序进行。WrapPanel面板也提供了 Orientation属性设置排列方式,这跟上面的StackPanel基本相似。不同的是WrapPanel会根据内容自动换行。

4

  要实现的效果如下图(用XAML和C#实现同一效果):

4

  XAML代码实现:

<Window    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    x:Class="WPFLayoutDemo.WrapPanelDEMO"    x:Name="Window"  
  Title
="WrapPanelDEMO"    WindowStartupLocation="CenterScreen"     Width="640" Height="480">  
<WrapPanel Margin="0,0,0,0" Background="White">          
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>        
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>              
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>        
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>          
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>          
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>        
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>        
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>      
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>      
<Rectangle Margin="10,10,10,10" Fill ="Azure" Width="60" Height="60"/>  
</WrapPanel>
</Window>

   C#代码实现:

namespace WPFLayoutDemo{
  
public partial class WrapPanelDEMOCodeBehind
{      
public WrapPanelDEMOCodeBehind()        {        
this.InitializeComponent();            WrapPanel wp
= new WrapPanel();          
  
//把wp添加为窗体的子控件        
this.Content
= wp;        
wp.Margin
= new Thickness(0, 0, 0, 0);          
wp.Background
= new SolidColorBrush(Colors.White);    
  
//遍历增加Rectangles      
Rectangle r;        
for (int i = 0; i <= 10; i++)            {                
r
= new Rectangle();          
  r.Fill
= new SolidColorBrush(Colors.Azure);            
    r.Margin
= new Thickness(10, 10, 10, 10);              
r.Width
= 60;              
r.Height
= 60;            
   wp.Children.Add(r);          
}              
}  
}
}

   

  七. DockPanel

  DockPanel定义一个区域,在此区域中,您可以使子元素通过描点的形式排列。停靠面板其实就是在WinForm类似于Dock属性的元素。DockPanel会对每个子元素进行排序,并停靠在面板的一侧,多个停靠在同侧的元素则按顺序排序,最后一个元素填充这个Panel(这个需要设置LastChildFill属性为 True)。对于在DockPanel中的元素的停靠属性可以通过Panel.Dock的附加属性来设置.

  要实现的效果如下图(用XAML和C#实现同一效果):

   XAML代码实现:

<Window    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    x:Class="WPFLayoutDemo.DockPanelDEMO"    x:Name="Window"  
Title
="DockPanelDEMO"    WindowStartupLocation="CenterScreen"    Width="640" Height="480">  
<DockPanel Width="Auto" Height="Auto" LastChildFill="True">      
<Rectangle Fill="Beige" Stroke="BlanchedAlmond" Height="180" DockPanel.Dock="Top"/>    
<Rectangle Fill="Azure" Stroke="Orange"
/>  
</DockPanel>
</Window>

   C#代码实现:

namespace WPFLayoutDemo{  
  
public partial class DockPanelDEMOCodeBehind  
{    
  
public DockPanelDEMOCodeBehind()        {          
  this.InitializeComponent();            DockPanel dp
= new DockPanel();            dp.LastChildFill = true;          
dp.Width
= Double.NaN;  
  
//这个就相当于在XAML中设置Width="Auto"          
  dp.Height
= Double.NaN;  
//这个就相当于在XAML中设置Height="Auto"          
//把dp添加为窗体的子控件        
this.Content
= dp;          
  
//添加Rectangles        
Rectangle rTop
= new Rectangle();          
rTop.Fill
= new SolidColorBrush(Colors.BlanchedAlmond);            rTop.Stroke = new SolidColorBrush(Colors.BlanchedAlmond);            rTop.Height = 180;          
dp.Children.Add(rTop);            rTop.SetValue(DockPanel.DockProperty,Dock.Top);            Rectangle rFill
= new Rectangle();            rFill.Fill = new SolidColorBrush(Colors.Azure);          
  rFill.Stroke
= new SolidColorBrush(Colors.Azure);          
  dp.Children.Add(rFill);  
      }  
  }
}

    八. Grid

  Grid和其他各个Panel比较起来,功能最多也最为复杂,它由列元素集和行元素集合两种元素组成。而放置在Grid面板中的控件元素都必须显示采用附加属性语法定义其放置所在的行和列,否则元素均默认放置在第0行第0列。由于Grid的组成并非简单的添加属性标记来区分行列,这也使得用户在实际应用中可以具体到某一单元格中,所以布局起来就很精细了。

  Grid的列宽与行高可采用固定、自动、按比列三种方式定义

<Grid>
  
<Grid.RowDefinitions>    
    
<RowDefinition Height="Auto" />  
      
<RowDefinition Height="Auto" />  
      
<RowDefinition Height="*" />
      
<RowDefinition Height="40" />  
  
</Grid.RowDefinitions>
  
<Grid.ColumnDefinitions>  
      
<ColumnDefinition Width="Auto" />  
    
<ColumnDefinition Width="300" />  
  
</Grid.ColumnDefinitions>
</Grid>

     

  使用GridSplit分割

  使用GridSplit控件结合Grid控件实现类似于WinForm中SplitContainer的功能,这个大家在WinForm当中经常用到,我们也不多做介绍。

  第一种,固定长度——宽度不够,会裁剪,不好用。单位pixel。

  第二种,自动长度——自动匹配列中最长元素的宽度。

  第三种,比例长度——*表示占用剩余的全部宽度;两行都是*,将平分剩余宽度;像上面的一个2*,一个*,表示前者2/3宽度。

  跨越多行和多列

<Rectangle Fill="Silver" Grid.Column="1" Grid.ColumnSpan="3"/>

   使用Grid.ColumnSpan和Grid.RowSpan附加属性可以让相互间隔的行列合并,所以元素也可以跨越多个单元格。

  使用GridSplit分割

<GridSplitter Height="6"
VerticalAlignment
="Stretch"
HorizontalAlignment
="Stretch"
               Grid.Row
="2" Grid.Column="2">
</GridSplitter>

   使用GridSplit控件结合Grid控件实现类似于WinForm中SplitContainer的功能,这个大家在WinForm当中经常用到,我们也不多做介绍。

 

  要实现的效果如下图(用XAML和C#实现同一效果):

 

  XAML代码实现:

<Window    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
x:Class
="WPFLayoutDemo.GridDEMO"  
  x:Name
="Window"    
Title
="GridDEMO"    WindowStartupLocation="CenterScreen"    Width="640" Height="480">
  
<Grid Width="Auto" Height="Auto" >  
      
<Grid.ColumnDefinitions>
          
<ColumnDefinition Width="139"/>
  
<ColumnDefinition Width="184*"/>
<ColumnDefinition Width="45*" />

<ColumnDefinition Width="250*"/>  
      
</Grid.ColumnDefinitions>  
              
<Rectangle Fill="Azure" Grid.ColumnSpan="2"
Margin
="0,0,21,0" />  
      
<Rectangle Fill="Silver" Grid.Column="1" Grid.ColumnSpan="3"/>  
  
</Grid>
</Window>

   C#代码实现:

namespace WPFLayoutDemo{  
  
public partial class GridDEMOCodeBehind    {      
public GridDEMOCodeBehind()    
    {          
  this.InitializeComponent();    
        Grid grid
= new Grid();
           grid.Width
= Double.NaN;  
//这个就相当于在XAML中设置

Width
="Auto"          
grid.Height
= Double.NaN;  
//这个就相当于在XAML中设置Height="Auto"          
//把grid添加为窗体的子控件  
          this.Content
= grid;  
        
//列一        
   ColumnDefinition cd1
= new ColumnDefinition();        
    cd1.Width
= new GridLength(139);          
  grid.ColumnDefinitions.Add(cd1);          
  
//列二            
ColumnDefinition cd2
= new ColumnDefinition();          
cd2.Width
= new GridLength(1, GridUnitType.Star);  
         grid.ColumnDefinitions.Add(cd2);      
    
//列三        
    ColumnDefinition cd3
= new ColumnDefinition();          
cd3.Width
= new GridLength(2, GridUnitType.Star);  
         grid.ColumnDefinitions.Add(cd3);            
//把单元格添加到grid中        
   Rectangle r1c1
= new Rectangle();
            r1c1.Fill
= new SolidColorBrush(Colors.Azure);        
   r1c1.SetValue(Grid.ColumnProperty,
0);          
r1c1.SetValue(Grid.RowProperty,
0);          
grid.Children.Add(r1c1);        
    Rectangle r1c23
= new Rectangle();        
   r1c23.Fill
= new SolidColorBrush(Colors.Silver);        
   r1c23.SetValue(Grid.ColumnProperty,
1);          
  r1c23.SetValue(Grid.ColumnSpanProperty,
2);          
grid.Children.Add(r1c23);    
    }  
  }
}
0
相关文章