WPF设计时特性加速界面设计
很对 WPF 开发者在开发过程中,尤其是在开发UI过程中,会遇到没有数据,看不出效果的情况,例如:
Window
的DataContext
因为在后台代码中赋值,导致在设计时无法看到绑定的数据,也无法在书写绑定时获得智能提示。- 存在一个默认处于折叠状态的
Expander
控件,然而在设计阶段无法看到折叠后的效果,只能在运行时进行查看,或者临时对IsExpanded
属性加以修改; - 于
TextBlock
控件的设计过程中,由于不能预览字体、字号、颜色等属性,所以得临时给Text
属性赋予一个值,查看效果后再将其删除;
倘若你曾有此类困扰,那么这篇文章必然能够帮到你。本文会阐述 WPF 里设计时特性的运用办法,使你在设计之时就能见到更多的成效,提升开发的效率。
基本概念
设计时特性(Design-Time Attributes)是 WPF 中的一种特性,用于在设计时为控件提供更多的信息,以便在设计时能够更好地预览控件的效果。这一功能其实默认一直都是开启的,。比如我们在 WPF 中新建一个 窗口,那么就会在 XAML 的开头看到类似这样的代码:
xml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
</Window>
在起始处,模板会自动为我们增添众多的 XML 命名空间(xmlns
),然而许多开发者或许仅仅知晓 xmlns
与 xmlns:x
这两个,并且常常忽视了其余的几个。但实际上其余的几个(xmlns:mc
、xmlns:d
)的确提供了设计时特性的支撑。最为典型的示例就好比上面的 d:DesignHeight
和 d:DesignWidth
,这两个属性正是用于在设计时明确控件的高度和宽度的。这些特性最为显著的特点在于,它们仅仅在设计时发挥作用,不会对运行时的效果产生影响。
常用设计时特性
比如 TextBlock
的 Text
在设计时并没有内容(比如是 DynamicResource
从运行时加载的语言文件中获取,或绑定了 ViewModel
中的属性,但是设计时这个属性没有值,却又想预览字体效果,这时候就可以使用 d:Text
来指定设计时的文本内容:
<TextBlock Text="{DynamicResource ResourceKey=HelloWorld}" d:Text="Hello, World!" />
这样在设计时就可以看到 Hello, World!
这个文本了。
如果 TextBlock
的内容是用多个 Run
组合而成,那么我们也可以用这样的方式来实现:
xml
<TextBlock>
<d:TextBlock.Inlines>
<Run Text="Hello, " />
<Run Text="World!" FontWeight="Bold" />
</d:TextBlock.Inlines>
</TextBlock>
<!-- 或者也可以用下面将要介绍的虚拟控件 -->
<d:TextBlock>
<Run Text="Hello, " />
<d:Run
FontSize="50"
FontWeight="Bold"
Text="World!" />
</d:TextBlock>
其他类似的例子还比如:
- 有一个只在特殊情况下才会展示的进度条,希望查看它的效果:
d:Visibility="Visible"
- 有一个平时默认折叠的面板,想查看效果:
d:IsExpanded="True"
- 一个用户控件,想给它一个相对合理的默认大小:
d:DesignHeight="600"
或d:Height
- 一个下拉框,想查看选中项的预览效果:
d:SelectedIndex="0"
- 一个导航用的
ContentControl
,我们希望预览导航到某一页的效果,就可以写d:ContentControl.Content
,这个有点特殊,为什么我们不直接将想要导航的内容以下面要提到的虚拟控件的方式添加到ContentControl
之中呢?因为通常来说,我们写的导航页面是借助UserControl
实现的,而它的命名空间通常为xmlns:local
等。对于这样的命名空间,我们没有办法使用d:
技巧,所以这里我们选择为ContentControl
的Content
属性添加d:
特性。
虚拟控件
我们不仅可以借助设计时特性来实现控件某些属性的虚拟,还可以虚拟整个控件出来:
<d:Button Content="Virtual Button" Style="{StaticResource MyButtonStyle}" />
这样就可以在设计时看到一个虚拟的按钮了,而不需要在运行时才能看到效果。
还有一种常见情形是,我们设计的软件会让用户去手动添加一些项目,从而动态生成对应的控件。对于这样的情况,我们如果能在设计时就看到一些"生成"出来的控件,那么就能更好地开发样式了。此时,我们添加一些虚拟控件,就可以满足这个需求。
xaml
<ItemsControl>
<d:ItemsControl.Items>
<Button Content="Button 1" />
<Button Content="Button 2" />
<Button Content="Button 3" />
</d:ItemsControl.Items>
</ItemsControl>
设计时数据
我们大部分的时候使用了MVVM 模式,其数据通常是通过 this.DataContext = new ViewModel();
来添加 ViewModel
,导致在设计时无法看到绑定的数据,也无法获得智能提示。这时候我们可以使用 d:DataContext
来指定设计时的数据:
xml
<Window ...
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}">
</Window>
DesignInstance
还有一个 IsDesignTimeCreatable
属性,用于指定是否在设计时创建实例。如果设为 True
,还将能够在设计时看到一些 ViewModel 中属性的默认值。
或者我们还可以这样写,并且还可以在 XAML 中定制一些 ViewModel 的属性的初始值,便于观察效果:
xml
<Window ...>
<d:DataContext>
<vm:MainViewModel Message="Hello!" />
</d:DataContext>
</Window>
<StackPanel>
<TextBlock FontSize="50" Text="{Binding Message}" />
</StackPanel>
列表项
如果我们有一个 ListBox
,并且想要查看列表项的效果,可以这样:
xml
<ListBox ItemsSource="{Binding Students}">
<d:ListBox.ItemsSource>
<x:Array Type="model:Student">
<model:Student Name="Alice" Age="18" />
<model:Student Name="Bob" Age="19" />
<model:Student Name="Charlie" Age="20" />
</x:Array>
</d:ListBox.ItemsSource>
</ListBox>
例:
xml
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="3" Text="{Binding Name}" />
<TextBlock Margin="3" Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<d:ListBox.ItemsSource>
<x:Array Type="{x:Type local:Person}">
<local:Person Name="Alic" Age="12" />
<local:Person Name="Alic" Age="12" />
<local:Person Name="Alic" Age="12" />
</x:Array>
</d:ListBox.ItemsSource>
</ListBox>
如果不想写 x:Array
,而是希望采用传统的为 Items
添加控件的方式添加预览项,也可以这样,要设置d:ItemsSource="{x:Null}
:
xml
<ListBox ItemsSource="{Binding Students}" d:ItemsSource="{x:Null}">
<ListBox.Items>
<ListBoxItem>Student 1</ListBoxItem>
<ListBoxItem>Student 2</ListBoxItem>
<ListBoxItem>Student 3</ListBoxItem>
</ListBox.Items>
</ListBox>
这里额外写一个 d:ItemsSource="{x:Null}"
是因为 ItemsSource
和 Items
两个属性不能同时使用,所以我们需要将 ItemsSource
设置为 null
,就可以避免这个报错了。
除了这些,如果我们想要预览的是比较简单的数据,或者我们并不非常关心数据的内容及格式,只是希望生成几个项目从而查看样式或模板的书写是否有问题,那么还有一个更简单的方法:
<ListBox ItemsSource="{Binding Students}" d:ItemsSource="{d:SampleData ItemCount=5}" />
这样就可以生成 5 个虚拟的列表项了,这个很强大也很方便。
xml
<ListBox d:ItemsSource="{d:SampleData ItemCount=3}" ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="3" Text="{Binding Name}" />
<TextBlock Margin="3" Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>