原文:
最近一直没时间更新这系列文章,因为我一直在埋头编写我的第一个WPF应用程序:
今天开始编写附属的加密/解密工具,对UI自动化布局有些心得,就此分享出来。
我们先来看一下实现的效果:
这样的界面是怎么个自动法呢?请看下图:
就是说界面在适度的伸缩时,不会破坏其布局结构及美观性。
这样做有什么好处呢?你可以想象以下情况:
1.需要用户或通过程序控制变更窗体尺寸以适应特殊要求时。UI自动化可以伸缩各元素以适应变化后的窗体。
2. 软件语言变更,或需要字体/局部元素缩放时。将设计初始时的语言改变为其他语言,按钮、标签等元素呈现所需的最小空间都会有增减,UI自动化可以调整变动元素,并使与之相关的元素也能做出相应的调整。
3.需要对UI设计进行更改时。可以说执行UI自动化是一件一劳永逸的事,此后你都不必再为诸如一个标签的文字变更而去调整其他元素的坐标与尺寸了。
当然,相比通过窗体设计器“画”界面来说,UI自动化需要更多的知识与经验、更复杂的设计步骤以及更少的可视化编辑器支持,有时可视化的编辑行为甚至会破坏你已有的UI自动化布局,所以要谨慎操作。
下面我们就来谈一谈UI自动化所需的各种技巧:
让窗口适应内容的大小
我们通常所做的都是为Window设置固定的尺寸,然后再向里面填东西,但是也许反过来会更便于操作和使用。
清除Window的宽度和高度,并为之设置 SizeToContent="WidthAndHeight" 即可。
同理,Window内的布局容器(如Grid)也应当避免使用绝对宽度和高度,除非在其内容无法决定宽度或高度的情况下。
但请注意,此类设置可以留在布局即将完成时再进行,否则你可能会在编辑器里看到一个0宽0高的窗体,从而没有了可视化的操作空间。或者,你可以为容器设置较小的MinHeight和MinWidth值,这样它们即保留了伸缩特性,又会具有一定的可视/操作空间。
使用Grid进行全局性布局
微软把 Grid 作为 Visual Studio 的默认 WPF 模板的布局不是没有道理的,Grid的确可为各种布局控件之首。
新接触WPF的朋友可能都对这个复杂的家伙有所退避,比如我开始就每次都把它删掉,换成简单的WrapPanel什么的,可一旦掌握了使用它的方法,那真是爱不释手。
Grid 可以说是结合了传统的表格布局与坐标布局的优点的新型布局方案。
如你所见,蓝色的线条就是Grid划分的格子,一些元素老老实实的呆在一个格子里,还有一些毫不客气的横跨了多个格子。
要设计这样的布局,你需要拿出当年W3C跑出来叫板之前的表格布局网页理念,才能游刃有余地安排元素。
元素在格子内的相对位置由其 Margin 操控。
其相对的方向采用上、下、左、右均可,但横向和竖向都至少依赖一个方向,通过VerticalAlignment和HorizontalAlignment属性设置。
我们来看一下我这个布局的Grid是如何定义的:
我定义了3个列,都没有设置宽度,这三列用来承载最下方的三个按钮,我希望按钮的最小尺寸会根据其内容而定,所以不进行绝对值设置。
我又定义了5个行,其中4个是具有绝对的高度值,中间的一个就是ListBox所在的区域,我希望界面高度伸缩时,唯一可随之变化的就是这个区域,所以设置为*,但是这时如果我不为Grid指定绝对高度值的话,Grid将无法判断中间这个区域的高度具体应为多少,它把它会变为0,所以我指定了Grid的高度为391。
让按钮自动化
我们可以不为按钮指定固定的宽度或高度,这样它就会随其内容而伸缩了,不过这样按钮边缘会紧贴其内容,这可以通过设置其Padding属性来进行调整:
用内容和 Padding 属性撑起了按钮的最小尺寸后,还可以通过 Margin 属性与其所在的 Grid 格子边缘进行关联,这样在格子变小时,不会小于按钮的最小尺寸,格子变大时,按钮会随之伸展。
其他元素大都也与按钮同理,如Label、TextBlock。
利用DockPanel进行局部布局
虽然Grid很棒,但我们不能完全依赖于它,满屏幕画上密密麻麻的格子或是一层又一层嵌套的Grid都不便于设计和维护,并且Grid布局会消耗很多的运算资源。
对于局部的布局,可以采用更轻量级的布局方案,有时会获得更好的收效,DockPanel就是一种简便的表格布局控件。
我通过DockPanel实现了这个区域的布局:
这个区域的布局要求是:
1.标签居左,采用最小宽度
2.按钮居右,采用最小宽度
3.中间的空档由密码框完全占据
实现的代码是这样:
通过DockPanel.Dock属性可以设置元素位于DockPanel布局方位。
标签未设置该属性,因为其默认为居左,也就是DockPanel.Dock="Left"。
按钮设置为 DockPanel.Dock="Right" 。
本该排列在第二,也就是会居中并填满空白区域的密码框写在了最后,并且也未设置DockPanel.Dock属性,难道它也是采用了默认的 DockPanel.Dock="Left" 吗?非也,如果你显式设置为DockPanel.Dock="Left",它就会向左排在标签后面,并缩成一小团,非常难看。这其实是因为DockPanel有一个默认为真的属性:
最后一个元素会用以填满剩余的区域。
这个设计手法看起来是不是很像CSS布局时设置的Float浮动属性?
好了,就分享到这里了,代码奉上:
Code <Window x:Class="MailMailPassBox.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Style="{StaticResource subWindow}" MouseLeftButtonDown="Window_MouseLeftButtonDown" Loaded="Window_Loaded" DragEnter="Window_DragEnter" Drop="Window_Drop" Title="Window1" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" ShowInTaskbar="True" AllowDrop="True"> <Grid Height="391"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="15" /> <RowDefinition Height="60" /> <RowDefinition Height="*" /> <RowDefinition Height="28" /> <RowDefinition Height="42" /> </Grid.RowDefinitions> <Border Background="#989898" BorderBrush="#989898" BorderThickness="2,0,2,2" Margin="0,0,5.643,0" Height="14.75" VerticalAlignment="Top" Grid.ColumnSpan="3" HorizontalAlignment="Right" Width="111"> <WrapPanel> <CheckBox ToolTip="{StaticResource 窗口总在最前提示}" Height="12.75" Name="top" Click="top_Click" Background="{StaticResource top}" Width="35" Content="Close" Margin="0,0,1,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Template="{StaticResource barCheckBox}"/> <Button Height="12.75" Name="mini" ToolTip="{StaticResource 最小化提示}" Click="mini_Click" Background="{StaticResource mini}" Width="35" Content="Close" Margin="0,0,1,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Template="{StaticResource barButton}"/> <Button Height="12.75" Name="close" ToolTip="{StaticResource 关闭提示}" Click="close_Click" Background="{StaticResource close}" Width="35" Content="Close" Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Template="{StaticResource barButton}"/> </WrapPanel> </Border> <Rectangle Fill="{StaticResource title2}" Margin="7,0" Name="rectangle1" Stroke="Black" Grid.Row="1" StrokeThickness="0" Grid.ColumnSpan="3" /> <GroupBox Grid.Row="2" Margin="7" Name="groupBox1" Grid.ColumnSpan="3"> <GroupBox.Header> <WrapPanel> <TextBlock>待加密/解密文件 (</TextBlock> <TextBlock Text="{Binding ElementName=listBox1, Path=Items.Count}"/> <TextBlock>)</TextBlock> </WrapPanel> </GroupBox.Header> <ListBox PreviewMouseRightButtonDown="listBox1_PreviewMouseRightButtonDown" MouseDoubleClick="listBox1_MouseDoubleClick" ToolTip="{StaticResource 文件列表提示}" Name="listBox1" SelectionMode="Multiple"> <ListBox.ContextMenu> <ContextMenu> <MenuItem Name="cmi2" Header="删除所有选中的文件" Click="cmi2_Click"/> <MenuItem Name="cmi3" Header="清空文件列表" Click="cmi3_Click"/> </ContextMenu> </ListBox.ContextMenu> </ListBox> </GroupBox> <DockPanel Grid.Row="3" Margin="0" Name="dockPanel1" Grid.ColumnSpan="3"> <Label Height="28" Name="label1" Margin="7,0,0,0">密钥:</Label> <Button Height="23" Name="button4" DockPanel.Dock="Right" Padding="8,3" Margin="7,0">粘帖入</Button> <PasswordBox Height="23" Name="passwordBox1"/> </DockPanel> <Button Grid.Row="4" Margin="7" Name="button1" Padding="8,3" MinWidth="64">直接加密/解密</Button> <Button Grid.Column="1" Grid.Row="4" Margin="0,7" Name="button2" Padding="8,3" MinWidth="64">加密/解密并备份原文件</Button> <Button Grid.Column="2" Grid.Row="4" Margin="7" Name="button3" Padding="8,3" MinWidth="64">加密/解密到指定目录..</Button> </Grid></Window>
当然,你不能期待直接编译或使用这个代码了,它只是我的程序的一部分。
我会在以后更多地分享我在编写 期间获得的经验的,现在的任务是赶紧把它造出来,嘿嘿。
现在是这个样子滴:
更多抓图还是看我这个帖子了: