一、引言關(guān)于WPF早在一年前就已經(jīng)看過(guò)《深入淺出WPF》這本書(shū),當(dāng)時(shí)看完之后由于沒(méi)有做筆記,以至于我現(xiàn)在又重新?lián)炱饋?lái)并記錄下學(xué)習(xí)的過(guò)程,本系列將是一個(gè)WPF快速入門(mén)系列,主要介紹WPF中主要的幾個(gè)不同的特性,如依賴(lài)屬性、命令、路由事件等。 在正式介紹之前,我還想分享下為什么我又要重新?lián)炱饋?lái)WPF呢?之前沒(méi)有記錄下來(lái)的原來(lái)主要是打算走互聯(lián)網(wǎng)方向的,后面發(fā)現(xiàn)互聯(lián)網(wǎng)方向經(jīng)常加班,又累,有時(shí)候忙的連自己寫(xiě)了什么都不知道的,所以后面機(jī)緣巧合地進(jìn)了一家外企,在外企不像互聯(lián)網(wǎng)行業(yè)那樣,比較清楚,有更多的時(shí)間去理清楚自己所學(xué)習(xí)到的知識(shí),其中同時(shí)也發(fā)現(xiàn)了WPF的重要性和應(yīng)用場(chǎng)景,在一些美資企業(yè)和印度的公司,客戶(hù)端都非常喜歡用WPF來(lái)做演示的客戶(hù)端,所以,自然走上外企這條路,所以就打算好好研究下WPF了,所以也就有了這個(gè)系列。 二、WPF的自我介紹Windows Presentation Foudation,WPF是下一代顯示系統(tǒng),用來(lái)生成能帶給用戶(hù)震撼視覺(jué)體驗(yàn)的Windows客戶(hù)端應(yīng)用程序。WPF的核心是一個(gè)與分辨率無(wú)關(guān)并且基于向量的程序引擎,目的在于利用現(xiàn)代圖形硬件的優(yōu)勢(shì)。WPF在.NET Framework 3.0中被微軟引入到.NET Framework類(lèi)庫(kù)中,并且在.NET 3.5、4.0 和4.5都有所更新。WPF可以理解為是實(shí)現(xiàn)下一代Windows 桌面應(yīng)用程序的技術(shù),在之前我們通常會(huì)使用MFC或Winform來(lái)實(shí)現(xiàn)Windows桌面程序。 WPF除了引入了新的API之前,還引入了一些新的概念,這些新的概念會(huì)在本系列中一一介紹。眾所周知,在實(shí)現(xiàn)桌面應(yīng)用程序之前,第一步必然是對(duì)窗體進(jìn)行布局,WPF為了更好地實(shí)現(xiàn)布局,提供了很多布局控件,下面就讓我們一起去看看WPF布局組件。 三、WPF布局詳解WPF的布局控件都繼承于System.Windows.Controls.Panel這個(gè)類(lèi),本文主要介紹在Panel基類(lèi)下的幾個(gè)常用的布局控件。下圖是布局控件的繼承關(guān)系: 3.1 WPF布局過(guò)程WPF布局包括兩個(gè)階段:一個(gè)測(cè)量(measure)階段和一個(gè)排列(arrange)階段。在測(cè)量階段,容器遍歷所有子元素,并詢(xún)問(wèn)子元素它們所期望的大小。在排列階段,容器在合適的位置放置子元素。WPF布局可以理解為一個(gè)遞歸過(guò)程,它會(huì)遞歸對(duì)布局控件內(nèi)的每個(gè)子元素進(jìn)行大小調(diào)整,定位和繪制,最后進(jìn)行呈現(xiàn),直到遞歸所有子元素為止,這樣也就完成了整個(gè)布局過(guò)程。 布局系統(tǒng)為每個(gè)子元素完成了兩個(gè)處理過(guò)程:測(cè)量處理和排列處理。每個(gè)Panel都提供了自己的MeasureOverride和ArrangeOverride方法,以實(shí)現(xiàn)自己特定的布局行為。所以,你如果想自定義布局控件,也可以重新這兩個(gè)方法來(lái)達(dá)到,關(guān)于自定義布局控件會(huì)在后面介紹到。 3.2 Canvas 布局控件Canvas面板是最輕量級(jí)的布局容器,它不會(huì)自動(dòng)調(diào)整內(nèi)部元素的排列和大小,不指定元素位置,元素將默認(rèn)顯示在畫(huà)布的左上方。Canvas主要用來(lái)畫(huà)圖。Canvas默認(rèn)不會(huì)自動(dòng)裁剪超過(guò)自身范圍的內(nèi)容,即溢出的內(nèi)容會(huì)顯示在Canvas外面,這是因?yàn)镃anvas的ClipToBounds屬性默認(rèn)值是false,我們可以顯式地設(shè)置為true來(lái)裁剪多出的內(nèi)容。下面XAML代碼簡(jiǎn)單演示了Canvas面板的使用。 <Canvas Margin="10,10,10,10" Background="White" > <Rectangle Name="rect" Canvas.Left="300" Canvas.Top="180" Fill="Black" Stroke="Red" Width="200" Height="200"/> <Ellipse Name="el" Canvas.Left="160" Canvas.Top="150" Fill="Azure" Stroke="Green" Width="180" Height="180"/> </Canvas> 上面XAML實(shí)現(xiàn)的效果如下圖所示: 其中,矩形的右邊區(qū)域以溢出Canvas面板區(qū)域,如向右拉動(dòng)邊框,此時(shí)Canvas會(huì)拉伸以填滿(mǎn)可用空間,此時(shí)就可以看到矩形溢出的部分。但Canvas面板內(nèi)的控件不會(huì)改變其尺寸和位置。對(duì)應(yīng)的C#代碼實(shí)現(xiàn)如下所示: public partial class CanvasDemo : Window { public CanvasDemo() { InitializeComponent();
Canvas canv = new Canvas(); canv.Margin = new Thickness(10, 10, 10, 10); canv.Background = new SolidColorBrush(Colors.White);
// 把canv添加為窗體的子控件 this.Content = canv;
// Rectangle Rectangle rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Black); rect.Stroke = new SolidColorBrush(Colors.Red); rect.Width = 200; rect.Height = 200; rect.SetValue(Canvas.LeftProperty, (double)300); rect.SetValue(Canvas.TopProperty, (double)180); canv.Children.Add(rect);
// Ellipse Ellipse el = new Ellipse(); el.Fill = new SolidColorBrush(Colors.Azure); el.Stroke = new SolidColorBrush(Colors.Green); el.Width = 180; el.Height = 180; el.SetValue(Canvas.LeftProperty, (double)160); // 必須轉(zhuǎn)換為double,否則執(zhí)行會(huì)出現(xiàn)異常 // 詳細(xì)介紹見(jiàn):http://msdn.microsoft.com/zh-cn/library/system.windows.controls.canvas.top(v=vs.110).aspx el.SetValue(Canvas.TopProperty, (double)150); el.SetValue(Panel.ZIndexProperty, -1); canv.Children.Add(el);
// Print Zindex Value int zRectIndex = (int)rect.GetValue(Panel.ZIndexProperty); int zelIndex = (int)el.GetValue(Panel.ZIndexProperty); Debug.WriteLine("Rect ZIndex is: {0}", zRectIndex); Debug.WriteLine("Ellipse ZIndex is: {0}", zelIndex); } } 從上面可以看出,即使C#代碼可以實(shí)現(xiàn)完全一樣的效果,但是需要書(shū)寫(xiě)更多的代碼,所以,在平時(shí)開(kāi)發(fā)中,對(duì)于控件的布局,一般采用XAML的方式,C#代碼一般用于在運(yùn)行時(shí)加載某個(gè)控件到界面中的實(shí)現(xiàn)。 3.3 StackPanel 布局控件StackPanel就是將子元素按照堆棧的形式一一排列,可以通過(guò)設(shè)置StackPanel的Orientation屬性設(shè)置兩種排列方式:橫排(Horizontal,該值為默認(rèn)值)和豎排(Vertical)??v向的StackPanel每個(gè)元素默認(rèn)寬度與面板一樣寬,反之橫向是高度和面板一樣高。如果包含的元素超過(guò)了面板控件,它會(huì)被截?cái)喽喑龅膬?nèi)容??梢酝ㄟ^(guò)Orientation屬性來(lái)設(shè)置StackPanel是橫排(設(shè)置其值為Vertical)還是豎排(設(shè)置其值為Horizontal)。下面XAML代碼演示了StackPanel的使用: <Window x:Class="WPFLayoutDemo.StackPanelDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="StackPanel" Height="300" Width="200"> <StackPanel Margin="10,10,10,10" Background="Azure"> <Label>A Button Stack</Label> <Button Content="Button 1"></Button> <Button>Button 2</Button> <Button>Button 3</Button> <Button Content="Button 4"></Button> </StackPanel> </Window> 對(duì)應(yīng)的C#實(shí)現(xiàn)代碼如下所示: public partial class StackPanelDemo : Window { public StackPanelDemo() { InitializeComponent(); StackPanel sp = new StackPanel(); sp.Margin = new Thickness(10, 10, 10, 10); sp.Background = new SolidColorBrush(Colors.Azure); sp.Orientation = Orientation.Vertical; // 把sp添加為窗體的子控件 this.Content = sp;
// Label Label lb = new Label(); lb.Content = "A Button Stack"; sp.Children.Add(lb);
// Button 1 Button btn1 = new Button(); btn1.Content = "Button 1"; sp.Children.Add(btn1);
// Button 2 Button btn2 = new Button(); btn2.Content = "Button 2"; sp.Children.Add(btn2);
// Button 3 Button btn3 = new Button(); btn3.Content = "Button 3"; sp.Children.Add(btn3);
// Button 4 Button btn4 = new Button(); btn4.Content = "Button 4"; sp.Children.Add(btn4); } } 上面代碼的實(shí)現(xiàn)效果如下圖所示: 如果將StackPanel的Orientation屬性設(shè)置為“Horizontal”的話(huà),此時(shí)的效果如下圖所示: 管布局由容器決定,但子元素仍然有一定的決定權(quán),布局面板支持一些布局屬性,以便與子元素結(jié)合使用,在下圖中列出了這些布局屬性: 3.4 WrapPanel 布局控件WrapPanel面板在可能的空間中,一次以一行或一列的方式布置控件。默認(rèn)情況下,WrapPanel.Orientation屬性設(shè)置為Horizontal,控件從左向右進(jìn)行排列,然后再在下一行中排列,但你可將WrapPanel.Orientation設(shè)置為Vertical,從而在多個(gè)列中放置元素。與StackPanel面板不同,WrapPanel面板實(shí)際上用來(lái)控制用戶(hù)界面中一小部分的布局細(xì)節(jié),并非用于控制整個(gè)窗口布局。 下面示例中定義了一系列具有不同對(duì)齊方式的按鈕,并將這些按鈕放在一個(gè)WrapPanel面板中。 <Window x:Class="WPFLayoutDemo.WrapPanelDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WrapPanelDemo" Height="300" Width="500"> <WrapPanel Margin="10" Background="Azure"> <Button VerticalAlignment="Top" Margin="5">Top Button</Button> <Button MinHeight="50"> Tall Button 2</Button> <Button VerticalAlignment="Bottom">Bottom Button</Button> <Button>Stretch Button</Button> <Button VerticalAlignment="Center">Center Button</Button> <Button>Next Button</Button> </WrapPanel> </Window> 下圖顯示了如何對(duì)這些按鈕進(jìn)行換行以適應(yīng)WrapPanel面板的當(dāng)前尺寸,WrapPanel面板的當(dāng)前尺寸由包含它的窗口尺寸決定的。在上面的例子中,WrapPanel面板水平地創(chuàng)建一系列假象的行,每一行的搞定都被設(shè)置為所包含元素中最高元素的高度。其他空間可能被拉伸以適應(yīng)該高度,或根據(jù)VerticalAlignment屬性設(shè)置進(jìn)行對(duì)齊。 當(dāng)縮小窗口大小時(shí),對(duì)應(yīng)的WrapPanel也會(huì)改變,從而改變WrapPanel面板中控件的排列,具體效果如下圖所示: 3.5 DockPanel 布局控件DockPanel面板定義一個(gè)區(qū)域,在此區(qū)域中,你可以使子元素通過(guò)錨點(diǎn)的形式進(jìn)行排列。DockPanel類(lèi)似于WinForm中Dock屬性的功能。對(duì)于在DockPanel中的元素的??靠梢酝ㄟ^(guò)Panel.Dock的附加屬性來(lái)設(shè)置,如果設(shè)置LastChildFill屬性為true,則最后一個(gè)元素將填充剩余的所有空間。 下面XAML代碼演示了DockPanel控件的使用: <Window x:Class="WPFLayoutDemo.DockPanelDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="DockPanelDemo" Height="300" Width="300"> <DockPanel Margin="10" Background="Azure" LastChildFill="True"> <Button DockPanel.Dock="Top" Background="Red">Top Button</Button> <Button DockPanel.Dock="Left" Background="Gray">Left Button</Button> <Button DockPanel.Dock="Right" Background="Green">Right Button</Button> <Button DockPanel.Dock="Bottom" Background="White">Bottom Button</Button> <Button>Remaining Button</Button> </DockPanel> </Window> 運(yùn)行的效果如下圖所示: .6 Grid 布局控件Grid比起其他Panel,功能是最多最為復(fù)雜的布局控件。它由<Grid.ColumnDefinitions>列元素集合和<Grid.RowDefinitions>行元素集合兩種元素組成。而放在Grid面板中的元素必須顯式采用附加屬性定義其所在行和列,否則元素均默認(rèn)放置在第0行第0列。下面XAML演示了Grid面板的使用: <Window x:Class="WPFLayoutDemo.GridDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="GridDemo" Height="300" Width="480"> <Grid Width="Auto" Height="Auto"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="120"/> <ColumnDefinition Width="150"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="2*"/> </Grid.ColumnDefinitions> <Rectangle Grid.Row="0" Grid.Column="0" Fill="Green" Margin="10,10,10,20"/> <Rectangle Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Fill="Blue" Margin="10,10,10,20"/> <Rectangle Grid.Row="0" Grid.Column="4" Fill="Orange"/> <Button Grid.Row="1" Grid.Column="0">Button 2</Button> <Rectangle Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Fill="Red"/> </Grid> </Window> 定義Grid的列寬和行高可采用固定、自動(dòng)和按比例三種方式定義。 第一種:固定長(zhǎng)度——寬度不夠時(shí),元素會(huì)被裁剪,單位是pixel; 第二種:自動(dòng)長(zhǎng)度——自動(dòng)匹配行中最寬元素的高度。 第三種:比例長(zhǎng)度——"*"表示占用剩余的全部寬度或高度,兩行都是*,則將剩余高度平分。像上面的一個(gè)2*,一個(gè)*,表示前者2/3寬度。 其運(yùn)行效果如下圖所示: 3.7 UniformGrid 布局控件UniformGrid是Grid簡(jiǎn)化版本,不像Grid面板,UniformGrid不需要預(yù)先定義行集合和列集合,反而,通過(guò)簡(jiǎn)單設(shè)置Rows和Columns屬性來(lái)設(shè)置尺寸。每個(gè)單元格始終具有相同的大小。UniformGrid每個(gè)單元格只能容納一個(gè)元素,將自動(dòng)按照在其內(nèi)部的元素個(gè)數(shù),自動(dòng)創(chuàng)建行和列,并通過(guò)保存相同的行列數(shù)。 下面XAML演示了UniformGrid控件的使用: <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> <Ellipse Margin="10" Fill="Gray"/> <Ellipse Margin="10" Fill="Gray"/> <Ellipse Margin="10" Fill="Green"/> <Ellipse Margin="10" Fill="Green"/> <Ellipse Margin="10" Fill="Red"/> </UniformGrid> </Window> 在上面,并沒(méi)有顯示指定UniformGrid的行和列數(shù),此時(shí)UniformGrid將自動(dòng)按照元素的個(gè)數(shù),自動(dòng)創(chuàng)建行和列。運(yùn)行效果如下圖所示。最好是顯式指定Rows和Columns屬性,這樣才能確保布局是按照你的思路去進(jìn)行的。 3.8 ScrollViewer 控件通常用戶(hù)界面中的內(nèi)容比計(jì)算機(jī)屏幕的顯示區(qū)域大的時(shí)候,可以利用ScrollViewer控件可以方便地使應(yīng)用程序中的內(nèi)容具備滾動(dòng)功能。具體的使用示例如下所示: <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 HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Auto"> <Rectangle Width="500" Height="400" Fill="Green"/> </ScrollViewer> </Grid> </Window> 運(yùn)行效果如下圖所示: 四、布局綜合運(yùn)用前 前面例子都是單獨(dú)介紹每個(gè)布局控件的,然而在實(shí)際開(kāi)發(fā)中,程序的界面布局都是由多個(gè)布局控件一起來(lái)完成的,這里演示一個(gè)綜合實(shí)驗(yàn)的小例子。要實(shí)現(xiàn)的效果圖如下所示:
具體的XAML代碼實(shí)現(xiàn)如下所示: <Window x:Class="WPFLayoutDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStartupLocation="CenterScreen" Title="布局綜合運(yùn)用實(shí)例" Height="400" Width="480"> <DockPanel Width="Auto" Height="Auto" LastChildFill="True"> <!--頂部菜單區(qū)域--> <Menu Width="Auto" Height="20" Background="LightGray" DockPanel.Dock="Top"> <!--File菜單項(xiàng)--> <MenuItem Header="文件"> <MenuItem Header="保存"/> <Separator/> <MenuItem Header="退出"/> </MenuItem> <!--About 菜單項(xiàng)--> <MenuItem Header="幫助"> <MenuItem Header="關(guān)于本產(chǎn)品"/> </MenuItem> </Menu>
<!--狀態(tài)欄--> <StackPanel Width="Auto" Height="25" Background="LightGray" Orientation="Horizontal" DockPanel.Dock="Bottom"> <Label Width="Auto" Height="Auto" Content="狀態(tài)欄" FontFamily="Arial" FontSize="12"/> </StackPanel> <!--Left--> <StackPanel Width="130" Height="Auto" Background="Gray" DockPanel.Dock="Left"> <Button Margin="10" Width="Auto" Height="30" Content="導(dǎo)航欄"/> <Button Margin="10" Width="Auto" Height="30" Content="導(dǎo)航欄"/> <Button Margin="10" Width="Auto" Height="30" Content="導(dǎo)航欄"/> </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í)際開(kāi)發(fā)中,自然少不了自定義控件的開(kāi)發(fā),下面介紹下如何自定義布局控件。在前面介紹過(guò)布局系統(tǒng)的工作原理是先測(cè)量后排列,測(cè)量即是確定面板需要多大空間,排列則是定義面板內(nèi)子元素的排列規(guī)則。所以,要實(shí)現(xiàn)自定義布局控件,需要繼承于Panel類(lèi)并重寫(xiě)MeasureOverride和ArrangeOverride方法即可,下面實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的自定義布局控件: namespace CustomLayoutControl { public class CustomStackPanel: Panel { public CustomStackPanel() : base() { }
// 重寫(xiě)默認(rèn)的Measure方法 // avaiableSize是自定義布局控件的可用大小 protected override Size MeasureOverride(Size availableSize) { Size panelDesiredSize = new Size(); foreach (UIElement child in this.InternalChildren) { child.Measure(availableSize);
// 子元素的期望大小 panelDesiredSize.Width += child.DesiredSize.Width; panelDesiredSize.Height += child.DesiredSize.Height; }
return panelDesiredSize; }
// 重寫(xiě)默認(rèn)的Arrange方法 protected override Size ArrangeOverride(Size finalSize) { double x = 10; double y = 10; foreach (UIElement child in this.InternalChildren) { // 排列子元素的位置 child.Arrange(new Rect(new Point(x, y), new Size(finalSize.Width - 10, child.DesiredSize.Height))); y += child.RenderSize.Height + 5; }
return finalSize; } } } 控件的最終大小和位置是由該控件和父控件共同完成的,父控件會(huì)先給子控件提供可用大?。∕easureOverride中availableSize參數(shù)),子控件再反饋給父控件一個(gè)自己的期望值(DesiredSize),父控件最后根據(jù)自己所擁有的空間大小與子控件期望的值分配一定的空間給子控件并返回自己的大小。這個(gè)過(guò)程是通過(guò)MeasureOverride和ArrangeOverride這兩個(gè)方法共同完成的,這里需要注意:父控件的availableSize是減去Margin、Padding等的值。 接下來(lái),創(chuàng)建一個(gè)測(cè)試上面自定義布局控件的WPF項(xiàng)目,然后添加自定義布局控件的程序集,然后在WPF項(xiàng)目中MainWindows添加如下代碼: <Window x:Class="TestCustomerPanel.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:custom="clr-namespace:CustomLayoutControl;assembly=CustomLayoutControl" Title="測(cè)試自定義布局控件" Height="350" Width="525"> <custom:CustomStackPanel Background="Red"> <Button Content="Button 1"></Button> <Button Content="Button 2"></Button> <Button Content="Button 3"></Button> </custom:CustomStackPanel> </Window> 運(yùn)行成功后的效果如下圖所示: 六、小結(jié)到這里,WPF布局的內(nèi)容就介紹結(jié)束了,這里最后只是簡(jiǎn)單地定義了一個(gè)類(lèi)似StackPanel的布局控件,你還可以自定義更加復(fù)雜的布局控件 參考鏈接:https://www.cnblogs.com/zhili/p/WPFLayout.html |
|
來(lái)自: ontheroad96j47 > 《待分類(lèi)》