WPF控件和布局


  WPF控件和布局,根據劉鐵猛《深入淺出WPF》書籍講解內容,主要記錄控件和布局的原理,如果有不足的地方,請大牛們鍵盤下留情--輕噴!如果還算有用,請給點動力,支持一把!

一、WPF里的控件

1.1 控件的實質

  我們先從UI上分析,UI的功能是讓用戶觀察和操作數據,為了能顯示數據和響應用戶的操作通知程序(通過事件來通知,如何處理事件又是一系列的算法),所以控件就是顯示數據和響應用戶操作的UI元素,也即:控件就是數據和行為的載體。 

1.2 WPF中的一個重要概念--數據驅動UI

  什么是數據驅動UI呢?我們知道傳統的GUI界面都是由windows消息通過事件傳遞給程序,程序根據不同的操作來表達出不同的數據體現在UI界面上,這樣數據在某種程度上來說,受到很大的限制。WPF中是數據驅動UI,數據是核心,處於主動的,UI從屬於數據並表達數據,是被動的。因為以后的章節會重點介紹,在此不做過多的說明,只要記着,WPF數據第一,控件第二。

1.3 WPF中控件的知多少

  雖然控件沒有數據重要,但是還是比較重要的,畢竟是門面啊,只是在數據面前,它比較"有禮貌"。控件有很多,但是如果仔細去分析,也是有規律可循的,根據其作用,我們可以把控件分為6類:

  • 布局控件:是可以容納多個控件或者嵌套其他布局的控件,用於在UI上組織和排列控件。其父類為Panel。
  • 內容控件:只能容納一個控件或者布局控件作為他的內容。所以經常借助布局控件來規划其內容。其父類為ContentControl。
  • 帶標題內容控件:相當於一個內容控件,但是可以加一個標題,標題部分也可以容納一個控件或者布局,其父類為HeaderedContentControl。
  • 條目控件:可以顯示一列數據,一般情況下,是數據的類型是相同的。其共同的基類為ItemsControl。
  • 帶標題的條目控件:和上面的帶標題內容控件類同,其基類為HeaderdeItemsControl。
  • 特殊內容控件:這類控件比較獨立,但也比較常用,如TextBox,TextBlock,Image等(由於其常用性和相對比較簡單,本篇筆記不做說明)。

上面的控件的派生關系如圖1:

圖1

二、各類控件模型詳解

2.1 WPF中的內容模型  

  為了理解各個控件的模型,還是先了解一下WPF中的內容模型。在上述各類控件里,至少可以容納一個內容,主要原因是由於每個控件對象都會有一個重要又不常寫出來的屬性--Content Property(有Content,Child,Items,Children幾個屬性,如Grid可以容納多個控件,用的是Children)。內容模型就是每一族的控件都含有一個或者多個元素作為其內容(其下面的元素可能是其他控件)。為什么可以不常寫出來呢?先讓我們看下面兩段代碼:

XAML Code
<Window x:Class="Chapter_03.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="內容屬性測試" Height="350" Width="525">
    <Grid>
        <Grid.Children>
            <Button Content="1"  Margin="120,146,0,146" HorizontalAlignment="Left" Width="82" />
            <Button Content="2" x:Name="btn2" Margin="0,146,142,145" HorizontalAlignment="Right" Width="82" />
        </Grid.Children>
    </Grid>
</Window>
XAML
<Window x:Class="Chapter_03.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="內容屬性測試" Height="350" Width="525">
    <Grid>

            <Button Content="1"  Margin="120,146,0,146" HorizontalAlignment="Left" Width="82" />
            <Button Content="2" x:Name="btn2" Margin="0,146,142,145" HorizontalAlignment="Right" Width="82" />

    </Grid>
</Window>

運行兩段代碼效果一樣。充分說明了重要而有不常見的原因。因為省略的省時,而且簡潔明了。所以多數引用時都省去了。

2.2ContentControl族

  先說一下其特點:他們內容屬性的名稱為Content,只能有單一元素充當其內容。下面通過例子說明其特點:

        <Button Margin="120,146,0,76" HorizontalAlignment="Left" Width="100">
            <TextBox Text="測試"/>
            <TextBox Text="測試"/>
            <TextBox Text="測試"/>
        </Button>

上面的會報錯,原因是Button里面只能有單一元素充當其內容。去掉后面的兩個TextBox,效果如圖2:

 

圖2

發現button里面不僅可以顯示文字還可以用一個控件來當其內容。其他的控件不在一一舉例。在此列出此類的主要控件:

Button、ButtonBase、CheckBox、ComboBoxItem、ContentControl、Frame、GridViewColumnHeader、GropItem、Label、ListBoxItem、ListViewItem、NavigationWindow、RadioButton、ScrollViewer、StatusBarItem、ToggleButton、ToolTip、UserControl、Window。

2.3 HeaderedContentControl族

  特點:可以顯示帶標題的數據,內容屬性為Content和Header,其這兩個屬性都只能容納一個元素。在此舉例說明GroupBox的用法,然后列出其他屬於此類的控件。XAML代碼為:

View Code
<Window x:Class="Chapter_03.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="內容屬性測試" Height="200" Width="300">
    <Grid Background="Gold">
        <GroupBox Margin="42,0,96,26">
            <GroupBox.Header>
                <Label Content="我是標題"/>
            </GroupBox.Header>
        <Button  HorizontalAlignment="Left" Width="117" Height="45">
            <TextBox Text="測試"/>
        </Button>
        </GroupBox>
    </Grid>
</Window>

效果圖如圖3:

圖3

是不是看着很還好呢?現在列出同類主要的控件:Expender,GroupBox,HeaderedContentControl,TabItem。

2.4 ItemsControl族

  特點:該類控件用於顯示列表化的數據,內容屬性為Items或ItemsSource,每種ItemsControl都對應有自己的條目容器(Item Container)。本類元素可能會用的比較多些,也比較靈活,所以這里不做過多記錄,以后的記錄會經常用到,具體的再詳細說明。下面就用一個ListBox控件來小試牛刀吧!XAML代碼、Cs代碼如下:

XAML
<Window x:Class="Chapter_03.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="內容屬性測試" Height="260" Width="408">
    <Grid Background="Gold">
        <ListBox x:Name="listbox" Margin="0,0,198,55">
                <CheckBox x:Name="cb1" Content="選擇"/>
                <CheckBox x:Name="cb2" Content="選擇"/>
                <CheckBox x:Name="cb3" Content="選擇"/>
                <CheckBox x:Name="cb4" Content="選擇"/>
                <Button x:Name="btn1" Content="按鈕1"/>
                <Button x:Name="btn2" Content="按鈕1"/>
                <Button x:Name="btn3" Content="按鈕1"/>
        </ListBox>
    </Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Chapter_03
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Button btn=new  Button();
            btn.Content="另外添加一個";
            btn.Click += new RoutedEventHandler(btn_Click);
            this.listbox.Items.Add(btn);
            btn3.Click+=new RoutedEventHandler(btn_Click);
        }
        /// <summary>
        /// 用來找到button的父級元素類型
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void btn_Click(object sender, RoutedEventArgs e)
        {
            Button btn=(sender) as Button;
            DependencyObject level1 = VisualTreeHelper.GetParent(btn);
            DependencyObject level2 = VisualTreeHelper.GetParent(level1);
            DependencyObject level3 = VisualTreeHelper.GetParent(level2);
            if (btn != null)
                MessageBox.Show(level3.GetType().ToString());
            else MessageBox.Show("無找到!");
        }
    }
}

效果圖如圖4:

圖4

   先來說明一下代碼:在listBox里面放了幾個checkbox和button,說明ListBoxI的Item不僅支持類型相同的元素,還支持類型不同的元素。這是因為,Listbox的每一項都是經過“ListBoxItem”加工廠處理的,最終放入當做自己的內容--放入自己的容器內。這里通過后台代碼說明了每一個條目都被ListboxItem包裝過了,完全沒有必要每一個條目都在xmal文件按照如下寫法:

            <ListBoxItem>
                <Button x:Name="btn3" Content="按鈕1"/>
            </ListBoxItem>

  在實際項目中,很少像上面那樣把代碼寫死,可以動態的綁定ListBox。把數據源賦給ListBox的ItemsSource,通過DisplayMemberPath屬性來顯示string類型的數據源里面的字段條目(如果想顯示復雜的數據的話,要使用DataTemplate,具體在模板再記錄,在此知道有這么一回事就好了);通過SelectedItem和SelectionChanged來觀察選中的項。下面的例子實現在listbox上綁定指定數據,然后彈出選中人的年齡。直接給出代碼:

XAML
<Window x:Class="Chapter_03.ListBoxTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxTest" Height="300" Width="300">
    <Grid>
        <ListBox x:Name="listbox1" Margin="0,0,60,31" SelectionChanged="listbox1_SelectionChanged"></ListBox>
    </Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_03
{
    /// <summary>
    /// ListBoxTest.xaml 的交互邏輯
    /// </summary>
    public partial class ListBoxTest : Window
    {
        public ListBoxTest()
        {
            InitializeComponent();
            InitData();
        }
        protected void InitData()
        {
            List<People> peopleList = new List<People>()
            {
                new People(){Id=1,Name="Tim",Age=30},
                new People(){Id=2,Name="Tom",Age=30},

顯示結果如圖5:

圖5

  下面列出屬於ItemsControl族元素和其對應的Item Container有ComboBox——ComboBoxItem,ContextMenu——MenuItem,ListBox——ListBoxItem,ListView——ListViewItem,Menu——MenuItem,StatusBar——StatusBarItem,TabControl——TabItem,TreeView——TreeViewItem.

  由於已經演示了HeaderedContentControl和ItemsControl的功能,另外HeaderedItemsControl的用法就不再記錄了,僅僅列出屬於其族的控件:

  MemuItem、TreeViewItem、TooBar。

三、 UI布局

  在介紹布局之前還是先記錄一下布局控件的特點與屬於Panel族的控件。

  panel族控件內容屬性為Children,所以內容可以是多個元素,這對布局來說是很重要的特征。布局控件與ItemControl的區別是:前者強調的是對元素的布局,后者強調的是條目。屬於Panel類的控件有:Canvas,DockPanel,Grid,TabPanel,ToolBarOverflowPanel,StackPanel,ToolBarPanel,UniformGrid,VirtualizingPanel,VirtualizingStackPanel,WrapPanel。這么多控件不可能一個個去介紹,找幾個比較重要的實踐一下。回頭如果有用到的話再逐一研究。

3.1 主要布局控件的特性

  在WPF里面控件與控件的關系除了相鄰和重疊(用Opacity來控制哪個控件在上面,哪個在下面),還有一個包含。正因為如此,才有了以window為根的樹形結構的XAML。下面介紹一下主要布局元素的特性:

  • Grid:網格。可以自定義行和列,並通過行列的數量、行高和列寬來調整控件的布局,有點類似於html中的Table。
  • StackPanel:棧式面板。可以將包含元素排成一條直線,當添加或移除包含元素時,后面的元素會自動向下或向上移動。
  • Canvas:畫布。可以指定包含元素的絕對坐標位置。
  • DockPanel:泊靠式面板。內部元素可以選擇泊靠方式。
  • WarpPanel:自動折行面板。當一行元素排滿后會自動換行。類似html中的流式布局。

3.2 Grid

  Grid的特點如下:

  • 可以定義任意數量的行和列
  • 行高與列寬可以使用絕對值,相對比以及最大值和最小值
  • 內部元素可以設置自己的所在列和行,還可以設置自己跨幾列和行。
  • 可以設置Children元素的對齊方式

  現在給出定義行與列的代碼(記得在后台代碼上加上 this.grid.ShowGridLines=true以便顯示出網格):

XAML
<Window x:Class="Chapter_03.Grid"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Grid" Height="300" Width="300" MinHeight="300" MaxWidth="500">
    <!--MinHeight="300" MaxWidth="500"限制窗口的最小高度和最大寬度-->
    <Grid x:Name="grid">
        <!--定義行-->
        <Grid.RowDefinitions>
            <RowDefinition Height="25" ></RowDefinition>
            <RowDefinition Height="50"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto">
            </RowDefinition>
        </Grid.RowDefinitions>
        <!--定義列-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="25" ></ColumnDefinition>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>
        <!--在指定的行列中布置控件-->
        <TextBox Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="2" Text="布局" Background="Gray"/>
    </Grid>
</Window>

運行效果圖如圖6,可以放大觀察效果(是因為Width="*"的原因,本例子中利用了兩個*其中第三行是一個*,所以占剩余的二分之一,可以試着改成2*,就是三分之二了,可以試着觀察效果):

圖6

 3.3 StackPanel

  StackPanel可以把內部的元素在縱向或者橫向上緊密排列,形成棧式布局。先介紹一下其三個屬性:

  • Orientation 決定內部元素是橫向還是縱向累積。可取值為Horizontal,Vertical。
  • HorizontalAlignment 決定內部元素水平方向上的對齊方式。可取值Left,Center,Right,Stretch。
  • VerticalAlignment 決定內部元素豎直方向上的對齊方式。可取Top,Center,Bottom,Stretch。

StackPanel也是布局中比較常見的控件,下面舉例:添加按鈕,其他內容控件會自動下移。效果如圖7:

圖7

下面上代碼:

XAML
<Window x:Class="Chapter_03.StackPanel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StackPanel" Height="338" Width="423">
    <Grid Height="286" Width="382">
        <GroupBox Header="測試StackPanel" BorderBrush="Black" Margin="5">
            <StackPanel Margin="5" x:Name="stackpanel">
                <StackPanel Orientation="Vertical" x:Name="btnList"></StackPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                    <TextBlock Text="填寫添加按鈕名稱: " Height="20" />
                    <TextBox Name="btnName" Width="102" Height="20" />
                    <Button Content="添加" Width="60" Margin="5" Click="Button_Click" />
                </StackPanel>
            </StackPanel>
            
        </GroupBox>
        
    </Grid>
</Window>
Cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_03
{
    /// <summary>
    /// StackPane_.xaml 的交互邏輯
    /// </summary>
    public partial class StackPanel : Window
    {
        public StackPanel()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(this.btnName.Text))
            {
                Button btn = new Button();
                btn.Content = this.btnName.Text;
                this.btnList.Children.Add(btn);
            }
            else
                MessageBox.Show("請輸入按鈕名稱!");
        }
    }
}

當輸入按鈕名稱的話,點擊添加,原有的內容會下移。

3.4 Canvas  

  畫布:內容控件可以准確定位到指定坐標,但是不足的地方是,如果要修改的話可能會關系到很多的控件,所以如果不需要經常修改的窗體,使用該控件布局,或者是藝術性比較強(用來實現依賴於橫縱坐標的動畫等功能)的布局使用此控件布局。

  在此制作一個登陸頁面主要來看一下Canvas.Left與Canvas.Top的用法。效果圖如圖8,直接上代碼:

XAML
<Window x:Class="Chapter_03.Canvas"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="登陸" Height="145" Width="300">
    <Canvas Background="Sienna">
        <TextBlock  Canvas.Left="0" Canvas.Top="13" Margin="5" Text="用戶名:"/>
        <TextBox Canvas.Left="50" Canvas.Top="13" Width="160" />
        <TextBlock  Canvas.Left="0" Canvas.Top="47" Margin="5" Text="密  碼:"/>
        <TextBox Canvas.Left="50" Canvas.Top="47" Width="160" />
        <Button Content="確定" Canvas.Left="70" Canvas.Top="77"  Width="63" Height="22" />
        <Button Canvas.Left="150" Canvas.Top="77" Content="清除"  Width="63" Height="22" />
    </Canvas>
</Window>

 圖8

3.5 DockPanel

  這個控件主要有個最后一個內容控件實現填充所有剩余部分的功能。主要用到LastChildFill=True屬性。下面給出一個例子,先看一下把LastChildFill分別設置為True和False的結果對比圖如圖9:

圖9

XAML代碼給出: 

XAML
<Window x:Class="Chapter_03.DockPanel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DockPanel" Height="300" Width="300">
    <DockPanel Name="dockpanel" LastChildFill="True">
        <Button Name="button1" DockPanel.Dock="Top">1</Button>
        <Button Name="button2" DockPanel.Dock="Bottom" >2</Button>
        <Button Name="button3" DockPanel.Dock="Left">3</Button>
        <Button Name="button4" DockPanel.Dock="Right">4</Button>
        <Button DockPanel.Dock="Top">剩余空間</Button>
    </DockPanel>

</Window>

  在此說明一下,如果LastChildFill=True,最后一個元素 <Button >剩余空間</Button>就會充滿其剩余部分。上面的只能填充,但是不能通過拖拽的方式改變控件的寬度。下面給出一個實現拖拽功能的代碼。不過是在Grid里面的通過GridSplitter(可以改變Grid初始設置的行高或列寬)控件實現。直接給出代碼:

XAML
<Window x:Class="Chapter_03.GridSplitter"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GridSplitter" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="5"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox Grid.ColumnSpan="3" BorderBrush="Black"/>
        <TextBox Grid.Row="1" BorderBrush="Black"/>
        <GridSplitter Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Center"
                      Width="5" Background="Gray" ShowsPreview="True"/>
        <TextBox Grid.Row="1" Grid.Column="2" BorderBrush="Black"/>
    </Grid>
</Window>

具體的GridSplitter的屬性見http://www.cnblogs.com/luluping/archive/2011/08/26/2155218.html

3.6 WrapPanel

  此控件會根據布局的大小來控制內容元素的排列。不會因為窗體沒有放大,影響到其他內容的顯示。在此只舉一個例子,來理解WrapPanel。上代碼了:

XAML
<Window x:Class="Chapter_03.WrapPanel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WrapPanel" Height="300" Width="300">
    <WrapPanel>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
        <Button Width="50" Height="50"/>
    </WrapPanel>
</Window>

效果圖如圖10:

  圖10

四、總結

  布局一直是自己的弱項,所以可能這篇記錄的會比較差點,但是重在理解控件的作用以及能舉一反三。 雖然控件沒有一一列出,但是對於每一族的控件都給出了一個實例,可以通過實例加深對各個控件的理解,具體的運用還需多加強練習和查閱msdn。下一篇:深入淺出話Binding。 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM