WPF: A Beginner's Guide - Part 1 of n

翻译,原文来自CodeProject,有节选。作者Sacha Barber写文章很用心,希望我能很用心的看大笑。我更关注的是实际工程中的应用。

这篇文章主要讲解WPF中的Layout。

1)控件的属性参数Margin

通过使用Margin,我们可以指定当前的控件与四周的空间大小。WPF提供一个函数ValueConverter,可以接受一个字符串如5,5,5,5,指示当前控件与四周间隔5个像素,分别为left,top,Right,Bottom;

2)Canvas

比较简单的布局控件;是个X/Y位置控制的容器,但子容器需要指定以下四个参数:

Canvas.Left; Canves.Right; Canvas.Top; Canvas.Bottom

这看起来有些奇怪,因为我们已经有了一个Canvas.Left(文中没有更多的解释,只是说这不是我们在.Net 2.0下经常使用到的属性)。

Canvas属性就这些。另外如果碰到两个Canvas有重叠,如何确认一个在另一个上面?这时候就要考虑属性Canvas.ZIndex,用于确认哪个控件在上面,数值大的在上面;如果没有指定数值,则ZIndex的数值是按我们添加控件的顺序来改变的。

下图显示一个Canvas上包含两个子控件:


对应的XAML代码为:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.CanvasDEMO"
	x:Name="Window"
	Title="CanvasDEMO"
        WindowStartupLocation="CenterScreen" 
	Width="640" Height="480">

		<Canvas Margin="0,0,0,0" Background="White">
			<Rectangle Fill="Blue" 
				Stroke="Blue" 
				Width="145" 
				Height="126" 
				Canvas.Left="124" Canvas.Top="122"/>
			<Ellipse Fill="Green" 
				Stroke="Green" 
				Width="121" Height="100" 
				Panel.ZIndex="1" 
				Canvas.Left="195" Canvas.Top="191"/>
		</Canvas>

</Window>
3)StackPanel

比较简单,只是stack它包含的控件,以竖直或水平方向,属性名为Orientation。

下图显示一个stackPanel包含两个子控件,一个在上,一个在下:


对应的XAML代码为:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.StackPanelDEMO"
	x:Name="Window"
	Title="StackPanelDEMO"
        WindowStartupLocation="CenterScreen" 
	Width="640" Height="480">

        <StackPanel Margin="0,0,0,0" Background="White" Orientation="Vertical">
            <Button Content="Im Top of Stack"/>
            <Button Content="Im Bottom Of Stack"/>
        </StackPanel>

</Window>
4)WrapPanel

解释为;It simply wraps its contents.

下图显示一个WrapPanel包含10个子控件:


对应的XAML代码:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.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 ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>
        </WrapPanel>

</Window>

代码中·10个Rectangle一模一样,的确,WrapPanel什么都没做,只是提供一个容器的功能。

5)DockPanel

DockPanel用途比较广,可以作为窗体的基本容器,实现如:menu在上,左右内容显示区域,加底下一个状态显示栏。比较重要的属性是DockPanel.Dock及LastChildFill,后者会改变原来的布局(填满剩下的空间),如果设为true的话。

下图显示一个DockPanel包含两个子控件,一个在上面,另外一个填充了剩余的空间:


对应的XAML代码为:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.DockPanelDEMO"
	x:Name="Window"
	Title="DockPanelDEMO"
        WindowStartupLocation="CenterScreen"
	Width="640" Height="480">

    <DockPanel Width="Auto" Height="Auto" LastChildFill="True">
        <Rectangle Fill="CornflowerBlue" Stroke="CornflowerBlue" Height="20" DockPanel.Dock="Top"/>
        <Rectangle Fill="Orange" Stroke="Orange"  />
    </DockPanel>
</Window>
6)Grid

比较复杂,类似于一个HTML中的table控件,可以指定row,column数量及跨行跨列;同时还有一个比较奇怪的语法标识"*",如

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="40"/>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>

表示3列:第一列固定宽度40pixel;剩下空间被分成两列,第三列宽度是第二列的两倍。语法同样适用于行。

控制Grid上子控件的位置使用(Index从0开始的):

Grid.Column

Grid.Row

控制某一cell所占据的大小使用(Index从1开始):

Grid.ColumnSpan

Grid.RowSpan

下图显示Grid控件包含3列1行,第一个控件Column=1,第二个占据了2-3列,这是由于Grid.ColumnSpan=2的缘故。


对应的XAML代码为:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.GridDEMO"
	x:Name="Window"
	Title="GridDEMO"
        WindowStartupLocation="CenterScreen"
	Width="640" Height="480">

    <Grid Width="Auto" Height="Auto" >
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        
        <Rectangle Fill="Aqua" Grid.Column="0" Grid.Row="0"/>
        <Rectangle Fill="Plum" Grid.Column="1" Grid.ColumnSpan="2"/>

    </Grid>
</Window>
7)Putting it all Together

考虑上面所说的WinForm经典界面:


包括:菜单栏,按钮区及显示区,外加底部状态栏,用WPF来完成,XAML代码为:

<Window x:Class="WPF_Tour_Beginners_Layout.PuttingItAllTogether"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    WindowStartupLocation="CenterScreen"    
    Title="PuttingItAllTogether" Width="640" Height="480" >

    <DockPanel Width="Auto" Height="Auto" LastChildFill="True">
        <!--Top Menu Area-->
        <Menu Width="Auto" Height="20" Background="SteelBlue" DockPanel.Dock="Top">
            <!-- File Menu -->
            <MenuItem Header="File">
                <MenuItem Header="Save"/>
                <Separator/>
                <MenuItem Header="Exit"/>
            </MenuItem>
            <!-- About Menu -->
            <MenuItem Header="Help">
                <MenuItem Header="About"/>
            </MenuItem>
        </Menu>

        <!--Bottom Status Bar area, declared before middle section, as I want it to fill entire
        bottom of Window, which it wouldnt if there was a Left docked panel before it -->
        <StackPanel Width="Auto" Height="31" Background="LightGray" Orientation="Horizontal" DockPanel.Dock="Bottom">
            <Label Width="155" Height="23" Content="Status Bar Message...." FontFamily="Arial" FontSize="10"/>
        </StackPanel>

        <!--Left Main Content area-->
        <StackPanel Width="136" Height="Auto" Background="Gray">
            <Button Margin="5,5,5,5" Width="Auto" Height="26" Content="button1"/>
            <Button Width="126" Height="26" Content="button2" Margin="5,5,5,5"/>
            <Button Width="126" Height="26" Content="button3" Margin="5,5,5,5"/>
        </StackPanel>


        <!--Right Main Content area, NOTE HOW this Grid is the last child
        so takes all the remaining room -->
        <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="Aqua" Margin="10,10,10,10" Grid.Row="0" Grid.Column="0"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="0" Grid.Column="1"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="1" Grid.Column="0"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="1" Grid.Column="1"/>
        </Grid>

    </DockPanel>
</Window>

设计DockPanel时依次是Menu->Bottom Staus -> leftButtons -> Right Shown,注意DockPanel的LastChildFill属性为true;同时status bar的StackPanel必须始于其它控件(声明为DockPanel.Dock=Left或DockPanel.Dock=Right)。

结果界面与WinForm相同(没有展示)。

8)Performance Considerations

考虑父控件中包含大量的子控件,如一个StackPanel中有一个ListBox,而ListBox与一个大型的询问数据库相连。这种情况下,ListBox包含很多子控件(使用StackPanel作为子控件?),看起来这不是个好主意。

然而,在ListBox使用VirtualizingStackPanel.IsVirtualizing,这意味着ListBox控件内部的StackPanel将会可见?

当一个Panel可见,意即可视化部分被创建,如当创建的ListBox与有100000行的数据库相连用于显示图像,这样加载ListBox会耗费很长时间。如果使用Virtualize Panel,只有UI上可见部分的图像被创建。当向下拉动滚动条,当前显示的部分被销毁,新的可见部分会被加载。目前只有一个Panel支持Virtualization,就是VirtualizingStackPanel。如果需要新的Virtualized Panel,就需要自己写。

9)Custom Layouts

创建自己喜欢的Panel,需要从System.Windows.Controls.Panel派生,然后重写函数:MeasureOverride,LayoutOverride,可以参考FishEye Panel

10)后记

我在运行实例代码时,发现主界面很好看:


点击上面任何一个*.xaml Demo,弹出的窗体(上文有显示)会覆盖这个界面,类似于只看到一个界面,找到对应的XAML代码:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="WPF_Tour_Beginners_Layout.DEMOLauncher"
	x:Name="Window"
	Title="DEMOLauncher"
        WindowStartupLocation="CenterScreen" Width="640" Height="480">

		<DockPanel Margin="0,0,0,0" Background="White" LastChildFill="true" x:Name="dockContainer">
			<Label Height="44" Content="From this page you can launch all the DEMO windows" FontFamily="Arial Rounded MT" FontSize="16" Margin="0,0,0,0" VerticalAlignment="Top" HorizontalAlignment="Center" Width="{Binding Path=ActualWidth, ElementName=dockContainer, Mode=Default}" Background="#FF000000" Foreground="#FFFFFFFF" DockPanel.Dock="Top"/>

			<ListBox x:Name="lstDEMOS" Width="Auto" Height="Auto" IsSynchronizedWithCurrentItem="True"/>

		</DockPanel>
</Window>

代码比较简单,其中Binding Path的Actual Width为DockPanel自身属性值,Element name就是DockPanel。

再看看C#代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;

namespace WPF_Tour_Beginners_Layout
{
	public partial class DEMOLauncher
	{
		public DEMOLauncher()
		{
			this.InitializeComponent();
                        lstDEMOS.SelectionChanged += new SelectionChangedEventHandler(lstDEMOS_SelectionChanged);
                        lstDEMOS.Items.Add("CanvasDEMO.xaml");
                        lstDEMOS.Items.Add("CanvasDEMOCodeBehind.xaml");
                        lstDEMOS.Items.Add("StackPanelDEMO.xaml");
                        lstDEMOS.Items.Add("StackPanelDEMOCodeBehind.xaml");
                        lstDEMOS.Items.Add("WrapPanelDEMO.xaml");
                        lstDEMOS.Items.Add("WrapPanelDEMOCodeBehind.xaml");
                        lstDEMOS.Items.Add("DockPanelDEMO.xaml");
                        lstDEMOS.Items.Add("DockPanelDEMOCodeBehind.xaml");
                        lstDEMOS.Items.Add("GridDEMO.xaml");
                        lstDEMOS.Items.Add("GridDEMOCodeBehind.xaml");
                        lstDEMOS.Items.Add("PuttingItAllTogether.xaml");
		}

                void lstDEMOS_SelectionChanged(object sender, SelectionChangedEventArgs e)
                {
                    switch (lstDEMOS.SelectedValue.ToString())
                    {
                        case "CanvasDEMO.xaml":
                            CanvasDEMO demoWin1 = new CanvasDEMO();
                            demoWin1.Owner = this;
                            demoWin1.ShowDialog();
                            break;
                        case "CanvasDEMOCodeBehind.xaml":
                            CanvasDEMOCodeBehind demoWin2 = new CanvasDEMOCodeBehind();
                            demoWin2.Owner = this;
                            demoWin2.ShowDialog();
                            break;
                        case "StackPanelDEMO.xaml":
                            StackPanelDEMO demoWin3 = new StackPanelDEMO();
                            demoWin3.Owner = this;
                            demoWin3.ShowDialog();
                            break;
                        case "StackPanelDEMOCodeBehind.xaml":
                            StackPanelDEMOCodeBehind demoWin4 = new StackPanelDEMOCodeBehind();
                            demoWin4.Owner = this;
                            demoWin4.ShowDialog();
                            break;
                        case "WrapPanelDEMO.xaml":
                            WrapPanelDEMO demoWin5 = new WrapPanelDEMO();
                            demoWin5.Owner = this;
                            demoWin5.ShowDialog();
                            break;
                        case "WrapPanelDEMOCodeBehind.xaml":
                            WrapPanelDEMOCodeBehind demoWin6 = new WrapPanelDEMOCodeBehind();
                            demoWin6.Owner = this;
                            demoWin6.ShowDialog();
                            break;
                        case "DockPanelDEMO.xaml":
                            DockPanelDEMO demoWin7 = new DockPanelDEMO();
                            demoWin7.Owner = this;
                            demoWin7.ShowDialog();
                            break;
                        case "DockPanelDEMOCodeBehind.xaml":
                            DockPanelDEMOCodeBehind demoWin8 = new DockPanelDEMOCodeBehind();
                            demoWin8.Owner = this;
                            demoWin8.ShowDialog();
                            break;
                        case "GridDEMO.xaml":
                            GridDEMO demoWin9 = new GridDEMO();
                            demoWin9.Owner = this;
                            demoWin9.ShowDialog();
                            break;
                        case "GridDEMOCodeBehind.xaml":
                            GridDEMOCodeBehind demoWin10 = new GridDEMOCodeBehind();
                            demoWin10.Owner = this;
                            demoWin10.ShowDialog();
                            break;
                        case "PuttingItAllTogether.xaml":
                            PuttingItAllTogether demoWin11 = new PuttingItAllTogether();
                            demoWin11.Owner = this;
                            demoWin11.ShowDialog();
                            break;
                    }
                }
	}
}

也很简洁,ListBox的SelectionChanged事件也是在C#声明并完成的,使用ShowDialog()显示Demo窗体,这样就看不到原来的窗体(关掉Demo窗体后才能看到)。

真正做到了界面与数据的分离。


猜你喜欢

转载自blog.csdn.net/huan_126/article/details/80280349