Catel - 第 2 部分 (共 n 部分):使用 WPF 控件和主题






4.60/5 (19投票s)
本文将介绍 Catel 中一些最重要的控件和窗口。
Catel 是一个全新的框架(或企业库,随你怎么称呼),集数据处理、诊断、日志记录、WPF 控件和 MVVM 框架于一体。所以,Catel 不仅仅是另一个 MVVM 框架或一些好用的扩展方法。它更像是一个你会在未来开发的所有(WPF)应用程序中使用的库。
本文将介绍该框架的用户控件和主题。
文章浏览器
- Catel - 第 n 部分 1:数据处理之道
- Catel - 第 n 部分 2:使用 WPF 控件和主题
- Catel - 第 3 部分 (共 n 部分):MVVM 框架
- Catel - 第 n 部分 4:使用 Catel 进行单元测试
- Catel - 第 5 部分 (共 n 部分):在 1 小时内构建一个 Catel WPF 示例应用程序
- Catel - 第 6 部分(
共 n 部分): WP7 的 Bing Maps 应用程序 - Catel - 第 n 部分 7:Catel 2.x 有什么新特性
- Catel - 第 n 部分 8:WP7 Mango 和相机单元测试
目录
2. 简介
欢迎阅读 Catel 系列文章的第二部分。本文将介绍 Catel 中一些最重要的控件和窗口。并未涵盖所有控件,因为数量太多无法一一列举。
如果您还没有阅读过 Catel 的前一篇(或多篇)文章,强烈建议您阅读。它们已经编号,所以查找起来应该不难。
本文包含三个详细的章节
- 控件:本章介绍 Catel 中最重要的控件。
- 验证:本章介绍 Catel 的验证控件。它们非常特殊,值得单独一章介绍。
- 窗口:本章介绍 Catel 附带的窗口。
本文将介绍的每个项目都包含(如果适用)对象的图像、其设计原因,以及最终的设计方式。有时,控件或窗口的设计方式非常明显,我们会省略这部分。如果您无法弄清楚控件的工作原理,请随时告诉我们!本文不是用户控件的参考,而更多地展示 Catel 在各个方面提供的功能。Catel 附带一个帮助文件,其中包含 Catel 中所有对象方法的实际文档,如果您需要实际文档,请使用该文件。本文是一篇介绍性文章,旨在激发您对 Catel 所提供 UI 元素的兴趣。
这些控件大多需要手动集成到您自己的控件和窗口中。但是,强烈建议将 MVVM 框架与本文介绍的控件结合使用。Catel 附带的 MVVM 框架将在下一篇文章中进行描述和解释,但它充分利用了本文介绍的控件(如验证控件)的强大功能。
3. 控件
3.1. BrowseForFile
BrowseForFile
控件允许您轻松地在应用程序中添加“浏览文件”功能。该控件包含一个按钮,因此您无需自行处理。
3.1.1. 示例代码
<Controls:BrowseForFile FileName="{Binding SelectedFileName}" />
3.1.2. 为什么?
当用户需要从磁盘选择文件时,此控件非常有用。该控件使开发人员无需反复创建带有按钮的文本框,并且还集成了 OpenFileDialog
类。
3.2. DropDownButton
DropDownButton
是一个非常知名的控件,所以 WPF 开发者没有包含它有些奇怪。DropDownButton
具有自定义的 Content
属性,因此它是完全可定制的。
3.2.1. 示例代码
<Controls:DropDownButton ToolTip="Select a person"
Style="{DynamicResource ImageDropDownButtonStyle}">
<Controls:DropDownButton.DropDownContent>
<ListBox ItemsSource="{Binding PersonCollection}"
SelectedItem="{Binding Person}" Margin="0">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Resources -->
<DataTemplate.Resources>
<Style TargetType="{x:Type LocalControls:PersonSummary}">
<Setter Property="Opacity" Value="0.50" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background"
Value="{StaticResource {x:Static
SystemColors.HighlightBrushKey}}" />
<Setter Property="Cursor" Value="Hand" />
</Trigger>
<!-- On MouseEnter, set opacity -->
<EventTrigger
RoutedEvent="LocalControls:PersonSummary.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.50" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- On MouseLeave, set opacity -->
<EventTrigger
RoutedEvent="LocalControls:PersonSummary.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1" To="0.50" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<!-- Content -->
<StackPanel Margin="4">
<LocalControls:PersonSummary DataContext="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Controls:DropDownButton.DropDownContent>
<!-- Actual image on the dropdown button -->
<Image Source="/Resources/Images/group.png" />
</Controls:DropDownButton>
3.2.2. 为什么?
此控件的开发目的是允许开发人员使用知名的 DropDownButton
控件。该示例使用该控件显示用户可以选择的用户名列表,以便用户轻松选择特定用户。
3.2.3. 如何实现?
在内部,DropDownButton
控件使用一个 Popup
类,该类由 Content
属性的值填充。然后,当按钮被点击时,它会显示弹出窗口。
3.3. MultiLineInput
MultiLineInput
控件是一个方便的控件,可让用户轻松输入文本。
3.3.1. 示例代码
<Controls:MultiLineInput Caption="Please provide correct input:"
Text="{BindingProvidedText}" />
3.3.2. 为什么?
您需要多少次让用户在前面带有标签的情况下进行多行输入?确实很多!从现在开始,您可以使用这个简单的控件来获取输入,而无需再定义单独的控件。
3.3.3. 如何实现?
这是一个用户控件,它将 TextBlock
和 TextBox
组合成一个控件。
3.4. StackGrid
尽管示例看起来很糟糕(我不是设计师),但它展示了 StackGrid
的强大功能。您不必指定 Grid.Row
和 Grid.Column
附加属性。还记得您想插入一个网格,并且不得不增加所有数字的日子吗?从现在开始,使用 StackGrid
!
3.4.1. 示例代码
<Controls:StackGrid>
<!-- Row definitions -->
<Controls:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MinHeight="15" />
<RowDefinition Height="Auto" />
</Controls:StackGrid.RowDefinitions>
<!-- Column definitions -->
<Controls:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Controls:StackGrid.ColumnDefinitions>
<!-- Name, will be set to row 0, column 1 and 2 -->
<Label Content="Name" />
<TextBox Text="Geert van Horrik" />
<!-- Empty row -->
<Controls:EmptyRow />
<!-- Wrappanel, will span 2 columns -->
<WrapPanel Grid.ColumnSpan="2">
<Button Command="ApplicationCommands.Close" />
</WrapPanel>
</Controls:StackGrid>
3.4.2. 为什么?
Grid
是一个出色的控件,可以在屏幕上以漂亮的布局显示多个控件。但是,网格通常只有 2 或 3 列,第一列用于所有标签,第二列用于文本框等控件。您会根据用户需求正确实现应用程序的所有窗口和控件,然后用户决定要在包含大约 20 行的网格中插入一行。发生这种情况时,您需要重新定义网格的所有行属性。
使用 StackGrid
,不再需要定义行和列定义。StackGrid
可以智能地解释控件的位置,从而为您填充 Grid.Row
和 Grid.Column
附加属性。您需要一个空行?没问题,您可以使用 EmptyRow
类来填充一行。您想要列跨度?没问题,只需使用现有的 Grid.Column
附加属性,StackGrid
就会自动为您处理。
3.4.3. 如何实现?
StackGrid
在内部使用 Grid
来测量布局。但是,它会动态地循环遍历其子级,然后为用户分配 Grid.Row
和 Grid.Column
附加属性。
3.5. ToggleRadioButton
ToggleRadioButton
允许您轻松地向用户提问,用可空的布尔值表示多项选择答案。
3.5.1. 示例代码
<Controls:ToggleRadioButton TextCaption="Do you like this control?"
GivenAnswer="{Binding UserLikesControl}" />
3.5.2. 为什么?
有时您只想问用户一个问题,用户只能回答两个选项之一(主要是“是”或“否”)。这需要您创建多个控件,并且需要编写多个转换器将单选按钮的值映射到给定的答案。
使用 ToggleRadioButton
,您可以完全自定义问题和答案的标题。然后,您可以简单地将一个可空的布尔值绑定到 GivenAnswer
属性。当值为 null
时,用户尚未给出答案。否则,该值将是true 或 false,具体取决于给定的答案。
3.5.3. 如何实现?
ToggleRadioButton
将 TextBlock
和几个 RadioButton
控件组合成一个控件。然后,它使用转换器来确保 GivenAnswer
正确反映单选按钮的值,反之亦然。
3.6. TraceOutputControl
TraceOutputControl
是一个方便的调试控件。它显示所有跟踪输出。这样,您可以轻松地在应用程序中查看所有绑定错误等,而不是 Visual Studio 中没有颜色的输出框。
3.6.1. 示例代码
<Controls:TraceOutputControl />
3.6.2. 为什么?
很多时候,开发人员在应用程序中查看他们创建的结果。但是,他们也想知道后台发生的事情并查看他们编写的跟踪。Visual Studio 的输出窗口是一个解决方案,但它不能很好地显示错误(黑色,就像普通输出一样)。此外,它不允许对结果进行运行时过滤。
TraceOutputControl
允许开发人员将控件嵌入到实际应用程序的窗口或控件中,并在应用程序实际运行时查看信息。TraceOutputControl
也可用作单独的窗口,以防无法将其嵌入到软件本身(例如,为第三方应用程序开发插件时)。
3.6.3. 如何实现?
TraceOutputControl
将自定义 TraceListener
订阅到 Trace.Listeners
集合。然后,它过滤出用户实际想要查看的消息,并将这些消息存储在内部集合中,以便用户以后仍然可以过滤这些消息。
4. 验证
验证控件是用户控件,但值得拥有一个专门的章节,因为这些控件应该一起使用。
4.1. InfoBarMessageControl
是否想向最终用户显示 WPF 错误消息的详细信息?那么,InfoBarMessageControl
就是要使用的控件!该控件显示由实现 IDataErrorInfo
接口的对象上的绑定提供的所有业务和字段错误的摘要。
与 WarningAndErrorValidator
控件结合使用时,InfoBarMessageControl
甚至可以显示实现 Catel 附带的 IDataWarningInfo
接口的对象的字段和业务警告。
4.1.1. 示例代码
<Controls:InfoBarMessageControl>
<!-- Actual content here -->
</Controls:InfoBarMessageControl>
4.1.2. 为什么?
作为 Catel 的开发人员,我们一直在寻找一种向用户显示错误和警告的好方法。WPF 默认提供了一些显示错误给用户的方法(控件周围的红色边框),但我们认为这还不够,因为用户看到了有问题,但不知道具体是什么问题。
这就是我们对验证的设想:
- 显示字段错误消息的工具提示(我们通过 Catel 中可用的主题解决了这个问题,稍后会详细介绍);
- 一个始终位于同一位置(窗口或用户控件的顶部)的消息栏,显示所有(业务和字段)错误的摘要;
- 能够显示警告(因为有时用户输入有效,但可能不是用户预期的)。
4.1.3. 如何实现?
InfoBarMessageControl
订阅 Validation
类。该类负责显示 WPF 默认显示的控件周围的红色边框。然后,它请求数据项的实际字段错误属性。这被添加到内部错误消息集合中,因此该控件能够显示所有绑定的错误。
当 WarningAndErrorValidator
控件作为子控件找到时,InfoBarMessageControl
还会订阅 WarningAndErrorValidator
公开的事件。该控件的内部工作原理将在本文后面进行解释。当通过 WarningAndErrorValidator
订阅数据对象时,InfoBarMessageControl
还会处理该数据对象的警告和业务错误。
4.2. WarningAndErrorValidator
WarningAndErrorValidator
控件对最终用户是不可见的。该控件唯一负责的是将业务错误和警告转发给感兴趣的控件。Catel 附带的唯一控件是 InfoBarMessageControl
。由于 WarningAndErrorValidator
,InfoBarMessageControl
能够向最终用户显示业务错误和警告。
4.2.1. 示例代码
<Controls:WarningAndErrorValidator Source="{Binding MyObject}" />
4.2.2. 为什么?
.NET Framework 默认缺少一种在用户界面中显示警告和业务错误的方法。因此,Catel 的开发团队开发了 InfoBarMessageControl
,它能够显示字段和业务错误的摘要。但是,Validation
类仅提供字段错误的事件,而不提供业务错误的事件。而且,默认情况下,.NET Framework 不提供向用户显示警告(字段和业务)的方法。
WarningAndErrorValidator
为开发人员解决了这些问题。
4.2.3. 如何实现?
WarningAndErrorValidation
需要放置在 InfoBarMessageControl
中。然后,该控件订阅所有属性更改事件,以确保它收到所有更改通知。然后,在每次属性更改时,控件会检查发件人是否实现了 IDataErrorInfo
或 IDataWarningInfo
接口。
当在更改的属性上找到错误或警告时,控件会调用相应的事件,以便 InfoBarMessageControl
可以显示正确的信息。当模型中不再存在错误或警告时,会调用 Removed
事件,以便 InfoBarMessageControl
知道应从摘要中删除该错误或警告。
5. 窗口
5.1. DataWindow
在 WPF 中开发软件时,我总是需要以下三种类型的窗口:
- 数据窗口的确定/取消按钮
- 应用程序设置/选项的确定/取消/应用按钮
- 操作窗口的关闭按钮
创建这些窗口只是件乏味的事情,而且步骤总是相同的:
- 在窗口底部创建一个
WrapPanel
- 一遍又一遍地为相同的
RoutedUICommand
对象添加按钮
DataWindow
类通过指定窗口模式,可以更轻松地创建这些基本窗口。使用此窗口,您可以专注于实际实现,而无需担心按钮本身的实现,从而节省您的时间!
使用 DataWindow
时,您将获得很多免费功能。例如,您不必自己嵌入 InfoBarMessageControl
,因为它会自动为您添加。DataWindow
类可以做的不仅仅是生成默认窗口模板。还可以为窗口指定自定义按钮,以便您可以在窗口底部创建自己的按钮栏。
DataWindow
与 Catel 附带的 MVVM 框架完全兼容,并且在 Catel 中实现的几乎所有窗口中都使用了它。
5.2. MultiLineInputWindow
MultiLineInputWindow
控件是一个方便的控件,可让用户轻松输入文本。它是 MultiLineInput
控件的包装器。
5.2.1. 示例代码
// Show window
MultiLineInputWindow multiLineInputWindow = new MultiLineInputWindow();
multiLineInputWindow.ShowDialog();
5.2.2. 为什么?
您需要多少次让用户在前面带有标签的情况下进行多行输入?确实很多!从现在开始,您可以使用这个简单的控件来获取输入,而无需再定义单独的控件。
5.2.3. 如何实现?
该窗口只是将 MultiLineInput
控件包装到一个窗口中。
5.3. MultipleChoiceWindow
MultipleChoiceWindow
允许您提出一个多项选择问题(可选带有自由文本输入)。
5.3.1. 示例代码
// Create a collection of choices
List<choice> choices = new List<Choice>();
choices.Add(new Choice("Awesome", "Awesome, never seen such a great window!"));
choices.Add(new Choice("Good", "It's pretty good actually"));
choices.Add(new Choice("It's OK", "It's OK, but I've seen better"));
choices.Add(new Choice("Hmmm", "Hmmm, what shall I say?"));
// Show window
Windows.MultipleChoiceWindow multipleChoiceWindow =
new Windows.MultipleChoiceWindow(choices, true);
multipleChoiceWindow.Title = "What do you think of this window?";
multipleChoiceWindow.ShowDialog();
5.3.2. 为什么?
有时,您只想问一个简单的多项选择问题,用户必须在几个答案之间进行选择。而不是每次都创建一个自定义控件,您可以简单地创建您的选择列表,并将它们传递给 MultipleChoiceWindow
的构造函数。
这样可以避免您在每个窗口中单独指定选择,并且不必考虑在列表控件中绑定单选按钮。
5.4. PleaseWaitWindow
PleaseWaitWindow
是一个在长时间操作期间显示的绝佳窗口。还有一个 PleaseWaitHelper
类,可以使使用 PleaseWaitWindow
更加容易。
5.4.1. 示例代码
// Show window by using the PleaseWaitHelper
Catel.Windows.PleaseWaitHelper.Show(() => Thread.Sleep(2000));
5.4.2. 为什么?
我们需要一种方法在某些时候向用户显示应用程序正在忙碌。要求如下:
- 使背景变暗,以便焦点集中在窗口上;
- 显示某种动画,即使当前线程(UI)被阻塞;
- 能够自定义文本;
- 能够提供一个在窗口显示期间应执行的委托。如果委托完成,窗口应自动隐藏;
- 显示窗口的最短时间(以防止闪烁)。
5.4.3. 如何实现?
这个窗口最困难的部分是能够显示动画,即使当前的 UI 线程被当前忙碌的逻辑阻塞。幸运的是,一个名叫“Dwayne Need" 的聪明人已经 解决了这个问题。
请务必也查看 PleaseWaitHelper
类。它负责创建 PleaseWaitWindow
,并允许您通过委托显示窗口。将执行该委托,一旦委托完成,PleaseWaitWindow
就会再次隐藏。
5.5. TipOfTheDayWindow
有时,您的最终用户并不是地球上最聪明的生物。很多时候,我与最终用户交流是因为有一个“错误”或“严重问题”需要立即修复。
在“调试”会话期间(即,像最终用户描述的那样使用软件),您会点击很多键盘按键和其他有用的快捷方式,我以为用户已经熟悉这些了。不幸的是,大多数时候,他们并不熟悉。
因此,“今日提示”窗口是一个在用户启动您的软件时向他们展示一些便捷技巧的好方法。例如,了解快捷方式、“隐藏”功能等。
5.5.1. 示例代码
// Show window
TraceOutputWindow traceOutputWindow = new TraceOutputWindow();
traceOutputWindow.Show();
5.5.2. 为什么?
您可能在想:为什么为一个如此简单的窗口写一篇博客文章?嗯,因为这个“今日提示”窗口比乍一看更强大。例如,您有没有想过如何输入或管理提示?这个窗口为您处理了这个问题。
只需在“今日提示”窗口获得焦点时按CTRL + F2,您就会看到以下编辑模式:
使用包含的编辑器,您可以简单地创建、修改、删除和预览窗口中包含的所有提示。提示以 XML 格式存储在名为“Help" 的子目录中。
5.6. TraceOutputWindow
TraceOutputWindow
是一个方便的调试窗口。它的行为与 TraceOutputControl
完全相同,但它是一个单独的窗口而不是嵌入式控件。
5.6.1. 示例代码
// Show window
TraceOutputWindow traceOutputWindow = new TraceOutputWindow();
traceOutputWindow.Show();
5.6.2. 为什么?
很多时候,开发人员在应用程序中查看他们创建的结果。但是,他们也想知道后台发生的事情并查看他们编写的跟踪。Visual Studio 的输出窗口是一个解决方案,但它不能很好地显示错误(黑色,就像普通输出一样)。此外,它不允许对结果进行运行时过滤。
TraceOutputWindow
允许开发人员将跟踪输出显示为单独的窗口,并在应用程序实际运行时查看信息。如果无法将 TraceOutputWindow
嵌入到软件本身(例如,为第三方应用程序开发插件时),则应优先使用它而不是 TraceOutputControl
。
5.6.3. 如何实现?
该窗口只是将 TraceOutputControl
包装到一个窗口中。
6. 主题
6.1. 随附主题
Catel 目前附带一个主题文件。该主题文件基于 WPF 库中包含的“Aero”主题。Catel 的主题会修正边距,因为默认的“Aero”主题将所有控件的边距设置为 0,这将导致所有用户控件粘在一起,如下图所示:
我们看到太多开发人员直接修复控件上的边距,而这也可以通过使用 Catel 主题和包含的 StyleHelper
类来完成。请参见下图以了解最终结果:
6.2. 像素着色器
Catel 还使用像素着色器通过主题和样式将效果应用于控件。例如,其中一个像素着色器是 GrayscaleEffect
。当按钮禁用时,此效果会自动将按钮上的图像转换为灰度。下面是着色器效果的示例:
如果屏幕上使用了大量按钮,则视频卡可能不支持如此多的着色器,然后 WPF 将开始引发异常。在这种情况下,请首先尝试将 Catel 的着色器模式设置为 ShaderRenderMode.Software
。如果无效,您可以使用 ShaderRenderMode.Off
来关闭着色器。
// Force software rendering
StyleHelper.PixelShaderMode = PixelShaderMode.Software;
// Turn off
StyleHelper.PixelShaderMode = PixelShaderMode.Off;
6.3. StyleHelper
StyleHelper
类有几个 static
成员,它们将创建样式转发器。样式转发器是在应用程序级别定义的样式,而不是在主题级别定义的。这允许您使用与控件名称相同的键创建转发器,但它将转发到 DefaultxxxStyle
。由于新样式是在应用程序级别定义的,因此您不会遇到循环引用,因为主题中定义的样式无法访问应用程序级别的资源。
只需在 WPF 应用程序的 OnStartup
中调用 StyleHelper.CreateStyleForwardersForDefaultStyles();
即可实现此目的。
<Application x:Class="OverrideStyles.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="UI/Windows/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Set custom theme -->
<ResourceDictionary
Source="/Catel.Windows;component/themes/generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
namespace OverrideStyles
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// Create style forwarders
Catel.Windows.Helpers.StyleHelper.
CreateStyleForwardersForDefaultStyles();
// Call base
base.OnStartup(e);
}
}
}
7. 历史
- 2010 年 11 月 25 日:添加文章浏览器和简要摘要
- 2010 年 11 月 12 日:初始版本