一. 摘要
首先非常高兴这个系列能得到大家的关注和支持。这段时间一直在研究Windows Azure。所以暂缓了更新。同一时候也本着想把它写好、宁缺毋滥的精神,在速度上自然也就慢了下来,这篇文章拖拖拉拉也经历了十多天才公布出来(每天写一点),只是请大家放心。这个系列一定会继续写下去。因为自己才疏学浅且是对这些技术的使用总结和心得体会,错误之处在所难免。怀着技术交流的心态,在这里发表出来。所以希望大家可以多多指点。这样在使一部分人受益的同一时候也能纠正我的错误观点,以便和各位共同提高。
这篇文章主要是对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#两种方式实现同一个功能)、怎样开发自己定义的布局控件。也就是本文所要讨论的范畴:
Panel详细继承关系详见以下类图:
如上图,公共属性太多了。就简介几个常见的属性:Margin是元素与其它元素的外边距;Padding是指在本元素内部的元素内容与边缘的距离。前面这两个元素基本和ASP.NE中的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...的布局,如此循环直到子控件的布局完毕后,再完毕父控件的布局,最后递归回去直到递归结束,这样整个布局过程就完毕了.
布局系统为 Children 集合的每一个成员完毕两个处理过程:測量处理过程(Measure)和排列处理过程(Arrange)。每一个子 Panel 均提供自己的 MeasureOverride 和 ArrangeOverride 方法,以实现自己特定的布局行为。
四. Canvas
Canvas比較简单,仅仅是一个存储元素的容器,它不会自己主动调整内部元素的排列及大小。不指定元素位置,元素将默认显示在画布的左上方。
Canvas的主要用途是用来绘图。
Canvas默认不会自己主动裁减超过自身范围的内容。即溢出的内容会显示在Canvas外面。这是由于默认 ClipToBounds="False";我们能够通过设置ClipToBounds="True来裁剪多出的内容。
要实现的效果例如以下图(用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.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是一个很easy的面板。从左至右按顺序位置定位子元素,假设排满断开至下一行。兴许排序依照从上至下或从右至左的顺序进行。WrapPanel面板也提供了 Orientation属性设置排列方式。这跟上面的StackPanel基本相似。
不同的是WrapPanel会依据内容自己主动换行。
要实现的效果例如以下图(用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.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.ColumnDefinitions>列元素集和<Grid.RowDefinitions>行元素集合两种元素组成。而放置在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>
第一种。固定长度——宽度不够,会裁剪。不好用。
单位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); } } }
九 UniformGrid
介绍了前面的Grid,接下来的这个UniformGrid 就太简单了,均布网格的是Grid的简化版本号,每一个单元格的大小同样。不用在定义行列集合。
均布网格每一个单元格仅仅能容纳一个元素,将自己主动依照定义在其内部的元素个数。自己主动创建行列,并通常保持同样的行列数。
要实现的效果例如以下图(用XAML和C#实现同一效果):
XAML代码实现:
<Window x:Class="WPFLayoutDemo.UniformGridDEMO" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="UniformGridDEMO" Height="300" Width="300"> <UniformGrid Columns="2" Rows="2" Name="uniformGrid1" > <Rectangle Margin="10,10,10,10" Fill ="Gray"/> <Rectangle Margin="10,10,10,10" Fill ="Gray" /> <Rectangle Margin="10,10,10,10" Fill ="Gray" /> <Rectangle Margin="10,10,10,10" Fill ="Gray" /> </UniformGrid> </Window>
C#代码实现:
namespace WPFLayoutDemo { public partial class UniformGridDEMOCodeBehind : Window { public UniformGridDEMOCodeBehind() { InitializeComponent(); UniformGrid wp = new UniformGrid(); //把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.Gray); r.Margin = new Thickness(10, 10, 10, 10); wp.Children.Add(r); } } } }
十. ViewBox
ViewBox这个控件通常和其它控件结合起来使用,是WPF中很实用的控制。
定义一个内容容器。该容器可拉伸和缩放单个子元素以填满可用空间。一个 Viewbox 仅仅能具有一个 Child。假设加入一个附加 Child。会导致一个执行时 ArgumentException错误。
我们用得最多的首先是Stretch属性,然后是StrctchDirection属性,关于这两个元素。大家能够执行我们的代码。然后改变设置就能够看到效果。
要实现的效果例如以下图(用XAML和C#实现同一效果):
XAML代码实现:
<Window x:Class="WPFLayoutDemo.ViewBoxDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ViewBoxDemo" Height="342" Width="535"> <Viewbox Stretch="Uniform"> <Button Content="Hello,Knights Warrior" /> </Viewbox> </Window>
C#代码实现:
namespace WPFLayoutDemo { public partial class ViewBoxDEMOBehind : Window { public ViewBoxDEMOBehind() { this.InitializeComponent(); Viewbox vb = new Viewbox(); vb.Stretch = Stretch.Uniform ; //把vb加入为窗口的子控件 this.Content = vb; //Button1 Button b1 = new Button(); b1.Content = "Hello,Knights Warrior"; vb.Child=b1; } } }
十一. Border
Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中仅仅能有一个子控件(这个子控件又能够包括多个子控件)。Border 的几个重要属性:Background:用用一个 Brush 对象来绘制背景 ;BorderBrush:用一个Brush 对象来绘制边框 ;BorderThickness:此属性设置 Border 边框的大小;CornerRadius:此属性设置 Border 的每个角圆的半径。Padding:此r属性设置 Border 里的内容与边框的之间的间隔。
要实现的效果例如以下图(用XAML和C#实现同一效果):
XAML代码实现:
<Window x:Class="WPFLayoutDemo.BorderDEMO" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="BorderDEMO" Height="300" Width="300"> <Border BorderThickness="5" BorderBrush="Green" CornerRadius="10" Background="LightGray" HorizontalAlignment="Left" VerticalAlignment="Top" Width="270" Height="250"> <Canvas Background="LightCyan" > <Rectangle Canvas.Left="30" Canvas.Top="20" Height="200" Width="200" Stroke="Black" StrokeThickness="10" Fill="Red" /> </Canvas> </Border> </Window>
C#代码实现:
namespace WPFLayoutDemo { public partial class BorderDEMOCodeBehind : Window { public BorderDEMOCodeBehind() { InitializeComponent(); Border border = new Border(); border.Background = new SolidColorBrush(Colors.LightGray); border.BorderThickness = new Thickness(5); border.BorderBrush = new SolidColorBrush(Colors.Green); border.CornerRadius = new CornerRadius(15); border.Width = 270; border.Height = 250; Canvas cnvas = new Canvas(); Rectangle rect = new Rectangle(); rect.Width = 200; rect.Height = 200; rect.Fill = new SolidColorBrush(Colors.Black); rect.StrokeThickness = 10d; cnvas.Children.Add(rect); border.Child = cnvas; this.Content = border; } } }
十二. ScrollViewer
通经常使用户界面中的内容比计算机屏幕的显示区域大,大出的部分就会破坏原有的布局。
利用 ScrollViewer 控件能够方便地使应用程序中的内容具备滚动功能。
这样大出的部分就能够正常显示出来了。经常使用属性、事件和继承关系见以下类图:
要实现的效果例如以下图(用XAML和C#实现同一效果):
XAML代码实现:
<Window x:Class="WPFLayoutDemo.ScrollViewerDEMO" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ScrollViewerDEMO" Height="300" Width="300"> <Grid> <ScrollViewer> <Rectangle Width="500" Height="500" Fill="Gray"></Rectangle> </ScrollViewer> </Grid> </Window>
C#代码实现:
namespace WPFLayoutDemo { public partial class ScrollViewerDEMOCodeBehind : Window { public ScrollViewerDEMOCodeBehind() { InitializeComponent(); ScrollViewer myScrollViewer = new ScrollViewer(); myScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; Rectangle myRectangle = new Rectangle(); myRectangle.Fill = Brushes.Gray; myRectangle.Width = 500; myRectangle.Height = 500; myScrollViewer.Content = myRectangle; this.Content = myScrollViewer; } } }
十三.布局综合应用
前面通过十多个小节讲了一些经常使用Panel的基本使用方法。那我们这里就简单做一个综合的小样例,通过这个样例,旨在巩固前面的内容。也能够当做一个举一反三的过程。要实现的效果例如以下图:
XAML代码实现:
<Window x:Class="WPFLayoutDemo.PuttingItAllTogether" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStartupLocation="CenterScreen" Title="布局综合运用" Width="640" Height="480" > <DockPanel Width="Auto" Height="Auto" LastChildFill="True"> <!--Top Menu Area--> <Menu Width="Auto" Height="20" Background="LightGray" DockPanel.Dock="Top"> <!-- File Menu --> <MenuItem Header="文件"> <MenuItem Header="保存"/> <Separator/> <MenuItem Header="退出"/> </MenuItem> <!-- About Menu --> <MenuItem Header="帮助"> <MenuItem Header="关于本产品"/> </MenuItem> </Menu> <!--State --> <StackPanel Width="Auto" Height="31" Background="LightGray" Orientation="Horizontal" DockPanel.Dock="Bottom"> <Label Width="155" Height="23" Content="状态栏" FontFamily="Arial" FontSize="10"/> </StackPanel> <!--Left--> <StackPanel Width="136" Height="Auto" Background="Gray"> <Button Margin="5,5,5,5" Width="Auto" Height="26" Content="导航栏"/> <Button Width="126" Height="26" Content="导航栏" Margin="5,5,5,5"/> <Button Width="126" Height="26" Content="导航栏" Margin="5,5,5,5"/> </StackPanel> <!--Right--> <Grid Width="Auto" Height="Auto" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Rectangle Fill="Gray" Margin="10,10,10,10" Grid.Row="0" Grid.Column="0"/> <Rectangle Fill="Gray" Margin="10,10,10,10" Grid.Row="0" Grid.Column="1"/> <Rectangle Fill="Gray" Margin="10,10,10,10" Grid.Row="1" Grid.Column="0"/> <Rectangle Fill="Gray" Margin="10,10,10,10" Grid.Row="1" Grid.Column="1"/> </Grid> </DockPanel> </Window>
其有用熟练上面的各个布局控件以后。你会发现布局UI是一件非常easy的事。遇到一个新的UI,你会发现随意一个Panel都能够实现你的需求。
当然对于较复杂且对要求非常高的UI。我们也会自己定义一些Panel。在以下我们就简介一下自己定义布局控件。
十四.自己定义布局控件
讲到自己定义布局控件。我们必须得先谈一下在WPF中自己定义控件,在WPF自己定义控件你能够选择下图的一些基类作为继承对象,你也能够继承自已有的一些控件,这个就看你的须要了。事实上开发WPF自己定义控件和开发WinForm、ASP.NET自己定义控件基本类似。仅仅是要注意一些特别的地方,比方依赖属性的处理、路由事件、视觉树和逻辑树等等。
于今天仅仅是讲怎样开发一个自己定义的Panel,所以在清楚了基类的前提下,首先得了解它有哪些属性和事件,这样就能够确定哪些是不须要单独写、哪些是须要override。
下图就是Panel和基类FrameworkElement 的类图:
在清楚了上面这张图以后,我们就能够着手開始写了,我们知道布局系统的工作原理是先測量后排列,測量就是确定面板须要多大空间,排列则是定义其面板内子元素的排列规则。
自己定义面板要继承自Panel类并重写MeasureOverride和rrangeOverride方法就可以。例如以下便是一个简单的自己定义Panel:
namespace WPFLayoutDemo { public class PlotPanel : Panel { public PlotPanel() : base() { } //重写默认的Measure方法 protected override Size MeasureOverride(Size availableSize) { Size panelDesiredSize = new Size(); foreach (UIElement child in InternalChildren) { child.Measure(availableSize); panelDesiredSize = child.DesiredSize; } return panelDesiredSize; } //重写默认的Arrange方法 protected override Size ArrangeOverride(Size finalSize) { foreach (UIElement child in InternalChildren) { double x = 50; double y = 50; child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); } return finalSize; } } }
控件的终于大小和位置是由该控件和父控件共同完毕的,父控件会先给子控件提供可用空间(availableSize),子控件再反馈给父控件一个自己的期望值(DesiredSize),父控件最后依据自己所拥有的空间大小与子控件的期望值分配一定的空间给子控件并返回自己的大小.那么这个过程就是通过MeasureOverride 和ArrangeOverride这两个方法来完毕(注意父控件的availableSize是减去Margin、padding等的值)。
本来想自己开发一个较复杂的Panel控件放上来,但一搜网络。发现已经有非常多非常好的Panel控件。所以在这里我也不写那么多了,大家能够研究一下这些控件,我也研究了几个,认为最好理解且最美观的当属“FishEyePanel & FanPanel, Paul Tallett, codeproject ”,大家能够依据链接过去看一下,Paul Tallett解说得很的仔细。
- TreeMapPanel, Kevin Moore (see bag-o-tricks for code)
- AnimatingTilePanel, Kevin Moore (see bag-o-tricks for code)
- Disposing Virtualizing Stack Panel, Aaron, WiredPrairie.us
- TimeLinePanel, Rob Zelt, robzelt.com (with credit to Robert Ingebretsen and Lauren Lavoie)
- Chart and Lens Panel by John Stewien (code available?)
- DiagonalPanel
- FishEyePanel & FanPanel, Paul Tallett, codeproject
- RadiaPanel & ItemsRadialPanel, Rhett log (Henry Hahn posted a Radial panel in 2005, but I'm not sure if it runs or not?)
- DisclaimerPanel, Chaz
- SpanningStackPanel, Nick Theusen
- PlotPanel, Windows SDK Sample
- CollapsiblePanel, Thomas Lebrun
- CornerStacker, Nick Thuesen
- StickyPanel, Unni, Blend PM
- ItemSkimmingPanel, Pavan Podila
顺便也链接两幅图:
说到这里,我们也顺便提一下写WPF自己定义控件的几个步骤。以后在说到这一节的时候会具体解说:
- 首先你得清楚你的自己定义控件是干什么用的(能解决什么问题)?公用到什么程度(其它项目也能够用、本项目用、项目其中一个模块用、仅仅有一个地方用)?是继承已有的控件还是从头写?对设计时是否支持?样式和模板的定义等。
- 确定好了上面的步骤后,我们就能够建立项目的结构,类和资源文件等该放在什么位置也就在这一步确定。
- 选择要继承的基类(UIElement、FrameworkElement 、Control 、ContentControl 、HeaderedContentControl 、ItemsControl 、Selector 、RangeBase还是已有的一些控件)。
- 重写默认的样式和新建一些样式并附默认值。
- 因为WPF的属性基本都是依赖属性,所以我们也要新建一些依赖属性。
- 逻辑树和视觉树的一些处理以及事件等。
十五.本文总结
今天我们主要讲了WPF布局系统,对整个布局系统的原理、各个Panel的基本使用方法(分别用XAML和C#两种方式实现同一个功能。便于大家学习)以及自己定义布局控件做了一些介绍。因为内容太多,我仅仅是力所能及的做一些相关的介绍和演示,所以仅仅能给大家提供一个參考。假设大家想了解很多其它,还须要去看专门的教材,同一时候有些知识也仅仅是个人的一些见解,所以大家仅仅能将就着看了。
写篇文章也是怀着技术交流的心态公布出来,因为是自己对这些技术的使用总结和心得体会,错误之处在所难免。所以希望大家可以多多指点,这样也能纠正我的错误观点,以便和各位共同提高!
最后假设大家感兴趣,能够关注WPF 基础到企业应用系列索引这个系列文章。我也会不定期的逐渐更新。谢谢各位的关注。也欢迎和各位交流讨论。