|
一.摘要
首先很高興這個(gè)系列能得到大家的關(guān)注和支持,這段時(shí)間一直在研究Windows Azure,所以暫緩了更新,同時(shí)也本著想把它寫好、寧缺毋濫的精神,在速度上自然也就慢了下來(lái),這篇文章拖拖拉拉也經(jīng)歷了十多天才發(fā)布出來(lái)(每天寫一點(diǎn)),不過(guò)請(qǐng)大家放心,這個(gè)系列一定會(huì)繼續(xù)寫下去。由于自己才疏學(xué)淺且是對(duì)這些技術(shù)的使用總結(jié)和心得體會(huì),錯(cuò)誤之處在所難免,懷著技術(shù)交流的心態(tài),在這里發(fā)表出來(lái),所以希望大家能夠多多指點(diǎn),這樣在使一部分人受益的同時(shí)也能糾正我的錯(cuò)誤觀點(diǎn),以便和各位共同提高。
這篇文章主要是對(duì)WPF布局系統(tǒng)做一個(gè)較簡(jiǎn)單的介紹,大家都知道:UI是做好一個(gè)軟件很重要的因素,如果沒(méi)有一個(gè)漂亮的UI,再怎么強(qiáng)大的功能也會(huì)顯得這個(gè)軟件很脆弱且沒(méi)有投資價(jià)值。本文以總分總的形式展開(kāi)介紹:首先對(duì)WPF Panel做一個(gè)總體認(rèn)識(shí)、然后講解各Panel基本用法、布局綜合應(yīng)用、自定義布局控件以及最后的總結(jié),希望對(duì)大家有所幫助。
二.本文提綱
· 1.摘要
· 2.本文提綱
· 3.總體介紹
· 4.
Canvas
· 5.
StackPanel
· 6.
WrapPanel
· 7.
DockPanel
· 8.
Grid
· 9.UniformGrid
· 10.ViewBox
· 11.Border
· 12.ScrollViewer
· 13.布局綜合應(yīng)用
· 14.自定義布局控件
· 15.本文總結(jié)
· 16.系列進(jìn)度
三.總體介紹
WPF的布局控件都在System.Windows.Controls.Panel這個(gè)基類下面,使用 Panel 元素在WPF應(yīng)用程序中放置和排列子對(duì)象。它具體包括哪些布局控件以及如何使用這些布局控件、如何開(kāi)發(fā)自定義的布局控件,也就是本文所要討論的范疇:
Panel具體繼承關(guān)系詳見(jiàn)下面類圖:
如上圖,公共屬性太多了,就簡(jiǎn)單介紹幾個(gè)常見(jiàn)的屬性:Margin是元素與其停放父元素的間距;Padding是指在本元素內(nèi)部的元素內(nèi)容與邊緣的距離;FlowDirection屬性標(biāo)示元素的內(nèi)容顯示方向;Panel.ZIndex是相對(duì)于顯示屏的Z軸坐標(biāo),用于調(diào)整層疊元素的顯示先后;RenderTransform和LayoutTransform用來(lái)將縮放和旋轉(zhuǎn)的變換應(yīng)用到某個(gè)元素上。
一個(gè)Panel 的呈現(xiàn)是測(cè)量和排列Children子元素、然后在屏幕上繪制它們的過(guò)程。所以在布局的過(guò)程中會(huì)經(jīng)過(guò)一系列的計(jì)算,那么Children 越多,執(zhí)行的計(jì)算次數(shù)就越多。如果不需要較為復(fù)雜的 Panel(如 Grid和自定義復(fù)雜的Panel),則可以使用構(gòu)造相對(duì)簡(jiǎn)單的布局(如 Canvas、UniformGrid等),這種布局可帶來(lái)更好的性能。如果有可能,我們應(yīng)盡量避免不必要地調(diào)用 UpdateLayout方法。
每當(dāng)Panel內(nèi)的子元素改變其位置時(shí),布局系統(tǒng)就可能觸發(fā)一個(gè)新的處理過(guò)程。對(duì)此,了解哪些事件會(huì)調(diào)用布局系統(tǒng)就很重要,因?yàn)椴槐匾恼{(diào)用可能導(dǎo)致應(yīng)用程序性能變差。
換句話說(shuō),布局是一個(gè)遞歸系統(tǒng),實(shí)現(xiàn)在屏幕上對(duì)元素進(jìn)行大小調(diào)整、定位和繪制,然后進(jìn)行呈現(xiàn)。具體如下圖,要實(shí)現(xiàn)控件0的布局,那么先要實(shí)現(xiàn)0的子控件01,02...的布局,要實(shí)現(xiàn)01的布局,那么得實(shí)現(xiàn)01的子控件001,002...的布局,如此循環(huán)直到子控件的布局完成后,再完成父控件的布局,最后遞歸回去直到遞歸結(jié)束,這樣整個(gè)布局過(guò)程就完成了。
布局系統(tǒng)為 Children 集合的每個(gè)成員完成兩個(gè)處理過(guò)程:測(cè)量處理過(guò)程(Measure)和排列處理過(guò)程(Arrange)。每個(gè)子 Panel 均提供自己的 MeasureOverride 和 ArrangeOverride 方法,以實(shí)現(xiàn)自己特定的布局行為。
四. Canvas
Canvas比較簡(jiǎn)單,只是一個(gè)存儲(chǔ)元素的容器,它不會(huì)自動(dòng)調(diào)整內(nèi)部元素的排列及大小。不指定元素位置,元素將默認(rèn)顯示在畫布的左上方。Canvas的主要用途是用來(lái)畫圖。Canvas默認(rèn)不會(huì)自動(dòng)裁減超過(guò)自身范圍的內(nèi)容,即溢出的內(nèi)容會(huì)顯示在Canvas外面,這是因?yàn)槟J(rèn) ClipToBounds="False";我們可以通過(guò)設(shè)置ClipToBounds="True來(lái)裁剪多出的內(nèi)容。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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);
}
}
}
五. StatickPanel
StatickPanel就是將子元素按照堆棧的形式一一排列,通過(guò)設(shè)置面板的Orientation屬性設(shè)置了兩種排列方式:橫排(Horizontal默認(rèn)的)和豎排(Vertical)??v向的StatickPanel默認(rèn)每個(gè)元素寬度與面板一樣寬,反之橫向亦然。如果包含的元素超過(guò)了面板空間,它只會(huì)截?cái)喽喑龅膬?nèi)容。元素的Margin屬性用于使元素之間產(chǎn)生一定得間隔,當(dāng)元素空間大于其內(nèi)容的空間時(shí),剩余空間將由HorizontalAlignment和VerticalAlignment屬性來(lái)決定如何分配。其他屬性,大家可以看看如下類圖:
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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是一個(gè)非常簡(jiǎn)單的面板,從左至右按順序位置定位子元素,如果排滿斷開(kāi)至下一行。后續(xù)排序按照從上至下或從右至左的順序進(jìn)行。WrapPanel面板也提供了 Orientation屬性設(shè)置排列方式,這跟上面的StackPanel基本相似。不同的是WrapPanel會(huì)根據(jù)內(nèi)容自動(dòng)換行。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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定義一個(gè)區(qū)域,在此區(qū)域中,您可以使子元素通過(guò)描點(diǎn)的形式排列。??棵姘迤鋵?shí)就是在WinForm類似于Dock屬性的元素。DockPanel會(huì)對(duì)每個(gè)子元素進(jìn)行排序,并??吭诿姘宓囊粋?cè),多個(gè)停靠在同側(cè)的元素則按順序排序,最后一個(gè)元素填充這個(gè)Panel(這個(gè)需要設(shè)置LastChildFill屬性為 True)。對(duì)于在DockPanel中的元素的??繉傩钥梢酝ㄟ^(guò)Panel.Dock的附加屬性來(lái)設(shè)置.
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
namespace WPFLayoutDemo
{
public partial class DockPanelDEMOCodeBehind
{
public DockPanelDEMOCodeBehind()
{
this.InitializeComponent();DockPanel dp = new DockPanel();
dp.LastChildFill = true;
dp.Width = Double.NaN; //這個(gè)就相當(dāng)于在XAML中設(shè)置Width="Auto"
dp.Height = Double.NaN; //這個(gè)就相當(dāng)于在XAML中設(shè)置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和其他各個(gè)Panel比較起來(lái),功能最多也最為復(fù)雜,它由<Grid.ColumnDefinitions>列元素集和<Grid.RowDefinitions>行元素集合兩種元素組成。而放置在Grid面板中的控件元素都必須顯示采用附加屬性語(yǔ)法定義其放置所在的行和列,否則元素均默認(rèn)放置在第0行第0列。由于Grid的組成并非簡(jiǎn)單的添加屬性標(biāo)記來(lái)區(qū)分行列,這也使得用戶在實(shí)際應(yīng)用中可以具體到某一單元格中,所以布局起來(lái)就很精細(xì)了。
Grid的列寬與行高可采用固定、自動(dòng)、按比列三種方式定義
<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>
第一種,固定長(zhǎng)度——寬度不夠,會(huì)裁剪,不好用。單位pixel。
第二種,自動(dòng)長(zhǎng)度——自動(dòng)匹配列中最長(zhǎng)元素的寬度。
第三種,比例長(zhǎng)度——*表示占用剩余的全部寬度;兩行都是*,將平分剩余寬度;像上面的一個(gè)2*,一個(gè)*,表示前者2/3寬度。
跨越多行和多列
<Rectangle Fill="Silver" Grid.Column="1" Grid.ColumnSpan="3"/>
使用Grid.ColumnSpan和Grid.RowSpan附加屬性可以讓相互間隔的行列合并,所以元素也可以跨越多個(gè)單元格。
使用GridSplit分割
<GridSplitter Height="6" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Grid.Row="2" Grid.Column="2"></GridSplitter>
使用GridSplit控件結(jié)合Grid控件實(shí)現(xiàn)類似于WinForm中SplitContainer的功能,這個(gè)大家在WinForm當(dāng)中經(jīng)常用到,我們也不多做介紹。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
namespace WPFLayoutDemo
{
public partial class GridDEMOCodeBehind
{
public GridDEMOCodeBehind()
{
this.InitializeComponent();
Grid grid = new Grid();
grid.Width = Double.NaN; //這個(gè)就相當(dāng)于在XAML中設(shè)置Width="Auto"
grid.Height = Double.NaN; //這個(gè)就相當(dāng)于在XAML中設(shè)置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,接下來(lái)的這個(gè)UniformGrid 就太簡(jiǎn)單了,均布網(wǎng)格的是Grid的簡(jiǎn)化版本,每個(gè)單元格的大小相同,不用在定義行列集合。均布網(wǎng)格每個(gè)單元格只能容納一個(gè)元素,將自動(dòng)按照定義在其內(nèi)部的元素個(gè)數(shù),自動(dòng)創(chuàng)建行列,并通常保持相同的行列數(shù)。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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這個(gè)控件通常和其他控件結(jié)合起來(lái)使用,是WPF中非常有用的控制。定義一個(gè)內(nèi)容容器,該容器可拉伸和縮放單個(gè)子元素以填滿可用空間。一個(gè) Viewbox只能具有一個(gè) Child。如果添加一個(gè)附加 Child,會(huì)導(dǎo)致一個(gè)運(yùn)行時(shí) ArgumentException錯(cuò)誤。我們用得最多的首先是Stretch屬性,然后是StrctchDirection屬性,關(guān)于這兩個(gè)元素,大家可以運(yùn)行我們的代碼,然后改變?cè)O(shè)置就可以看到效果。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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 是一個(gè)裝飾的控件,此控件繪制邊框及背景,在 Border 中只能有一個(gè)子控件(這個(gè)子控件又可以包含多個(gè)子控件)。Border 的幾個(gè)重要屬性:Background:用用一個(gè) Brush 對(duì)象來(lái)繪制背景;BorderBrush:用一個(gè)Brush 對(duì)象來(lái)繪制邊框;BorderThickness:此屬性設(shè)置 Border 邊框的大??;CornerRadius:此屬性設(shè)置 Border 的每一個(gè)角圓的半徑;Padding:此r屬性設(shè)置 Border 里的內(nèi)容與邊框的之間的間隔。
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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
通常用戶界面中的內(nèi)容比計(jì)算機(jī)屏幕的顯示區(qū)域大,大出的部分就會(huì)破壞原有的布局。利用 ScrollViewer 控件可以方便地使應(yīng)用程序中的內(nèi)容具備滾動(dòng)功能。這樣大出的部分就可以正常顯示出來(lái)了。常用屬性、事件和繼承關(guān)系見(jiàn)下面類圖:
要實(shí)現(xiàn)的效果如下圖(用XAML和C#實(shí)現(xiàn)同一效果):
XAML代碼實(shí)現(xiàn):
<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#代碼實(shí)現(xiàn):
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;
}
}
}
十三.布局綜合應(yīng)用
前面通過(guò)十多個(gè)小節(jié)講了一些常用Panel的基本用法,那我們這里就簡(jiǎn)單做一個(gè)綜合的小例子,通過(guò)這個(gè)例子,旨在鞏固前面的內(nèi)容,也可以當(dāng)做一個(gè)舉一反三的過(guò)程。要實(shí)現(xiàn)的效果如下圖:
XAML代碼實(shí)現(xiàn):
<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="布局綜合運(yùn)用" 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="關(guān)于本產(chǎn)品"/>
</MenuItem>
</Menu><!--State -->
<StackPanel Width="Auto" Height="31" Background="LightGray" Orientation="Horizontal" DockPanel.Dock="Bottom">
<Label Width="155" Height="23" Content="狀態(tài)欄" FontFamily="Arial" FontSize="10"/>
</StackPanel><!--Left-->
<StackPanel Width="136" Height="Auto" Background="Gray">
<Button Margin="5,5,5,5" Width="Auto" Height="26" Content="導(dǎo)航欄"/>
<Button Width="126" Height="26" Content="導(dǎo)航欄" Margin="5,5,5,5"/>
<Button Width="126" Height="26" Content="導(dǎo)航欄" 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>
其實(shí)用熟練上面的各個(gè)布局控件以后,你會(huì)發(fā)現(xiàn)布局UI是一件非常容易的事,遇到一個(gè)新的UI,你會(huì)發(fā)現(xiàn)任意一個(gè)Panel都可以實(shí)現(xiàn)你的需求。當(dāng)然對(duì)于較復(fù)雜且對(duì)要求很高的UI,我們也會(huì)自定義一些Panel,在下面我們就簡(jiǎn)單介紹一下自定義布局控件。
十四.自定義布局控件
講到自定義布局控件,我們必須得先談一下在WPF中自定義控件,在WPF自定義控件你可以選擇下圖的一些基類作為繼承對(duì)象,你也可以繼承自已有的一些控件,這個(gè)就看你的需要了。其實(shí)開(kāi)發(fā)WPF自定義控件和開(kāi)發(fā)WinForm、ASP.NET自定義控件基本類似,只是要注意一些特別的地方,比如依賴屬性的處理、路由事件、視覺(jué)樹(shù)和邏輯樹(shù)等等。
由于今天只是講如何開(kāi)發(fā)一個(gè)自定義的Panel,所以在清楚了基類的前提下,首先得了解它有哪些屬性和事件,這樣就可以確定哪些是不需要單獨(dú)寫、哪些是需要override。下圖就是Panel和基類FrameworkElement 的類圖:
在清楚了上面這張圖以后,我們就可以著手開(kāi)始寫了,我們知道布局系統(tǒng)的工作原理是先測(cè)量后排列,測(cè)量就是確定面板需要多大空間,排列則是定義其面板內(nèi)子元素的排列規(guī)則。自定義面板要繼承自Panel類并重寫MeasureOverride和rrangeOverride方法即可,如下便是一個(gè)簡(jiǎn)單的自定義Panel:
namespace WPFLayoutDemo
{
public class PlotPanel : Panel
{
public PlotPanel()
: base()
{
}//重寫默認(rèn)的Measure方法
protected override Size MeasureOverride(Size availableSize)
{
Size panelDesiredSize = new Size();
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
panelDesiredSize = child.DesiredSize;
}return panelDesiredSize;
}//重寫默認(rèn)的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;
}
}
}
控件的最終大小和位置是由該控件和父控件共同完成的,父控件會(huì)先給子控件提供可用空間(availableSize),子控件再反饋給父控件一個(gè)自己的期望值(DesiredSize),父控件最后根據(jù)自己所擁有的空間大小與子控件的期望值分配一定的空間給子控件并返回自己的大小.那么這個(gè)過(guò)程就是通過(guò)MeasureOverride 和ArrangeOverride這兩個(gè)方法來(lái)完成(注意父控件的availableSize是減去Margin、padding等的值)。
本來(lái)想自己開(kāi)發(fā)一個(gè)較復(fù)雜的Panel控件放上來(lái),但一搜網(wǎng)絡(luò),發(fā)現(xiàn)已經(jīng)有很多很好的Panel控件,所以在這里我也不寫那么多了,大家可以研究一下這些控件,我也研究了幾個(gè),覺(jué)得最好理解且最美觀的當(dāng)屬“FishEyePanel & FanPanel, Paul Tallett, codeproject ”,大家可以根據(jù)鏈接過(guò)去看一下,Paul Tallett講解得非常的細(xì)致。
- 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自定義控件的幾個(gè)步驟,以后在講到這一節(jié)的時(shí)候會(huì)詳細(xì)講解:
- 首先你得清楚你的自定義控件是干什么用的(能解決什么問(wèn)題)?公用到什么程度(其他項(xiàng)目也可以用、本項(xiàng)目用、項(xiàng)目當(dāng)中一個(gè)模塊用、只有一個(gè)地方用)?是繼承已有的控件還是從頭寫?對(duì)設(shè)計(jì)時(shí)是否支持?樣式和模板的定義等。
- 確定好了上面的步驟后,我們就可以建立項(xiàng)目的結(jié)構(gòu),類和資源文件等該放在什么位置也就在這一步確定。
- 選擇要繼承的基類(UIElement、FrameworkElement 、Control 、ContentControl 、HeaderedContentControl 、ItemsControl 、Selector 、RangeBase還是已有的一些控件)。
- 重寫默認(rèn)的樣式和新建一些樣式并附默認(rèn)值。
- 由于WPF的屬性基本都是依賴屬性,所以我們也要新建一些依賴屬性。
- 邏輯樹(shù)和視覺(jué)樹(shù)的一些處理以及事件等。
十五.本文總結(jié)
今天我們主要講了WPF布局系統(tǒng),對(duì)整個(gè)布局系統(tǒng)的原理、各個(gè)Panel的基本用法以及自定義布局控件做了一些介紹,由于本文不是專門進(jìn)行這方面的研究,所以只能給大家提供一個(gè)參考,如果大家想了解更多,還需要去看專門的教材,同時(shí)有些知識(shí)也只是個(gè)人的一些見(jiàn)解,所以大家只能將就著看了。寫篇文章也是懷著技術(shù)交流的心態(tài)發(fā)布出來(lái),由于是自己對(duì)這些技術(shù)的使用總結(jié)和心得體會(huì),錯(cuò)誤之處在所難免,所以希望大家能夠多多指點(diǎn),這樣也能糾正我的錯(cuò)誤觀點(diǎn),以便和各位共同提高!
最后如果大家感興趣,可以關(guān)注WPF 基礎(chǔ)到企業(yè)應(yīng)用系列索引這個(gè)系列文章,我也會(huì)不定期的逐漸更新,謝謝各位的關(guān)注,也歡迎和各位交流討論。
最后也附上代碼結(jié)構(gòu)圖和代碼下載:
下載鏈接:WPFLayoutDemo.zip
NET技術(shù):WPF 基礎(chǔ)到企業(yè)應(yīng)用系列6——布局全接觸,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。