- WPF 教程
- WPF - 首页
- WPF - 概述
- WPF - 环境搭建
- WPF - Hello World
- WPF - XAML 概述
- WPF - 元素树
- WPF - 依赖属性
- WPF - 路由事件
- WPF - 控件
- WPF - 布局
- WPF - 布局嵌套
- WPF - 输入
- WPF - 命令行
- WPF - 数据绑定
- WPF - 资源
- WPF - 模板
- WPF - 样式
- WPF - 触发器
- WPF - 调试
- WPF - 自定义控件
- WPF - 异常处理
- WPF - 本地化
- WPF - 交互
- WPF - 2D 图形
- WPF - 3D 图形
- WPF - 多媒体
- WPF 有用资源
- WPF 快速指南
- WPF - 有用资源
- WPF - 讨论
WPF 快速指南
WPF - 概述
WPF 代表 Windows Presentation Foundation。它是一个功能强大的框架,用于构建 Windows 应用程序。本教程解释了构建 WPF 应用程序所需了解的功能,以及它如何从根本上改变 Windows 应用程序。
WPF 最初在 .NET Framework 3.0 版本中引入,随后在后续的 .NET Framework 版本中添加了许多其他功能。
WPF 架构
在 WPF 之前,Microsoft 提供的其他用户界面框架(如 MFC 和 Windows 窗体)只是 User32 和 GDI32 DLL 的包装器,但 WPF 对 User32 的使用最少。所以,
- WPF 不仅仅是一个包装器。
- 它是 .NET Framework 的一部分。
- 它包含托管代码和非托管代码的混合。
WPF 架构的主要组件如下图所示。WPF 最重要的代码部分是:
- 表示框架 (Presentation Framework)
- 表示核心 (Presentation Core)
- Milcore
表示框架和表示核心是用托管代码编写的。Milcore是非托管代码的一部分,它允许与 DirectX(负责显示和渲染)紧密集成。CLR通过提供许多功能(例如内存管理、错误处理等)来提高开发效率。
WPF - 优点
在早期的 GUI 框架中,应用程序的外观和行为之间没有真正的分离。GUI 和行为都在同一种语言中创建,例如 C# 或 VB.Net,这需要开发人员付出更多努力来实现 UI 及其关联的行为。
在 WPF 中,UI 元素在 XAML 中设计,而行为可以在 C# 和 VB.Net 等过程语言中实现。因此,很容易将行为与设计器代码分离。
使用 XAML,程序员可以与设计人员并行工作。GUI 及其行为的分离使我们能够轻松地使用样式和模板来更改控件的外观。
WPF - 功能
WPF 是一个创建 Windows 应用程序的强大框架。它支持许多强大的功能,其中一些列在下面:
功能 | 描述 |
---|---|
控件内嵌控件 | 允许将一个控件定义为另一个控件的内容。 |
数据绑定 | 在用户界面上的 UI 元素和数据对象之间显示和交互数据的机制。 |
媒体服务 | 提供一个集成系统,用于使用常见的媒体元素(如图像、音频和视频)构建用户界面。 |
模板 | 在 WPF 中,您可以使用模板直接定义元素的外观。 |
动画 | 在用户界面上构建交互性和移动性。 |
替代输入 | 支持 Windows 7 及更高版本上的多点触控输入。 |
Direct3D | 允许显示更复杂的图形和自定义主题。 |
WPF - 环境搭建
Microsoft 提供了两个重要的 WPF 应用程序开发工具。
- Visual Studio
- Expression Blend
这两个工具都可以创建 WPF 项目,但事实是 Visual Studio 更受开发人员使用,而 Blend 更受设计人员使用。在本教程中,我们将主要使用 Visual Studio。
安装
Microsoft 提供了 Visual Studio 的免费版本,可以从 VisualStudio 下载。
下载文件并按照以下步骤在您的系统上设置 WPF 应用程序开发环境。
下载完成后,运行安装程序。将显示以下对话框。
单击安装按钮,它将启动安装过程。
安装过程成功完成后,您将看到以下对话框。
关闭此对话框,如果需要,重新启动计算机。
现在从“开始”菜单打开 Visual Studio,将打开以下对话框。
- 一切完成后,您将看到 Visual Studio 的主窗口。
您现在可以构建您的第一个 WPF 应用程序了。
WPF - Hello World
在本节中,我们将开发一个简单的 Hello World WPF 应用程序。让我们按照以下步骤开始简单的实现。
- 单击“文件”>“新建”>“项目”菜单选项。
- 将显示以下对话框。
在“模板”下,选择 Visual C#,在中间面板中,选择 WPF 应用程序。
为项目命名。在名称字段中键入HelloWorld,然后单击“确定”按钮。
默认情况下,将创建两个文件,一个是XAML文件 (mainwindow.xaml),另一个是CS文件 (mainwindow.cs)
在 mainwindow.xaml 中,您将看到两个子窗口,一个是设计窗口,另一个是源(XAML)窗口。
在 WPF 应用程序中,有两种方法可以为您的应用程序设计 UI。一种是简单地将 UI 元素从工具箱拖放到设计窗口。第二种方法是通过为 UI 元素编写 XAML 标记来设计 UI。Visual Studio 在使用拖放功能进行 UI 设计时处理 XAML 标记。
在 mainwindow.xaml 文件中,默认情况下会编写以下 XAML 标记。
<Window x:Class = "HelloWorld.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Grid> </Grid> </Window>
- 默认情况下,Grid 被设置为页面后的第一个元素。
- 让我们转到工具箱并将一个 TextBlock 拖到设计窗口。
- 您将在设计窗口中看到 TextBlock。
当您查看源窗口时,您会看到 Visual Studio 已为您生成了 TextBlock 的 XAML 代码。
让我们在 XAML 代码中将 TextBlock 的 Text 属性从 TextBlock 更改为 Hello World。
<Window x:Class = "HelloWorld.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <TextBlock x:Name = "textBlock" HorizontalAlignment = "Left" Margin = "235,143,0,0" TextWrapping = "Wrap" Text = "Hello World!" VerticalAlignment = "Top" Height = "44" Width = "102" /> </Grid> </Window>
- 现在,您也会在设计窗口中看到更改。
编译并执行上述代码后,您将看到以下窗口。
恭喜!您已经设计并创建了您的第一个 WPF 应用程序。
WPF - XAML 概述
使用 WPF 时,您将遇到的第一件事之一是 XAML。XAML 代表可扩展应用程序标记语言。它是一种基于 XML 的简单声明性语言。
在 XAML 中,很容易创建、初始化和设置具有层次关系的对象的属性。
它主要用于设计 GUI,但也可以用于其他目的,例如声明工作流基础中的工作流。
基本语法
创建新的 WPF 项目时,您将在 MainWindow.xaml 中默认遇到一些 XAML 代码,如下所示。
<Window x:Class = "Resources.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "525"> <Grid> </Grid> </Window>
上述 XAML 文件包含不同类型的信息。下表简要解释了每条信息的含义。
信息 | 描述 |
---|---|
<Window | 它是根的起始对象元素或容器。 |
x:Class = "Resources.MainWindow" | 这是一个部分类声明,它将标记连接到后面定义的部分类代码。 |
xmlns = "http://schemas.microsoft.com/win fx/2006/xaml/presentation" | 为 WPF 客户端/框架映射默认 XAML 命名空间。 |
xmlns:x = "http://schemas.microsoft.com/w infx/2006/xaml" | XAML 语言的 XAML 命名空间,将其映射到 x: 前缀。 |
> | 根对象的结束元素。 |
<Grid> </Grid> |
这是空网格对象的起始和结束标记。 |
</Window> | 关闭对象元素。 |
XAML 的语法规则几乎与 XML 相同。如果您查看 XAML 文档,您会注意到它实际上是一个有效的 XML 文件,但 XML 文件不一定是 XAML 文件。这是因为在 XML 中,属性的值必须是字符串,而在 XAML 中,它可以是不同的对象,称为属性元素语法。
对象元素的语法以左尖括号 (<) 开头,后跟对象名称,例如 Button。
定义该对象元素的一些属性。
对象元素必须用正斜杠 (/) 关闭,然后立即跟右尖括号 (>)。
没有子元素的简单对象的示例
<Button/>
具有某些属性的对象元素的示例
<Button Content = "Click Me" Height = "30" Width = "60" />
定义属性的替代语法的示例(属性元素语法)
<Button> <Button.Content>Click Me</Button.Content> <Button.Height>30</Button.Height> <Button.Width>60</Button.Width> </Button>
具有子元素的对象示例:StackPanel 包含 Textblock 作为子元素。
<StackPanel Orientation = "Horizontal"> <TextBlock Text = "Hello"/> </StackPanel>
为什么 WPF 中使用 XAML
XAML 不仅是 WPF 最广为人知的特性,也是最容易被误解的特性之一。如果您接触过 WPF,那么您一定听说过 XAML;但请注意以下关于 XAML 的两个鲜为人知的事实:
- WPF 不需要 XAML
- XAML 不需要 WPF
它们实际上是可分离的技术。为了理解这一点,让我们来看一个简单的例子,在这个例子中,一个按钮是用 XAML 创建的,并具有一些属性。
<Window x:Class = "WPFXAMLOverview.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <StackPanel> <Button x:Name = "button" Content = "Click Me" HorizontalAlignment = "Left" Margin = "150" VerticalAlignment = "Top" Width = "75" /> </StackPanel> </Window>
如果您选择不在 WPF 中使用 XAML,那么您也可以使用过程语言来实现相同的 GUI 结果。让我们来看同一个例子,但这次,我们将在 C# 中创建一个按钮。
using System.Windows; using System.Windows.Controls; namespace WPFXAMLOverview { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // Create the StackPanel StackPanel stackPanel = new StackPanel(); this.Content = stackPanel; // Create the Button Button button = new Button(); button.Content = "Click Me"; button.HorizontalAlignment = HorizontalAlignment.Left; button.Margin = new Thickness(150); button.VerticalAlignment = VerticalAlignment.Top; button.Width = 75; stackPanel.Children.Add(button); } } }
编译并执行 XAML 代码或 C# 代码时,您将看到如下所示的相同输出。
从上面的例子可以看出,您可以在 XAML 中创建、初始化和设置对象的属性,同样的任务也可以使用代码完成。
XAML 只是一种简单易用的设计 UI 元素的方法。
使用 XAML,并不意味着设计 UI 元素的方法是唯一的。您可以选择在 XAML 中声明对象,也可以使用代码定义它们。
XAML 是可选的,但尽管如此,它仍然是 WPF 设计的核心。
XAML 的目标是使视觉设计师能够直接创建用户界面元素。
WPF 旨在使从标记中控制用户界面的所有视觉方面成为可能。
WPF - 元素树
许多技术的元素和组件都是按照树状结构排序的,以便程序员能够轻松地处理对象并更改应用程序的行为。Windows Presentation Foundation (WPF) 以对象的形式拥有一个全面的树状结构。在 WPF 中,完整对象树的概念化主要有两种方式:
- 逻辑树结构
- 视觉树结构
借助这些树状结构,您可以轻松创建并识别 UI 元素之间的关系。大多数情况下,WPF 开发人员和设计人员要么使用过程语言创建应用程序,要么在 XAML 中设计应用程序的 UI 部分,同时牢记对象树结构。
逻辑树结构
在 WPF 应用程序中,XAML 中 UI 元素的结构表示逻辑树结构。在 XAML 中,UI 的基本元素由开发人员声明。WPF 中的逻辑树定义以下内容:
- 依赖属性
- 静态和动态资源
- 根据名称绑定元素等。
让我们来看一个创建按钮和列表框的示例。
<Window x:Class = "WPFElementsTree.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <StackPanel> <Button x:Name = "button" Height = "30" Width = "70" Content = "OK" Margin = "20" /> <ListBox x:Name = "listBox" Height = "100" Width = "100" Margin = "20"> <ListBoxItem Content = "Item 1" /> <ListBoxItem Content = "Item 2" /> <ListBoxItem Content = "Item 3" /> </ListBox> </StackPanel> </Window>
如果您查看 XAML 代码,您会观察到一个树状结构,即根节点是 Window,根节点内只有一个子节点,即 StackPanel。但 StackPanel 包含两个子元素:按钮和列表框。列表框还有三个列表框项子元素。
视觉树结构
在 WPF 中,视觉树的概念描述了视觉对象的结构,如 Visual 基类所示。它表示所有呈现到输出屏幕的 UI 元素。
当程序员想要为特定控件创建模板时,实际上是在呈现该控件的视觉树。对于那些想要出于性能和优化原因而绘制低级别控件的人来说,视觉树也非常有用。
在 WPF 应用程序中,视觉树用于:
- 渲染视觉对象。
- 渲染布局。
- 路由事件通常沿着视觉树传播,而不是逻辑树。
要查看包含按钮和列表框的上述简单应用程序的视觉树,让我们编译并执行 XAML 代码,您将看到以下窗口。
应用程序运行时,您可以在“实时视觉树”窗口中查看正在运行的应用程序的视觉树,该窗口显示此应用程序的完整层次结构,如下所示。
视觉树通常是逻辑树的超集。您可以在这里看到逻辑元素也存在于视觉树中。因此,这两棵树实际上只是构成 UI 的同一组对象的两种不同视图。
逻辑树省略了许多细节,使您可以专注于用户界面的核心结构,并忽略其呈现方式的确切细节。
逻辑树是您用于创建用户界面基本结构的工具。
如果您专注于演示,则视觉树将很有用。例如,如果您想自定义任何 UI 元素的外观,则需要使用视觉树。
WPF - 依赖属性
在 WPF 应用程序中,依赖属性是一种扩展 CLR 属性的特定类型的属性。它利用了 WPF 属性系统中提供的特定功能。
定义依赖属性的类必须继承自**DependencyObject**类。XAML 中使用的许多 UI 控件类都派生自**DependencyObject**类,并且它们支持依赖属性,例如 Button 类支持**IsMouseOver**依赖属性。
以下 XAML 代码创建了一个具有某些属性的按钮。
<Window x:Class = "WPFDependencyProperty.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFDependencyProperty" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Button Height = "40" Width = "175" Margin = "10" Content = "Dependency Property"> <Button.Style> <Style TargetType = "{x:Type Button}"> <Style.Triggers> <Trigger Property = "IsMouseOver" Value = "True"> <Setter Property = "Foreground" Value = "Red" /> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button> </Grid> </Window>
XAML 中的 x:Type 标记扩展具有与 C# 中的 typeof() 相似的功能。当指定采用对象类型的属性时使用它,例如 <Style TargetType = "{x:Type Button}">
编译并执行上述代码后,您将获得以下**MainWindow**。当鼠标悬停在按钮上时,它会更改按钮的前景色。当鼠标离开按钮时,它会恢复到原来的颜色。
为什么我们需要依赖属性
在应用程序中使用依赖属性可以为您带来各种好处。在以下情况下,可以使用依赖属性代替 CLR 属性:
- 如果要设置样式
- 如果要进行数据绑定
- 如果要使用资源(静态或动态资源)设置
- 如果要支持动画
基本上,依赖属性提供了许多使用 CLR 属性无法获得的功能。
**依赖属性**与其他**CLR 属性**的主要区别如下:
CLR 属性可以使用**getter**和**setter**直接读取/写入类的私有成员。相反,依赖属性不存储在局部对象中。
依赖属性存储在 DependencyObject 类提供的键/值对字典中。它还节省了大量内存,因为它只在属性更改时才存储它。它也可以在 XAML 中绑定。
自定义依赖属性
在 .NET 框架中,也可以定义自定义依赖属性。请按照以下步骤在 C# 中定义自定义依赖属性。
使用系统调用 register 声明并注册您的**依赖属性**。
为属性提供**setter**和**getter**。
定义一个**静态处理程序**,它将处理全局发生的任何更改。
定义一个**实例处理程序**,它将处理对该特定实例发生的任何更改。
以下 C# 代码定义了一个依赖属性来设置用户控件的**SetText**属性。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; 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 WpfApplication3 { /// <summary> /// Interaction logic for UserControl1.xaml /// </summary> public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public static readonly DependencyProperty SetTextProperty = DependencyProperty.Register("SetText", typeof(string), typeof(UserControl1), new PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged))); public string SetText { get { return (string)GetValue(SetTextProperty); } set { SetValue(SetTextProperty, value); } } private static void OnSetTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UserControl1 UserControl1Control = d as UserControl1; UserControl1Control.OnSetTextChanged(e); } private void OnSetTextChanged(DependencyPropertyChangedEventArgs e) { tbTest.Text = e.NewValue.ToString(); } } }
这是 XAML 文件,其中 TextBlock 定义为用户控件,并且 SetText 依赖属性将为其分配 Text 属性。
以下 XAML 代码创建一个用户控件并初始化其**SetText**依赖属性。
<Window x:Class = "WpfApplication3.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views = "clr-namespace:WpfApplication3" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <views:UserControl1 SetText = "Hellow World"/> </Grid> </Window>
让我们运行此应用程序。您可以立即观察到,在我们的 MainWindow 中,用户控件的依赖属性已成功用作 Text。
WPF - 路由事件
**路由事件**是一种事件类型,它可以在元素树中的多个侦听器上调用处理程序,而不仅仅是引发事件的对象。它基本上是由 Routed Event 类实例支持的 CLR 事件。它已在 WPF 事件系统中注册。RoutedEvents 有三种主要的路由策略,如下所示:
- 直接事件
- 冒泡事件
- 隧道事件
直接事件
直接事件类似于 Windows 窗体中的事件,这些事件由事件起源的元素引发。
与标准 CLR 事件不同,直接路由事件支持类处理,它们可以在自定义控件样式内的事件设置器和事件触发器中使用。
直接事件的一个很好的例子是 MouseEnter 事件。
冒泡事件
冒泡事件从事件起源的元素开始。然后,它沿着视觉树向上传播到视觉树中最顶层的元素。因此,在 WPF 中,最顶层的元素很可能是一个窗口。
隧道事件
调用元素树根上的事件处理程序,然后事件沿着视觉树向下传播到所有子节点,直到到达事件起源的元素。
冒泡事件和隧道事件的区别在于,隧道事件总是以预览开始。
在 WPF 应用程序中,事件通常实现为隧道/冒泡对。因此,您将拥有一个 preview MouseDown 事件,然后是一个 MouseDown 事件。
下面是一个路由事件的简单示例,其中创建了一个按钮和三个文本块,以及一些属性和事件。
<Window x:Class = "WPFRoutedEvents.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "450" Width = "604" ButtonBase.Click = "Window_Click" > <Grid> <StackPanel Margin = "20" ButtonBase.Click = "StackPanel_Click"> <StackPanel Margin = "10"> <TextBlock Name = "txt1" FontSize = "18" Margin = "5" Text = "This is a TextBlock 1" /> <TextBlock Name = "txt2" FontSize = "18" Margin = "5" Text = "This is a TextBlock 2" /> <TextBlock Name = "txt3" FontSize = "18" Margin = "5" Text = "This is a TextBlock 3" /> </StackPanel> <Button Margin = "10" Content = "Click me" Click = "Button_Click" Width = "80"/> </StackPanel> </Grid> </Window>
这是用于 Button、StackPanel 和 Window 的 Click 事件实现的 C# 代码。
using System.Windows; namespace WPFRoutedEvents { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { txt1.Text = "Button is Clicked"; } private void StackPanel_Click(object sender, RoutedEventArgs e) { txt2.Text = "Click event is bubbled to Stack Panel"; } private void Window_Click(object sender, RoutedEventArgs e) { txt3.Text = "Click event is bubbled to Window"; } } }
编译并执行上述代码后,它将生成以下窗口:
单击按钮时,文本块将更新,如下所示。
如果要在任何特定级别停止路由事件,则需要设置 e.Handled = true;
让我们更改**StackPanel_Click**事件,如下所示:
private void StackPanel_Click(object sender, RoutedEventArgs e) { txt2.Text = "Click event is bubbled to Stack Panel"; e.Handled = true; }
单击按钮时,您会发现单击事件不会路由到窗口,而会在 stackpanel 处停止,并且第 3 个文本块不会更新。
自定义路由事件
在 .NET 框架中,也可以定义自定义路由事件。您需要按照以下步骤在 C# 中定义自定义路由事件。
使用系统调用 RegisterRoutedEvent 声明并注册您的路由事件。
指定路由策略,即冒泡、隧道或直接。
提供事件处理程序。
让我们来看一个例子,以更好地理解自定义路由事件。请按照以下步骤操作:
使用 WPFCustomRoutedEvent 创建一个新的 WPF 项目。
右键单击您的解决方案,然后选择“添加”>“新建项…”。
将打开以下对话框,现在选择**自定义控件 (WPF)** 并将其命名为**MyCustomControl**。
单击“添加”按钮,您将看到在您的解决方案中添加了两个新文件 (Themes/Generic.xaml 和 MyCustomControl.cs)。
以下 XAML 代码在 Generic.xaml 文件中设置自定义控件的样式。
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFCustomRoutedEvent"> <Style TargetType = "{x:Type local:MyCustomControl}"> <Setter Property = "Margin" Value = "50"/> <Setter Property = "Template"> <Setter.Value> <ControlTemplate TargetType = "{x:Type local:MyCustomControl}"> <Border Background = "{TemplateBinding Background}" BorderBrush = "{TemplateBinding BorderBrush}" BorderThickness = "{TemplateBinding BorderThickness}"> <Button x:Name = "PART_Button" Content = "Click Me" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
以下是**MyCustomControl 类**的 C# 代码,它继承自**Control 类**,其中为自定义控件创建了一个自定义路由事件 Click。
using System.Windows; using System.Windows.Controls; namespace WPFCustomRoutedEvent { public class MyCustomControl : Control { static MyCustomControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); //demo purpose only, check for previous instances and remove the handler first var button = GetTemplateChild("PART_Button") as Button; if (button ! = null) button.Click + = Button_Click; } void Button_Click(object sender, RoutedEventArgs e) { RaiseClickEvent(); } public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyCustomControl)); public event RoutedEventHandler Click { add { AddHandler(ClickEvent, value); } remove { RemoveHandler(ClickEvent, value); } } protected virtual void RaiseClickEvent() { RoutedEventArgs args = new RoutedEventArgs(MyCustomControl.ClickEvent); RaiseEvent(args); } } }
这是 C# 中的自定义路由事件实现,当用户单击它时将显示一个消息框。
using System.Windows; namespace WPFCustomRoutedEvent { // <summary> // Interaction logic for MainWindow.xaml // </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void MyCustomControl_Click(object sender, RoutedEventArgs e) { MessageBox.Show("It is the custom routed event of your custom control"); } } }
这是 MainWindow.xaml 中的实现,用于添加带有路由事件 Click 的自定义控件。
<Window x:Class = "WPFCustomRoutedEvent.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFCustomRoutedEvent" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <local:MyCustomControl Click = "MyCustomControl_Click" /> </Grid> </Window>
编译并执行上述代码后,它将生成以下包含自定义控件的窗口。
单击自定义控件时,将显示以下消息。
WPF - 控件
Windows Presentation Foundation (WPF) 允许开发人员轻松构建和创建视觉效果丰富的基于 UI 的应用程序。
其他 UI 框架中的经典 UI 元素或控件在 WPF 应用程序中也得到了增强。
所有标准 WPF 控件都可以在工具箱中找到,工具箱是 System.Windows.Controls 的一部分。
这些控件也可以在 XAML 标记语言中创建。
WPF 控件的完整继承层次结构如下:
下表包含将在后续章节中讨论的控件列表。
序号 | 控件和说明 |
---|---|
1 | 按钮 (Button)
响应用户输入的控件 |
2 | 日历 (Calendar)
表示一个控件,允许用户使用可视日历显示来选择日期。 |
3 | 复选框 (CheckBox)
用户可以选择或清除的控件。 |
4 | 组合框 (ComboBox)
用户可以选择的下拉列表。 |
5 | 上下文菜单 (ContextMenu)
获取或设置上下文菜单元素,该元素应在通过用户界面 (UI) 从此元素内请求上下文菜单时显示。 |
6 | 数据网格 (DataGrid)
表示以可自定义网格显示数据的控件。 |
7 | 日期选择器 (DatePicker)
允许用户选择日期的控件。 |
8 | 对话框 (Dialogs)
应用程序还可以显示其他窗口,以帮助用户收集或显示重要信息。 |
9 | 网格视图 (GridView)
一个控件,以可水平滚动的行和列的形式呈现项目集合。 |
10 | 图像 (Image)
呈现图像的控件。 |
11 | 标签 (Label)
在窗体上显示文本。提供对访问键的支持。 |
12 | 列表框 (ListBox)
呈现用户可以选择的行内项目列表的控件。 |
13 | 菜单 (Menus)
表示 Windows 菜单控件,使您可以分层组织与命令和事件处理程序关联的元素。 |
14 | 密码框 (PasswordBox)
用于输入密码的控件。 |
15 | 弹出窗口 (Popup)
在应用程序窗口范围内,在现有内容之上显示内容。 |
16 | 进度条 (ProgressBar)
通过显示进度条来指示进度的控件。 |
17 | 单选按钮 (RadioButton)
允许用户从一组选项中选择单个选项的控件。 |
18 | 滚动查看器 (ScrollViewer)
允许用户平移和缩放其内容的容器控件。 |
19 | 滑块 (Slider)
允许用户通过沿轨道移动 Thumb 控件来从一系列值中进行选择的控件。 |
20 | 文本块 (TextBlock)
显示文本的控件。 |
21 | 切换按钮 (ToggleButton)
可以在两种状态之间切换的按钮。 |
22 | 工具提示 (ToolTip)
显示元素信息的弹出窗口。 |
23 | 窗口 (Window)
提供最小化/最大化选项、标题栏、边框和关闭按钮的根窗口 |
24 | 第三方控件 (3rd Party Controls)
在 WPF 应用程序中使用第三方控件。 |
我们将逐一讨论所有这些控件及其实现。
WPF - 布局
控件的布局对于应用程序可用性非常重要且至关重要。它用于在应用程序中排列一组 GUI 元素。选择布局面板时,需要考虑以下一些重要事项:
- 子元素的位置
- 子元素的大小
- 重叠子元素相互叠加的分层
当应用程序要在不同的屏幕分辨率上使用时,固定的像素控件排列不起作用。XAML 提供了一套丰富的内置布局面板来以适当的方式排列 GUI 元素。一些最常用和流行的布局面板如下:
序号 | 面板和说明 |
---|---|
1 | 堆栈面板 (Stack Panel)
堆栈面板是 XAML 中一个简单而有用的布局面板。在堆栈面板中,子元素可以基于 orientation 属性沿单行排列,水平或垂直排列。 |
2 | 换行面板 (Wrap Panel)
在 WrapPanel 中,子元素按顺序排列,从左到右或从上到下,基于 orientation 属性。 |
3 | 停靠面板 (Dock Panel)
DockPanel 定义一个区域,用于相对于彼此排列子元素,水平或垂直排列。使用 DockPanel,您可以使用 **Dock** 属性轻松地将子元素停靠到顶部、底部、右侧、左侧和中心。 |
4 | 画布面板 (Canvas Panel)
画布面板是基本布局面板,其中子元素可以使用相对于 **Canvas** 任一侧(例如左、右、上和下)的坐标显式定位。 |
5 | 网格面板 (Grid Panel)
网格面板提供一个灵活的区域,该区域由行和列组成。在网格中,子元素可以以表格形式排列。 |
WPF - 布局嵌套
布局嵌套是指在一个布局内使用另一个布局面板,例如在网格内定义堆栈面板。此概念广泛用于利用应用程序中的多个布局的优势。在下面的示例中,我们将使用网格内的堆栈面板。
让我们来看一下下面的 XAML 代码。
<Window x:Class = "WPFNestingLayouts.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFNestingLayouts" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid Background = "AntiqueWhite"> <Grid.RowDefinitions> <RowDefinition Height = "*" /> <RowDefinition Height = "*" /> <RowDefinition Height = "*" /> <RowDefinition Height = "*" /> <RowDefinition Height = "*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width = "*" /> </Grid.ColumnDefinitions> <Label Content = "Employee Info" FontSize = "15" FontWeight = "Bold" Grid.Column = "0" Grid.Row = "0"/> <StackPanel Grid.Column = "0" Grid.Row = "1" Orientation = "Horizontal"> <Label Content = "Name" VerticalAlignment = "Center" Width = "70"/> <TextBox Name = "txtName" Text = "Muhammad Ali" VerticalAlignment = "Center" Width = "200"> </TextBox> </StackPanel> <StackPanel Grid.Column = "0" Grid.Row = "2" Orientation = "Horizontal"> <Label Content = "ID" VerticalAlignment = "Center" Width = "70"/> <TextBox Name = "txtCity" Text = "421" VerticalAlignment = "Center" Width = "50"> </TextBox> </StackPanel> <StackPanel Grid.Column = "0" Grid.Row = "3" Orientation = "Horizontal"> <Label Content = "Age" VerticalAlignment = "Center" Width = "70"/> <TextBox Name = "txtState" Text = "32" VerticalAlignment = "Center" Width = "50"></TextBox> </StackPanel> <StackPanel Grid.Column = "0" Grid.Row = "4" Orientation = "Horizontal"> <Label Content = "Title" VerticalAlignment = "Center" Width = "70"/> <TextBox Name = "txtCountry" Text = "Programmer" VerticalAlignment = "Center" Width = "200"></TextBox> </StackPanel> </Grid> </Window>
编译并执行上述代码后,将生成以下窗口。
我们建议您执行上述示例代码并尝试其他嵌套布局。
WPF - 输入
Windows Presentation Foundation (WPF) 提供了一个强大的 API,应用程序可以使用该 API 从各种设备(如鼠标、键盘和触摸屏)获取输入。在本节中,我们将讨论可在 WPF 应用程序中处理的以下类型的输入:
序号 | 输入和说明 |
---|---|
1 | 鼠标 (Mouse)
有不同类型的鼠标输入,例如 MouseDown、MouseEnter、MouseLeave 等。 |
2 | 键盘 (Keyboard)
有许多类型的键盘输入,例如 KeyDown、KeyUp、TextInput 等。 |
3 | 上下文菜单或路由命令 (ContextMenu or RoutedCommands)
RoutedCommands 允许在更语义的级别上处理输入。这些实际上是简单的指令,例如新建、打开、复制、剪切和保存。 |
4 | 多点触控 (Multi Touch)
Windows 7 及更高版本能够接收来自多个触摸敏感设备的输入。WPF 应用程序还可以像处理其他输入(如鼠标或键盘)一样处理触摸输入,方法是在发生触摸时引发事件。 |
WPF - 命令行
命令行参数是一种机制,用户可以在执行 WPF 应用程序时向其传递一组参数或值。这些参数对于从外部控制应用程序非常重要,例如,如果要从命令提示符打开 Word 文档,则可以使用此命令“**_C:\> start winword word1.docx_**”,它将打开 **word1.docx** 文档。
命令行参数在 Startup 函数中处理。以下是一个简单的示例,它演示如何将命令行参数传递给 WPF 应用程序。让我们创建一个名为 **WPFCommandLine** 的新 WPF 应用程序。
从工具箱将一个文本框拖动到设计窗口。
在此示例中,我们将把 txt 文件路径作为命令行参数传递给我们的应用程序。
程序将读取 txt 文件,然后将所有文本写入文本框。
以下 XAML 代码创建一个文本框并使用一些属性对其进行初始化。
<Window x:Class = "WPFCommandLine.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFCommandLine" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Grid> <TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "180" Margin = "100" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "300"/> </Grid> </Window>
- 现在像下面这样订阅 App.xaml 文件中的 Startup 事件。
<Application x:Class = "WPFCommandLine.App" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFCommandLine" StartupUri = "MainWindow.xaml" Startup = "app_Startup"> <Application.Resources> </Application.Resources> </Application>
下面给出了 App.xaml.cs 中 app_Startup 事件的实现,它将获取命令行参数。
using System.Windows; namespace WPFCommandLine { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { public static string[] Args; void app_Startup(object sender, StartupEventArgs e) { // If no command line arguments were provided, don't process them if (e.Args.Length == 0) return; if (e.Args.Length > 0) { Args = e.Args; } } } }
现在,在 MainWindow 类中,程序将打开 txt 文件并将所有文本写入文本框。
如果发现某些错误,则程序将在文本框中显示错误消息。
using System; using System.IO; using System.Windows; namespace WPFCommandLine { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); String[] args = App.Args; try { // Open the text file using a stream reader. using (StreamReader sr = new StreamReader(args[0])) { // Read the stream to a string, and write // the string to the text box String line = sr.ReadToEnd(); textBox.AppendText(line.ToString()); textBox.AppendText("\n"); } } catch (Exception e) { textBox.AppendText("The file could not be read:"); textBox.AppendText("\n"); textBox.AppendText(e.Message); } } } }
编译并执行上述代码时,将生成一个带有文本框的空白窗口,因为此程序需要命令行参数。因此,Visual Studio 提供了一种简单的方法来使用命令行参数执行应用程序。
右键单击解决方案资源管理器中的 WPF 项目,然后选择属性,将显示以下窗口。
选择调试选项,并在命令行参数中写入文件路径。
创建一个名为 Test.txt 的 txt 文件,并在该文件中写入一些文本并将其保存到任何位置。在这种情况下,txt 文件保存在“**D:\**”硬盘驱动器上。
保存项目中的更改,然后编译并执行应用程序。您将在文本框中看到程序从 Text.txt 文件读取的文本。
现在让我们尝试将计算机上的文件名从 **Test.txt** 更改为 **Test1.txt** 并再次执行程序,然后您将在文本框中看到错误消息。
我们建议您执行上述代码并按照所有步骤成功执行应用程序。
WPF - 数据绑定
数据绑定是 WPF 应用程序中的一种机制,它为 Windows 运行时应用程序提供了一种简单易行的方式来显示和交互数据。在此机制中,数据的管理完全与数据的方式分开。
数据绑定允许数据在 UI 元素和用户界面上的数据对象之间流动。当建立绑定并且数据或业务模型发生更改时,它会自动将更新反映到 UI 元素,反之亦然。也可以绑定到页面上的另一个元素,而不是标准数据源。
数据绑定分为两种类型:**单向数据绑定**和**双向数据绑定**。
单向数据绑定 (One-Way Data Binding)
在单向绑定中,数据从其源(即保存数据的对象)绑定到其目标(即显示数据的对象)。
让我们来看一个简单的示例,以详细了解单向数据绑定。首先,创建一个名为 **WPFDataBinding** 的新 WPF 项目。
以下 XAML 代码创建两个标签、两个文本框和一个按钮,并使用一些属性对其进行初始化。
<Window x:Class = "WPFDataBinding.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFDataBinding" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Grid.RowDefinitions> <RowDefinition Height = "Auto" /> <RowDefinition Height = "Auto" /> <RowDefinition Height = "*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width = "Auto" /> <ColumnDefinition Width = "200" /> </Grid.ColumnDefinitions> <Label Name = "nameLabel" Margin = "2">_Name:</Label> <TextBox Name = "nameText" Grid.Column = "1" Margin = "2" Text = "{Binding Name, Mode = OneWay}"/> <Label Name = "ageLabel" Margin = "2" Grid.Row = "1">_Age:</Label> <TextBox Name = "ageText" Grid.Column = "1" Grid.Row = "1" Margin = "2" Text = "{Binding Age, Mode = OneWay}"/> <StackPanel Grid.Row = "2" Grid.ColumnSpan = "2"> <Button Content = "_Show..." Click="Button_Click" /> </StackPanel> </Grid> </Window>
两个文本框的 text 属性绑定到“Name”和“Age”,它们是 Person 类的类变量,如下所示。
在 Person 类中,我们只有两个变量 **Name** 和 **Age**,并且它的对象在 **MainWindow** 类中初始化。
在 XAML 代码中,我们绑定到 Name 和 Age 属性,但我们没有选择该属性属于哪个对象。
更简单的方法是将一个对象分配给 **DataContext**,我们在 **MainWindowconstructor** 中的以下 C# 代码中绑定其属性。
using System.Windows; namespace WPFDataBinding { public partial class MainWindow : Window { Person person = new Person { Name = "Salman", Age = 26 }; public MainWindow() { InitializeComponent(); this.DataContext = person; } private void Button_Click(object sender, RoutedEventArgs e) { string message = person.Name + " is " + person.Age; MessageBox.Show(message); } } public class Person { private string nameValue; public string Name { get { return nameValue; } set { nameValue = value; } } private double ageValue; public double Age { get { return ageValue; } set { if (value != ageValue) { ageValue = value; } } } } }
让我们运行此应用程序,您会立即在我们的 MainWindow 中看到我们已成功绑定到该 Person 对象的 Name 和 Age。
按下 **Show** 按钮时,它将在消息框中显示姓名和年龄。
让我们更改对话框中的姓名和年龄。
如果现在单击“显示”按钮,它将再次显示相同的消息。
这是因为数据绑定模式在 XAML 代码中设置为单向。要显示更新的数据,您需要了解双向数据绑定。
双向数据绑定 (Two-Way Data Binding)
在双向绑定中,用户可以通过用户界面修改数据,并将该数据更新到源。如果用户查看视图时源发生更改,则希望视图更新。
让我们采用相同的示例,但在这里,我们将 XAML 代码中的绑定模式从单向更改为双向。
<Window x:Class = "WPFDataBinding.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFDataBinding" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Grid.RowDefinitions> <RowDefinition Height = "Auto" /> <RowDefinition Height = "Auto" /> <RowDefinition Height = "*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width = "Auto" /> <ColumnDefinition Width = "200" /> </Grid.ColumnDefinitions> <Label Name = "nameLabel" Margin = "2">_Name:</Label> <TextBox Name = "nameText" Grid.Column = "1" Margin = "2" Text = "{Binding Name, Mode = TwoWay}"/> <Label Name = "ageLabel" Margin = "2" Grid.Row = "1">_Age:</Label> <TextBox Name = "ageText" Grid.Column = "1" Grid.Row = "1" Margin = "2" Text = "{Binding Age, Mode = TwoWay}"/> <StackPanel Grid.Row = "2" Grid.ColumnSpan = "2"> <Button Content = "_Show..." Click = "Button_Click" /> </StackPanel> </Grid> </Window>
让我们再次运行此应用程序。
它将生成相同的输出:
现在让我们更改 Name 和 Age 值:
如果现在单击“显示”按钮,它将显示更新的消息。
我们建议您在两种情况下都执行上述代码,以便更好地理解该概念。
WPF - 资源
资源通常是与某个对象关联的定义,您只希望多次使用该对象。它能够为控件或当前窗口本地存储数据,或为整个应用程序全局存储数据。
将对象定义为资源允许我们从其他地方访问它。这意味着该对象可以被重用。资源在资源字典中定义,任何对象都可以有效地定义为资源,使其成为可共享的资产。一个唯一的键指定给 XAML 资源,使用该键,可以使用 StaticResource 标记扩展来引用它。
资源可以分为两种类型:
- StaticResource
- DynamicResource
StaticResource 是一次性查找,而 DynamicResource 更像数据绑定。它记住一个属性与特定的资源键相关联。如果与该键关联的对象发生更改,动态资源将更新目标属性。
示例
这是一个用于 SolidColorBrush 资源的简单应用程序。
让我们创建一个名为 **WPFResouces** 的新 WPF 项目。
拖动两个矩形并设置它们的属性,如下面的 XAML 代码所示。
<Window x:Class = "WPFResources.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFResources" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Window.Resources> <SolidColorBrush x:Key = "brushResource" Color = "Blue" /> </Window.Resources> <StackPanel> <Rectangle Height = "50" Margin = "20" Fill = "{StaticResource brushResource}" /> <Rectangle Height = "50" Margin = "20" Fill = "{DynamicResource brushResource}" /> <Button x:Name = "changeResourceButton" Content = "_Change Resource" Click = "changeResourceButton_Click" /> </StackPanel> </Window>
在上面的 XAML 代码中,您可以看到一个矩形使用 StaticResource,另一个矩形使用 DynamicResource,brushResource 的颜色为 Bisque(米黄色)。
编译并执行代码后,它将生成以下 MainWindow。
单击“更改资源”按钮时,您将看到使用 DynamicResource 的矩形会将其颜色更改为红色。
资源范围
资源定义在 **资源字典** 中,但是可以定义资源字典的地方有很多。在上面的示例中,资源字典是在 Window/页面级别定义的。资源在哪个字典中定义会立即限制该资源的范围。因此,范围(即可以使用资源的位置)取决于您定义资源的位置。
在网格的资源字典中定义资源,则只有该网格及其子元素可以访问它。
在窗口/页面上定义它,则窗口/页面上的所有元素都可以访问它。
应用程序根目录可以在 App.xaml 资源字典中找到。它是我们应用程序的根目录,因此在此处定义的资源的范围是整个应用程序。
就资源范围而言,最常见的是应用程序级别、页面级别和特定元素级别(如 Grid、StackPanel 等)。
上述应用程序在其 Window/页面级别具有资源。
资源字典
XAML 应用程序中的资源字典意味着资源字典保存在单独的文件中。几乎所有 XAML 应用程序都遵循此做法。在单独的文件中定义资源具有以下优点:
在资源字典中定义资源与 UI 相关代码之间的分离。
在单独的文件(例如 App.xaml)中定义所有资源,使它们在整个应用程序中可用。
那么,我们如何在单独的文件中的资源字典中定义资源呢?很简单,只需按照以下步骤通过 Visual Studio 添加新的资源字典:
在您的解决方案中,添加一个新文件夹并将其命名为 **ResourceDictionaries**。
右键单击此文件夹,然后从“添加”子菜单项中选择“资源字典”,并将其命名为 **DictionaryWithBrush.xaml**
示例
现在让我们来看同一个例子,但是在这里,我们将资源字典定义在应用程序级别。MainWindow.xaml 的 XAML 代码如下:
<Window x:Class = "WPFResources.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFResources" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <StackPanel> <Rectangle Height = "50" Margin = "20" Fill = "{StaticResource brushResource}" /> <Rectangle Height = "50" Margin = "20" Fill = "{DynamicResource brushResource}" /> <Button x:Name = "changeResourceButton" Content = "_Change Resource" Click = "changeResourceButton_Click" /> </StackPanel> </Window>
以下是 DictionaryWithBrush.xaml 中的实现:
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"> <SolidColorBrush x:Key = "brushResource" Color = "Blue" /> </ResourceDictionary>
以下是 app.xaml 中的实现:
<Application x:Class="WPFResources.App" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" StartupUri = "MainWindow.xaml"> <Application.Resources> <ResourceDictionary Source = " XAMLResources\ResourceDictionaries\DictionaryWithBrush.xaml"/> </Application.Resources> </Application>
编译并执行上述代码后,将产生以下输出:
单击“更改资源”按钮时,矩形将将其颜色更改为红色。
我们建议您执行上述代码并尝试更多资源(例如,背景颜色)。
WPF - 模板
模板描述了控件的整体外观和视觉外观。对于每个控件,都关联有一个默认模板,该模板赋予控件其外观。在 WPF 应用程序中,当您想要自定义控件的视觉行为和视觉外观时,可以轻松创建您自己的模板。
逻辑和模板之间的连接可以通过数据绑定来实现。**样式**和**模板**之间的主要区别如下:
样式只能使用控件的默认属性来更改控件的外观。
使用模板,您可以访问比样式中更多的控件部分。您还可以指定控件的现有行为和新行为。
最常用的两种模板是:
- 控件模板
- 数据模板
控件模板
控件模板定义控件的视觉外观。所有 UI 元素都具有一定的外观和行为,例如,按钮具有外观和行为。单击事件或鼠标悬停事件是在响应单击和悬停时触发的行为,并且还有一个可以由控件模板更改的按钮默认外观。
示例
让我们来看一个简单的例子。我们将创建两个按钮(一个带有模板,另一个是默认按钮),并用一些属性初始化它们。
<Window x:Class = "TemplateDemo.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Window.Resources> <ControlTemplate x:Key = "ButtonTemplate" TargetType = "Button"> <Grid> <Ellipse x:Name = "ButtonEllipse" Height = "100" Width = "150" > <Ellipse.Fill> <LinearGradientBrush StartPoint = "0,0.2" EndPoint = "0.2,1.4"> <GradientStop Offset = "0" Color = "Red" /> <GradientStop Offset = "1" Color = "Orange" /> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <ContentPresenter Content = "{TemplateBinding Content}" HorizontalAlignment = "Center" VerticalAlignment = "Center" /> </Grid> <ControlTemplate.Triggers> <Trigger Property = "IsMouseOver" Value = "True"> <Setter TargetName = "ButtonEllipse" Property = "Fill" > <Setter.Value> <LinearGradientBrush StartPoint = "0,0.2" EndPoint = "0.2,1.4"> <GradientStop Offset = "0" Color = "YellowGreen" /> <GradientStop Offset = "1" Color = "Gold" /> </LinearGradientBrush> </Setter.Value> </Setter> </Trigger> <Trigger Property = "IsPressed" Value = "True"> <Setter Property = "RenderTransform"> <Setter.Value> <ScaleTransform ScaleX = "0.8" ScaleY = "0.8" CenterX = "0" CenterY = "0" /> </Setter.Value> </Setter> <Setter Property = "RenderTransformOrigin" Value = "0.5,0.5" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources> <StackPanel> <Button Content = "Round Button!" Template = "{StaticResource ButtonTemplate}" Width = "150" Margin = "50" /> <Button Content = "Default Button!" Height = "40" Width = "150" Margin = "5" /> </StackPanel> </Window>
编译并执行上述代码后,它将显示以下 MainWindow。
当您将鼠标悬停在带有自定义模板的按钮上时,它将更改其颜色,如下所示。
数据模板
数据模板定义并指定数据集合的外观和结构。它提供了在任何 UI 元素上格式化和定义数据表示的灵活性。它主要用于与数据相关的项目控件,例如 ComboBox、ListBox 等。
示例
让我们来看一个简单的例子来理解数据模板的概念。创建一个名为 **WPFDataTemplates** 的新 WPF 项目。
在下面的 XAML 代码中,我们将创建一个作为资源的数据模板来保存标签和文本框。还有一个按钮和一个列表框来显示数据。
<Window x:Class = "WPFDataTemplates.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFDataTemplates" xmlns:loc = "clr-namespace:WPFDataTemplates" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Window.Resources> <DataTemplate DataType = "{x:Type loc:Person}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height = "Auto" /> <RowDefinition Height = "Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width = "Auto" /> <ColumnDefinition Width = "200" /> </Grid.ColumnDefinitions> <Label Name = "nameLabel" Margin = "10"/> <TextBox Name = "nameText" Grid.Column = "1" Margin = "10" Text = "{Binding Name}"/> <Label Name = "ageLabel" Margin = "10" Grid.Row = "1"/> <TextBox Name = "ageText" Grid.Column = "1" Grid.Row = "1" Margin = "10" Text = "{Binding Age}"/> </Grid> </DataTemplate> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height = "Auto" /> <RowDefinition Height = "*" /> </Grid.RowDefinitions> <ListBox ItemsSource = "{Binding}" /> <StackPanel Grid.Row = "1" > <Button Content = "_Show..." Click = "Button_Click" Width = "80" HorizontalAlignment = "Left" Margin = "10"/> </StackPanel> </Grid> </Window>
以下是 **C# 实现**,其中将 Person 对象列表分配给 DataContext,Person 类的实现和按钮单击事件。
using System.Collections.Generic; using System.Windows; namespace WPFDataTemplates { public partial class MainWindow : Window { Person src = new Person { Name = "Ali", Age = 27 }; List<Person> people = new List<Person>(); public MainWindow() { InitializeComponent(); people.Add(src); people.Add(new Person { Name = "Mike", Age = 62 }); people.Add(new Person { Name = "Brian", Age = 12 }); this.DataContext = people; } private void Button_Click(object sender, RoutedEventArgs e) { string message = src.Name + " is " + src.Age; MessageBox.Show(message); } } public class Person { private string nameValue; public string Name { get { return nameValue; } set { nameValue = value; } } private double ageValue; public double Age { get { return ageValue; } set { if (value != ageValue) { ageValue = value; } } } } }
编译并执行上述代码后,它将生成以下窗口。它包含一个列表,在列表框内,每个列表框项目都包含显示在标签和文本框上的 Person 类对象数据。
WPF - 样式
.NET 框架提供了几种个性化和自定义应用程序外观的策略。样式使我们能够灵活地设置对象的某些属性,并在多个对象中重用这些特定设置,以获得一致的外观。
在样式中,您只能设置对象的现有属性,例如高度、宽度、字体大小等。
只能指定控件的默认行为。
多个属性可以添加到单个样式中。
样式用于为一组控件提供统一的外观。隐式样式用于将外观应用于给定类型的全部控件,并简化应用程序。想象一下三个按钮,它们都必须外观相同,宽度和高度相同,字体大小相同,前景色相同等。我们可以在按钮元素本身上设置所有这些属性,对于所有按钮来说,这仍然还可以。请看下图。
但在实际应用程序中,您通常会有更多这样的需要看起来完全相同的按钮。当然,不仅是按钮,您通常还希望文本块、文本框和组合框等在整个应用程序中看起来相同。当然,一定有更好的方法来实现这一点,这就是所谓的 **样式**。您可以将样式视为一种方便的方法,可以将一组属性值应用于多个元素。请看下图。
示例
让我们来看一个简单的例子来理解这个概念。首先创建一个新的 WPF 项目。
从工具箱中将三个按钮拖动到设计窗口。
以下 XAML 代码创建三个按钮并用一些属性初始化它们。
<Window x:Class = "WPFStyle.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace: WPFStyle" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <StackPanel> <Button Content = "Button1" Height = "30" Width = "80" Foreground = "Blue" FontSize = "12" Margin = "10"/> <Button Content = "Button2" Height = "30" Width = "80" Foreground = "Blue" FontSize = "12" Margin = "10"/> <Button Content = "Button3" Height = "30" Width = "80" Foreground = "Blue" FontSize = "12" Margin = "10"/> </StackPanel> </Window>
查看上面的代码,您会看到所有按钮的高度、宽度、前景色、字体大小和边距属性都是相同的。现在,当编译并执行上述代码时,将显示以下窗口。
现在让我们来看同一个例子,但是这次我们将使用 **样式**。
<Window x:Class = "XAMLStyle.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:XAMLStyle" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Window.Resources> <Style x:Key = "myButtonStyle" TargetType = "Button"> <Setter Property = "Height" Value = "30" /> <Setter Property = "Width" Value = "80" /> <Setter Property = "Foreground" Value = "Blue" /> <Setter Property = "FontSize" Value = "12" /> <Setter Property = "Margin" Value = "10" /> </Style> </Window.Resources> <StackPanel> <Button Content = "Button1" Style = "{StaticResource myButtonStyle}" /> <Button Content = "Button2" Style = "{StaticResource myButtonStyle}" /> <Button Content = "Button3" Style="{StaticResource myButtonStyle}" /> </StackPanel> </Window>
样式定义在资源字典中,每个样式都有一个唯一的键标识符和一个目标类型。在 <style> 内,您可以看到为样式中包含的每个属性定义了多个 setter 标记。
在上面的示例中,每个按钮的所有公共属性现在都在样式中定义,然后通过使用 StaticResource 标记扩展设置样式属性,将样式分配给每个按钮。
编译并执行上述代码后,它将显示以下窗口(相同的输出)。
这样做的优势是显而易见的,我们可以在其范围内在任何地方重用该样式;如果我们需要更改它,我们只需在样式定义中更改一次,而不是在每个元素上更改。
样式定义的级别会立即限制该样式的范围。因此,范围(即可以使用样式的位置)取决于您定义样式的位置。样式可以在以下级别定义:
序号 | 级别和说明 |
---|---|
1 | 控件级别
在控件级别定义的样式只能应用于该特定控件。下面是一个控件级别的示例,其中按钮和文本块具有它们自己的样式。 |
2 | 布局级别
在任何布局级别定义样式,将使该布局及其子元素可以访问它。 |
3 | 窗口级别
在窗口级别定义样式,将使窗口上的所有元素都可以访问它。 |
4 | 应用程序级别
在应用程序级别定义样式,将使整个应用程序都可以访问它。让我们来看同一个例子,但是在这里,我们将样式放在 app.xaml 文件中,以使其在整个应用程序中可用。 |
WPF - 触发器
触发器基本上使您能够根据属性的值更改属性值或执行操作。因此,它允许您动态更改控件的外观和/或行为,而无需创建新的控件。
触发器用于在满足某些条件时更改任何给定属性的值。触发器通常定义在样式中或文档的根目录中,这些样式应用于特定控件。触发器有三种类型:
- 属性触发器
- 数据触发器
- 事件触发器
属性触发器
在属性触发器中,当一个属性发生更改时,它将导致另一个属性立即或动画更改。例如,您可以使用属性触发器在鼠标悬停在按钮上时更改按钮的外观。
以下示例代码演示如何在鼠标悬停在按钮上时更改按钮的前景色。
<Window x:Class = "WPFPropertyTriggers.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Window.Resources> <Style x:Key = "TriggerStyle" TargetType = "Button"> <Setter Property = "Foreground" Value = "Blue" /> <Style.Triggers> <Trigger Property = "IsMouseOver" Value = "True"> <Setter Property = "Foreground" Value = "Green" /> </Trigger> </Style.Triggers> </Style> </Window.Resources> <Grid> <Button Width = "100" Height = "70" Style = "{StaticResource TriggerStyle}" Content = "Trigger"/> </Grid> </Window>
编译并执行上述代码后,它将生成以下窗口:
当鼠标悬停在按钮上时,其前景色将变为绿色。
数据触发器
当绑定数据满足某些条件时,数据触发器将执行某些操作。让我们来看一下下面的 XAML 代码,其中创建了一个复选框和一个文本块,以及一些属性。选中复选框时,它将将其前景色更改为红色。
<Window x:Class = "WPFDataTrigger.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "Data Trigger" Height = "350" Width = "604"> <StackPanel HorizontalAlignment = "Center"> <CheckBox x:Name = "redColorCheckBox" Content = "Set red as foreground color" Margin = "20"/> <TextBlock Name = "txtblock" VerticalAlignment = "Center" Text = "Event Trigger" FontSize = "24" Margin = "20"> <TextBlock.Style> <Style> <Style.Triggers> <DataTrigger Binding = "{Binding ElementName = redColorCheckBox, Path = IsChecked}" Value = "true"> <Setter Property = "TextBlock.Foreground" Value = "Red"/> <Setter Property = "TextBlock.Cursor" Value = "Hand" /> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </StackPanel> </Window>
编译并执行上述代码后,将产生以下输出:
选中复选框时,文本块将将其前景色更改为红色。
事件触发器
事件触发器在触发特定事件时执行某些操作。它通常用于完成对控件的一些动画,例如 DoubleAnumatio、ColorAnimation 等。在下面的示例中,我们将创建一个简单的按钮。当单击事件触发时,它将扩展按钮的宽度和高度。
<Window x:Class = "WPFEventTrigger.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Button Content = "Click Me" Width = "60" Height = "30"> <Button.Triggers> <EventTrigger RoutedEvent = "Button.Click"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "Width" Duration = "0:0:4"> <LinearDoubleKeyFrame Value = "60" KeyTime = "0:0:0"/> <LinearDoubleKeyFrame Value = "120" KeyTime = "0:0:1"/> <LinearDoubleKeyFrame Value = "200" KeyTime = "0:0:2"/> <LinearDoubleKeyFrame Value = "300" KeyTime = "0:0:3"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "Height" Duration = "0:0:4"> <LinearDoubleKeyFrame Value = "30" KeyTime = "0:0:0"/> <LinearDoubleKeyFrame Value = "40" KeyTime = "0:0:1"/> <LinearDoubleKeyFrame Value = "80" KeyTime = "0:0:2"/> <LinearDoubleKeyFrame Value = "150" KeyTime = "0:0:3"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Button.Triggers> </Button> </Grid> </Window>
编译并执行上述代码后,它将生成以下窗口:
单击按钮后,您将观察到它将开始在两个维度上扩展。
我们建议您编译并执行上述示例,并将触发器应用于其他属性。
WPF - 调试
这是一个系统化的机制,用于识别和修复代码中不符合预期行为的错误或缺陷。调试子系统紧密耦合的复杂应用程序并不容易,因为修复一个子系统中的错误可能会在另一个子系统中创建错误。
C#中的调试
在WPF应用程序中,程序员处理两种语言,例如C#和XAML。如果您熟悉在任何过程语言(例如C#或C/C++)中的调试,并且您还了解断点的用法,那么您可以轻松地调试应用程序的C#部分。
让我们以一个简单的例子来演示如何调试C#代码。创建一个名为**WPFDebuggingDemo**的新WPF项目。从工具箱中拖动四个标签、三个文本框和一个按钮。查看以下XAML代码。
<Window x:Class = "WPFDebuggingDemo.Window1" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "Window1" Height = "400" Width = "604"> <Grid> <TextBox Height = "23" Margin = "0,44,169,0" Name = "textBox1" VerticalAlignment = "Top" HorizontalAlignment = "Right" Width = "120" /> <TextBox Height = "23" Margin = "0,99,169,0" Name = "textBox2" VerticalAlignment = "Top" HorizontalAlignment = "Right" Width = "120" /> <TextBox HorizontalAlignment = "Right" Margin = "0,153,169,0" Name = "textBox3" Width = "120" Height = "23" VerticalAlignment = "Top" /> <Label Height = "28" Margin = "117,42,0,0" Name = "label1" VerticalAlignment = "Top" HorizontalAlignment = "Left" Width = "120"> Item 1</Label> <Label Height = "28" HorizontalAlignment = "Left" Margin = "117,99,0,0" Name = "label2" VerticalAlignment = "Top" Width = "120"> Item 2</Label> <Label HorizontalAlignment = "Left" Margin = "117,153,0,181" Name = "label3" Width = "120">Item 3</Label> <Button Height = "23" HorizontalAlignment = "Right" Margin = "0,0,214,127" Name = "button1" VerticalAlignment = "Bottom" Width = "75" Click = "button1_Click">Total</Button> <Label Height = "28" HorizontalAlignment = "Right" Margin = "0,0,169,66" Name = "label4" VerticalAlignment = "Bottom" Width = "120"/> </Grid> </Window>
下面是实现了按钮单击事件的C#代码。
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 WPFDebuggingDemo { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void button1_Click(object sender, RoutedEventArgs e) { if (textBox1.Text.Length > 0 && textBox2.Text.Length > 0 && textBox2.Text.Length > 0) { double total = Convert.ToDouble(textBox1.Text) + Convert.ToDouble(textBox2.Text) + Convert.ToDouble(textBox3.Text); label4.Content = total.ToString(); } else { MessageBox.Show("Enter the value in all field."); } } } }
编译并执行上述代码后,它将生成以下窗口。现在在文本框中输入值,然后按“总计”按钮。您将在对文本框中输入的所有值求和后获得总值。
如果您尝试输入非数值的值,则上述应用程序将崩溃。要查找和解决问题(为什么它会崩溃),您可以在按钮单击事件中插入断点。
让我们在项目1中写入“abc”,如下所示。
单击“总计”按钮后,您将看到程序在断点处停止。
现在将光标移到textbox1.Text,您将看到程序试图将**abc**值与其他值相加,这就是程序崩溃的原因。
XAML中的调试
如果您期望在XAML中进行相同类型的调试,那么您会惊讶地发现,目前还无法像调试其他过程语言代码那样调试XAML代码。当您听到在XAML代码中进行调试这个术语时,这意味着尝试查找错误。
在数据绑定中,您的数据没有显示在屏幕上,您不知道原因。
或者问题与复杂的布局有关。
或者对齐问题或边距颜色、叠加层等问题,以及一些扩展模板,如ListBox和组合框。
调试XAML程序通常是为了检查您的绑定是否有效;如果无效,则检查问题所在。不幸的是,除了在Silverlight中,无法在XAML绑定中设置断点,但我们可以使用输出窗口来检查数据绑定错误。让我们看一下下面的XAML代码来查找数据绑定中的错误。
<Window x:Class = "DataBindingOneWay.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <StackPanel Name = "Display"> <StackPanel Orientation = "Horizontal" Margin = "50, 50, 0, 0"> <TextBlock Text = "Name: " Margin = "10" Width = "100"/> <TextBlock Margin = "10" Width = "100" Text = "{Binding FirstName}"/> </StackPanel> <StackPanel Orientation = "Horizontal" Margin = "50,0,50,0"> <TextBlock Text = "Title: " Margin = "10" Width = "100"/> <TextBlock Margin = "10" Width = "100" Text = "{Binding Title}" /> </StackPanel> </StackPanel> </Grid> </Window>
两个文本块的Text属性静态设置为“Name”和“Title”,而其他两个文本块的Text属性绑定到“FirstName”和“Title”,但Employee类中的类变量为Name和Title,如下所示。
我们故意写了一个不正确的变量名,以便了解当没有显示所需输出时,我们可以在哪里找到这种类型的错误。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBindingOneWay { public class Employee { public string Name { get; set; } public string Title { get; set; } public static Employee GetEmployee() { var emp = new Employee() { Name = "Ali Ahmed", Title = "Developer" }; return emp; } } }
这是C#代码中MainWindow类的实现。
using System; using System.Windows; using System.Windows.Controls; namespace DataBindingOneWay { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = Employee.GetEmployee(); } } }
让我们运行此应用程序,您可以在我们的MainWindow中立即看到我们已成功绑定到该Employee对象的Title,但名称未绑定。
要检查名称发生了什么,让我们查看输出窗口,其中生成了许多日志。
查找错误的简单方法就是搜索错误,您将找到以下错误,该错误显示为“BindingExpression path error: 'FirstName' property not found on 'object' ''Employe”
System.Windows.Data Error: 40 : BindingExpression path error: 'FirstName' property not found on 'object' ''Employee' (HashCode=11611730)'. BindingExpression:Path = FirstName; DataItem = 'Employee' (HashCode = 11611730); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
这清楚地表明FirstName不是Employee类的成员,因此它有助于修复应用程序中的此类问题。
当您再次将FirstName更改为Name时,您将看到预期的输出。
XAML的UI调试工具
UI调试工具是使用Visual Studio 2015为XAML引入的,用于在运行时检查XAML代码。借助这些工具,XAML代码以正在运行的WPF应用程序的可视化树的形式呈现,以及树中不同的UI元素属性。要启用这些工具,请按照以下步骤操作。
- 转到“工具”菜单,然后从“工具”菜单中选择“选项”。
- 它将打开以下对话框。
- 转到左侧“调试”项下的“常规选项”。
- 选中突出显示的选项,即“启用XAML的UI调试工具”,然后单击“确定”按钮。
现在运行任何XAML应用程序或使用以下XAML代码。
<Window x:Class = "XAMLTestBinding.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" Title = "MainWindow" Height = "350" Width = "604"> <StackPanel> <ComboBox Name = "comboBox" Margin = "50" Width = "100"> <ComboBoxItem Content = "Green" /> <ComboBoxItem Content = "Yellow" IsSelected = "True" /> <ComboBoxItem Content = "Orange" /> </ComboBox> <TextBox Name = "textBox" Margin = "50" Width = "100" Height = "23" VerticalAlignment = "Top" Text = "{Binding ElementName = comboBox, Path = SelectedItem.Content, Mode = TwoWay, UpdateSourceTrigger = PropertyChanged}" Background = "{Binding ElementName = comboBox, Path = SelectedItem.Content}"> </TextBox> </StackPanel> </Window>
执行应用程序时,它将显示实时可视化树,其中所有元素都以树的形式显示。
此实时可视化树显示完整的布局结构,以便了解UI元素的位置。但是此选项仅在Visual Studio 2015中可用。如果您使用的是旧版本的Visual Studio,则无法使用此工具,但是还有另一个工具可以与Visual Studio集成,例如Visual Studio的XAML Spy。您可以从xamlspy下载它。
WPF - 自定义控件
WPF应用程序允许创建自定义控件,这使得创建功能丰富且可定制的控件变得非常容易。当Microsoft提供的所有内置控件都不能满足您的标准,或者您不想为第三方控件付费时,可以使用自定义控件。
在本节中,您将学习如何创建自定义控件。在我们开始查看自定义控件之前,让我们快速浏览一下用户控件。
用户控件
用户控件提供了一种收集和组合不同的内置控件并将它们打包到可重用的XAML中的方法。用户控件用于以下场景:
如果控件包含现有控件,即您可以创建多个现有控件的单个控件。
如果控件不需要主题支持。用户控件不支持复杂的自定义、控件模板,并且难以设置样式。
如果开发人员更喜欢使用代码隐藏模型编写控件,其中视图和事件处理程序的直接代码隐藏。
您不会跨应用程序共享您的控件。
示例
让我们来看一个用户控件的示例,并按照以下步骤操作。
创建一个新的WPF项目,然后右键单击您的解决方案,然后选择“添加”>“新建项…”。
将打开以下窗口。现在选择**用户控件(WPF)**,并将其命名为MyUserControl。
单击“添加”按钮,您将看到两个新文件(MyUserControl.xaml和MyUserControl.cs)将添加到您的解决方案中。
这是XAML代码,其中在MyUserControl.xaml文件中创建了一个按钮和一个文本框,并具有一些属性。
<UserControl x:Class = "WPFUserControl.MyUserControl" 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 = "300" d:DesignWidth = "300"> <Grid> <TextBox Height = "23" HorizontalAlignment = "Left" Margin = "80,49,0,0" Name = "txtBox" VerticalAlignment = "Top" Width = "200" /> <Button Content = "Click Me" Height = "23" HorizontalAlignment = "Left" Margin = "96,88,0,0" Name = "button" VerticalAlignment = "Top" Click = "button_Click" /> </Grid> </UserControl>
下面是MyUserControl.cs文件中按钮单击事件的C#代码,它更新文本框。
using System; using System.Windows; using System.Windows.Controls; namespace WPFUserControl { /// <summary> /// Interaction logic for MyUserControl.xaml /// </summary> public partial class MyUserControl : UserControl { public MyUserControl() { InitializeComponent(); } private void button_Click(object sender, RoutedEventArgs e) { txtBox.Text = "You have just clicked the button"; } } }
这是在MainWindow.xaml中添加用户控件的实现。
<Window x:Class = "XAMLUserControl.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:control = "clr-namespace:WPFUserControl" Title = "MainWindow" Height = "350" Width = "525"> <Grid> <control:MyUserControl/> </Grid> </Window>
编译并执行上述代码后,将生成以下窗口。
单击“单击我”按钮后,您会注意到文本框内的文本已更新。
自定义控件
自定义控件是一个类,它提供自己的样式和模板,这些样式和模板通常在generic.xaml中定义。自定义控件用于以下场景:
如果控件不存在,则必须从头创建它。
如果您想通过添加额外的属性或额外功能来扩展或添加预先存在的控件的功能以适应您的特定场景。
如果您的控件需要支持主题和样式。
如果您想跨应用程序共享您的控件。
示例
让我们来看一个例子来了解自定义控件是如何工作的。创建一个新的WPF项目,然后右键单击您的解决方案,然后选择“添加”>“新建项…”。
它将打开以下窗口。现在选择**自定义控件(WPF)**,并将其命名为**MyCustomControl**。
单击“添加”按钮,您将看到两个新文件(Themes/Generic.xaml和MyCustomControl.cs)将添加到您的解决方案中。
这是XAML代码,其中在Generic.xaml文件中为自定义控件设置了样式。
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFCustomControls"> <Style TargetType = "{x:Type local:MyCustomControl}" BasedOn = "{StaticResource {x:Type Button}}"> <Setter Property = "Background" Value = "LightSalmon" /> <Setter Property = "Foreground" Value = "Blue"/> </Style> </ResourceDictionary>
这是MyCustomControl类的C#代码,它继承自按钮类,并在构造函数中重写元数据。
using System; using System.Windows; using System.Windows.Controls; namespace WPFCustomControls { public class MyCustomControl : Button { static MyCustomControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl))); } } }
这是C#中自定义控件单击事件的实现,它更新文本块的文本。
using System; using System.Windows; using System.Windows.Controls; namespace WPFCustomControls { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void customControl_Click(object sender, RoutedEventArgs e) { txtBlock.Text = "You have just click your custom control"; } } }
这是在MainWindow.xaml中添加自定义控件和TextBlock的实现。
<Window x:Class = "WPFCustomControls.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:control = "clr-namespace:WPFCustomControls" Title = "MainWindow" Height = "350" Width = "604"> <StackPanel> <control:MyCustomControl x:Name = "customControl" Content = "Click Me" Width = "70" Margin = "10" Click = "customControl_Click"/> <TextBlock Name = "txtBlock" Width = "250" Height = "30"/> </StackPanel> </Window>
编译并执行上述代码后,它将生成以下窗口,其中包含一个自定义控件(一个自定义按钮)。
单击自定义按钮后,您将看到文本块内的文本已更新。
WPF - 异常处理
异常是程序执行期间遇到的任何错误条件或意外行为。异常可能是由于多种原因引起的,其中一些如下:
您的代码或您调用的代码(例如共享库)中的错误;
不可用的操作系统资源;
公共语言运行时遇到的意外条件(例如无法验证的代码);
语法
异常能够将程序的流程从一部分转移到另一部分。在.NET框架中,异常处理具有以下四个关键字:
**try** − 在此块中,程序识别引发某些异常的特定条件。
**catch** − catch关键字指示捕获异常。**try**块后跟一个或多个**catch**块,用于捕获带有异常处理程序的异常,该处理程序位于程序中您想要处理问题的点。
**finally** − finally块用于执行给定的语句集,无论是否抛出异常。例如,如果您打开一个文件,则无论是否引发异常,都必须关闭它。
**throw** − 当出现问题时,程序会抛出异常。这是使用throw关键字完成的。
使用这四个关键字的语法如下:
try { ///This will still trigger the exception } catch (ExceptionClassName e) { // error handling code } catch (ExceptionClassName e) { // error handling code } catch (ExceptionClassName e) { // error handling code } finally { // statements to be executed }
当try块根据程序流程的情况可能引发多个异常时,使用多个catch语句。
层次结构
.NET框架中几乎所有异常类都是直接或间接从Exception类派生的。从Exception类派生的最重要的异常类包括:
ApplicationException类 − 它支持程序生成的异常。当开发人员想要定义异常时,类应该从此类派生。
SystemException类 − 它是所有预定义运行时系统异常的基类。以下层次结构显示了运行时提供的标准异常。
下表列出了运行时提供的标准异常以及应该在什么条件下创建派生类。
异常类型 | 基类 | 描述 |
---|---|---|
Exception | Object | 所有异常的基类。 |
SystemException | Exception | 所有运行时生成的错误的基类。 |
IndexOutOfRangeException | SystemException | 仅当数组索引不正确时,才由运行时引发。 |
NullReferenceException | SystemException | 仅当引用空对象时,才由运行时引发。 |
AccessViolationException | SystemException | 仅当访问无效内存时,才由运行时引发。 |
InvalidOperationException | SystemException | 当方法处于无效状态时引发。 |
ArgumentException | SystemException | 所有参数异常的基类。 |
ArgumentNullException | ArgumentException | 方法不允许参数为null时引发。 |
ArgumentOutOfRangeException | ArgumentException | 方法验证参数在给定范围内时引发。 |
ExternalException | SystemException | 发生在运行时外部环境或针对运行时外部环境的异常的基类。 |
SEHException | ExternalException | 封装 Win32 结构化异常处理信息的异常。 |
示例
让我们来看一个简单的例子来更好地理解这个概念。首先创建一个名为WPFExceptionHandling的新WPF项目。
从工具箱拖动一个文本框到设计窗口。以下XAML代码创建一个文本框并用一些属性初始化它。
<Window x:Class = "WPFExceptionHandling.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFExceptionHandling" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "241" Margin = "70,39,0,0" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "453"/> </Grid> </Window>
以下是C#中带有异常处理的文件读取。
using System; using System.IO; using System.Windows; namespace WPFExceptionHandling { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ReadFile(0); } void ReadFile(int index) { string path = @"D:\Test.txt"; StreamReader file = new StreamReader(path); char[] buffer = new char[80]; try { file.ReadBlock(buffer, index, buffer.Length); string str = new string(buffer); str.Trim(); textBox.Text = str; } catch (Exception e) { MessageBox.Show("Error reading from "+ path + "\nMessage = "+ e.Message); } finally { if (file != null) { file.Close(); } } } } }
编译并执行上述代码后,它将生成一个窗口,其中文本框内显示文本。
当引发异常或手动引发异常(如下面的代码所示)时,它将显示一个带有错误的消息框。
using System; using System.IO; using System.Windows; namespace WPFExceptionHandling { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ReadFile(0); } void ReadFile(int index) { string path = @"D:\Test.txt"; StreamReader file = new StreamReader(path); char[] buffer = new char[80]; try { file.ReadBlock(buffer, index, buffer.Length); string str = new string(buffer); throw new Exception(); str.Trim(); textBox.Text = str; } catch (Exception e) { MessageBox.Show("Error reading from "+ path + "\nMessage = "+ e.Message); } finally { if (file != null) { file.Close(); } } } } }
执行上述代码时引发异常,将显示以下消息。
我们建议您执行上述代码并尝试其功能。
WPF - 本地化
本地化是指将应用程序资源翻译成针对应用程序支持的特定文化的本地化版本。
当您开发的应用程序只有一种语言时,您正在限制客户数量和业务规模。如果您想增加客户群,从而增加业务规模,那么您的产品必须面向全球受众。经济高效的本地化是接触更多客户的最佳和最经济的方法之一。
在WPF中,使用resx文件可以很容易地创建可本地化的应用程序,这是本地化最简单的解决方案。让我们来看一个简单的例子来了解它的工作原理:
创建一个名为WPFLocalization的新WPF项目。
在解决方案资源管理器中,您将在“属性”文件夹下看到Resources.resx文件。
将访问修饰符从internal更改为public,以便它可以在XAML文件中访问。
现在添加我们将要在应用程序中使用的以下字符串的名称和值。
创建Resources.resx文件的两个副本,名称分别为Resources.en.resx和Resources.ru-RU.resx。这些是特定于语言和国家/地区名称的命名约定,可以在国家语言支持 (NLS) API 参考 ( https://msdn.microsoft.com/en-us/goglobal/bb896001.aspx ) 页面上找到。
将Resources.ru-RU.resx中的值更改为俄语单词,如下所示。
让我们转到设计窗口,并拖动三个文本框、三个标签和三个按钮。
在XAML文件中,首先添加命名空间声明以使用本地化资源 xmlns:p = "clr-namespace:WPFLocalization.Properties"
设置所有控件的属性,如下所示。在这个例子中,我们不会在XAML文件中使用硬编码字符串作为标签、按钮和窗口标题的内容。我们将使用在*.resx文件中定义的字符串。例如,对于窗口标题,我们使用在*.resx文件中定义的Title字符串,如下所示 “Title = "{x:Static p:Resources.Title}"”
以下是创建控件并使用不同属性初始化控件的XAML文件。
<Window x:Class = "WPFLocalization.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local = "clr-namespace:WPFLocalization" xmlns:p = "clr-namespace:WPFLocalization.Properties" Title = "{x:Static p:Resources.Title}" Height = "350" Width = "604"> <Grid> <TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "23" Margin = "128,45,0,0" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "304"/> <Label x:Name = "label" Content = "{x:Static p:Resources.Name}" HorizontalAlignment = "Left" Margin = "52,45,0,0" VerticalAlignment = "Top" Width = "86"/> <TextBox x:Name = "textBox1" HorizontalAlignment = "Left" Height = "23" Margin = "128,102,0,0" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "304"/> <Label x:Name = "label1" Content = "{x:Static p:Resources.Address}" HorizontalAlignment = "Left" Margin = "52,102,0,0" VerticalAlignment = "Top" Width = "86"/> <TextBox x:Name = "textBox2" HorizontalAlignment = "Left" Height = "23" Margin = "128,157,0,0" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "80"/> <Label x:Name = "label2" Content = "{x:Static p:Resources.Age}" HorizontalAlignment = "Left" Margin = "52,157,0,0" VerticalAlignment = "Top" Width = "86"/> <Button x:Name = "button" Content = "{x:Static p:Resources.OK_Button}" HorizontalAlignment = "Left" Margin = "163,241,0,0" VerticalAlignment = "Top" Width = "75"/> <Button x:Name = "button1" Content = "{x:Static p:Resources.Cancel_Button}" HorizontalAlignment = "Left" Margin = "282,241,0,0" VerticalAlignment = "Top" Width = "75"/> <Button x:Name = "button2" Content = "{x:Static p:Resources.Help_Button}" HorizontalAlignment = "Left" Margin = "392,241,0,0" VerticalAlignment = "Top" Width = "75"/> </Grid> </Window>
编译并执行上述代码后,您将看到以下包含不同控件的窗口。
默认情况下,程序使用默认的Resources.resx。如果您想显示在Resources.ru-RU.resx文件中定义的俄语文本,则需要在App.xaml文件中程序启动时显式设置区域性,如下所示。
using System.Windows; namespace WPFLocalization { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { App() { System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("ru-RU"); //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en"); } } }
运行应用程序时,您将看到所有俄语文本。
我们建议您执行上述代码,并为其他语言环境创建resx文件。
WPF - 交互
在WPF中,交互显示视图如何与该视图中位于的控件交互。最常见的交互有两种:
- 行为
- 拖放
行为
行为是在Expression Blend 3中引入的,它可以将某些功能封装到可重用的组件中。要添加其他行为,您可以将这些组件附加到控件。行为提供了更大的灵活性,可以轻松设计复杂的用户交互。
让我们来看一个简单的例子,其中ControlStoryBoardAction行为附加到控件。
创建一个名为WPFBehavior的新WPF项目。
以下XAML代码创建一个椭圆和两个按钮来控制椭圆的移动。
<Window xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFBehaviors" xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei = "http://schemas.microsoft.com/expression/2010/interactions" x:Class = "WPFBehaviors.MainWindow" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Window.Resources> <Storyboard x:Key = "Storyboard1" RepeatBehavior = "Forever" AutoReverse = "True"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "(UIElement.RenderTransform).(TransformGroup.Children )[3].(TranslateTransform.X)" Storyboard.TargetName = "ellipse"> <EasingDoubleKeyFrame KeyTime = "0:0:1" Value = "301.524"/> <EasingDoubleKeyFrame KeyTime = "0:0:2" Value = "2.909"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "(UIElement.RenderTransform).(TransformGroup.Children )[3].(TranslateTransform.Y)" Storyboard.TargetName = "ellipse"> <EasingDoubleKeyFrame KeyTime = "0:0:1" Value = "-0.485"/> <EasingDoubleKeyFrame KeyTime = "0:0:2" Value = "0"/> </DoubleAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(ContentControl.Content)" Storyboard.TargetName = "button"> <DiscreteObjectKeyFrame KeyTime = "0" Value = "Play"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(ContentControl.Content)" Storyboard.TargetName = "button1"> <DiscreteObjectKeyFrame KeyTime = "0" Value = "Stop"/> <DiscreteObjectKeyFrame KeyTime = "0:0:2" Value = "Stop"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Window.Triggers> <EventTrigger RoutedEvent = "FrameworkElement.Loaded"> <BeginStoryboard Storyboard = "{StaticResource Storyboard1}"/> </EventTrigger> </Window.Triggers> <Grid> <Ellipse x:Name = "ellipse" Fill = "#FFAAAAC5" HorizontalAlignment = "Left" Height = "50.901" Margin = "49.324,70.922,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "73.684" RenderTransformOrigin = "0.5,0.5"> <Ellipse.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Ellipse.RenderTransform> </Ellipse> <Button x:Name = "button" Content = "Play" HorizontalAlignment = "Left" Height = "24.238" Margin = "63.867,0,0,92.953" VerticalAlignment = "Bottom" Width = "74.654"> <i:Interaction.Triggers> <i:EventTrigger EventName = "Click"> <ei:ControlStoryboardAction Storyboard = "{StaticResource Storyboard1}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> <Button x:Name = "button1" Content = "Stop" HorizontalAlignment = "Left" Height = "24.239" Margin = "160.82,0,0,93.922" VerticalAlignment = "Bottom" Width = "75.138"> <i:Interaction.Triggers> <i:EventTrigger EventName = "Click"> <ei:ControlStoryboardAction ControlStoryboardOption = "Stop" Storyboard = "{StaticResource Storyboard1}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> </Grid> </Window>
编译并执行上述代码后,它将生成以下包含椭圆和两个按钮的窗口。
按下播放按钮时,它将从左向右移动,然后返回其原始位置。停止按钮将停止椭圆的移动。
拖放
用户界面上的拖放功能可以显著提高应用程序的效率和生产力。使用拖放功能的应用程序很少,因为人们认为它很难实现。在某种程度上,处理拖放功能确实很难,但在WPF中,您可以轻松地处理它。
让我们来看一个简单的例子来了解它的工作原理。我们将创建一个应用程序,您可以在其中将颜色从一个矩形拖放到另一个矩形。
创建一个名为WPFDragAndDrop的新WPF项目。
将五个矩形拖到设计窗口,并设置以下XAML文件中所示的属性。
<Window x:Class = "WPFDragAndDrop.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFDragAndDrop" mc:Ignorable = "d" Title = "MainWindow" Height = "402.551" Width = "604"> <Grid> <Rectangle Name = "Target" Fill = "AliceBlue" HorizontalAlignment = "Left" Height = "345" Margin = "10,10,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "387" AllowDrop = "True" Drop = "Target_Drop"/> <Rectangle Fill = "Beige" HorizontalAlignment = "Left" Height = "65" Margin = "402,10,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "184" MouseLeftButtonDown = "Rect_MLButtonDown"/> <Rectangle Fill = "LightBlue" HorizontalAlignment = "Left" Height = "65" Margin = "402,80,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "184" MouseLeftButtonDown = "Rect_MLButtonDown"/> <Rectangle Fill = "LightCoral" HorizontalAlignment = "Left" Height = "65" Margin = "402,150,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "184" MouseLeftButtonDown = "Rect_MLButtonDown"/> <Rectangle Fill = "LightGray" HorizontalAlignment = "Left" Height = "65" Margin = "402,220,0,0" Stroke = "Black" VerticalAlignment = "Top" Width = "184" MouseLeftButtonDown = "Rect_MLButtonDown"/> <Rectangle Fill = "OliveDrab" HorizontalAlignment = "Left" Height = "65" Margin = "402,290,0,-7" Stroke = "Black" VerticalAlignment = "Top" Width = "184" MouseLeftButtonDown = "Rect_MLButtonDown"/> </Grid> </Window>
第一个矩形是目标矩形,因此用户可以将颜色从另一个矩形拖动到目标矩形。
以下是C#中拖放的事件实现。
using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace WPFDragAndDrop { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Rect_MLButtonDown(object sender, MouseButtonEventArgs e) { Rectangle rc = sender as Rectangle; DataObject data = new DataObject(rc.Fill); DragDrop.DoDragDrop(rc, data,DragDropEffects.Move); } private void Target_Drop(object sender, DragEventArgs e) { SolidColorBrush scb = (SolidColorBrush)e.Data.GetData(typeof(SolidColorBrush)); Target.Fill = scb; } } }
运行应用程序时,它将生成以下窗口。
如果您将颜色从右侧的矩形拖动并将其放到左侧的大矩形上,您将立即看到其效果。
让我们从右侧拖动第4个。
您可以看到目标矩形的颜色已更改。我们建议您执行上述代码并尝试其功能。
WPF - 2D 图形
WPF 提供了广泛的 2D 图形,可以根据您的应用程序需求进行增强。WPF 支持用于绘制图形内容的绘图和形状对象。
形状和绘图
Shape 类派生自 FrameworkElement 类,Shape 对象可以在面板和大多数控件中使用。
WPF 提供一些从 Shape 类派生的基本形状对象,例如 Ellipse、Line、Path、Polygon、Polyline 和 Rectangle。
另一方面,绘图对象不派生自 FrameworkElement 类,并提供更轻量级的实现。
与 Shape 对象相比,绘图对象更简单。它们也具有更好的性能特性。
示例
让我们来看一个简单的例子,了解如何使用不同的形状对象。
创建一个名为WPF2DGraphics的新WPF项目。
以下代码创建不同类型的形状。
<Window x:Class = "WPF2DGraphics.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPF2DGraphics" xmlns:PresentationOptions = "http://schemas.microsoft.com/winfx/2006/xaml/present ation/options" mc:Ignorable = "PresentationOptions" Title = "MainWindow" Height = "400" Width = "604"> <StackPanel> <Ellipse Width = "100" Height = "60" Name = "sample" Margin = "10"> <Ellipse.Fill> <RadialGradientBrush> <GradientStop Offset = "0" Color = "AliceBlue"/> <GradientStop Offset = "1" Color = "Gray"/> <GradientStop Offset = "2" Color = "Red"/> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> <Path Stroke = "Red" StrokeThickness = "5" Data = "M 10,70 L 200,70" Height = "42.085" Stretch = "Fill" Margin = "140.598,0,146.581,0" /> <Path Stroke = "BlueViolet" StrokeThickness = "5" Data = "M 20,100 A 100,56 42 1 0 200,10" Height = "81.316" Stretch = "Fill" Margin = "236.325,0,211.396,0" /> <Path Fill = "LightCoral" Margin = "201.424,0,236.325,0" Stretch = "Fill" Height = "124.929"> <Path.Data> <PathGeometry> <PathFigure StartPoint = "50,0" IsClosed = "True"> <LineSegment Point = "100,50"/> <LineSegment Point = "50,100"/> <LineSegment Point = "0,50"/> </PathFigure> </PathGeometry> </Path.Data> </Path> </StackPanel> </Window>
编译并执行上述代码后,它将生成一个椭圆、一条直线、一条弧线和一个多边形。
示例
让我们来看另一个例子,它演示如何用绘图绘制区域。
创建一个名为WPF2DGraphics1的新WPF项目。
以下XAML代码显示如何使用图像绘图进行不同的绘画。
<Window x:Class = "WPF2DGraphics1.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:PresentationOptions = "http://schemas.microsoft.com/winfx/2006/xaml/present ation/options" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "PresentationOptions" xmlns:local = "clr-namespace:WPF2DGraphics1" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Border BorderBrush = "Gray" BorderThickness = "1" HorizontalAlignment = "Left" VerticalAlignment = "Top" Margin = "20"> <Image Stretch = "None"> <Image.Source> <DrawingImage PresentationOptions:Freeze = "True"> <DrawingImage.Drawing> <DrawingGroup> <ImageDrawing Rect = "300,100,300,180" ImageSource = "Images\DSC_0104.JPG"/> <ImageDrawing Rect = "0,100,250,100" ImageSource = "Images\DSC_0104.JPG"/> <ImageDrawing Rect = "150,0,25,25" ImageSource = "Images\DSC_0104.JPG"/> <ImageDrawing Rect = "0,0,75,75" ImageSource = "Images\DSC_0104.JPG"/> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> </Border> </Grid> </Window>
运行应用程序时,将生成以下输出:
我们建议您执行上述代码并尝试更多 2D 形状和绘图。
WPF - 3D 图形
Windows Presentation Foundation (WPF) 提供了根据您的应用程序需求绘制、转换和动画化 3D 图形的功能。它不支持完整的 3D 游戏开发,但在某种程度上,您可以创建 3D 图形。
通过组合 2D 和 3D 图形,您还可以创建丰富的控件,提供复杂的数据插图,或增强应用程序界面的用户体验。Viewport3D 元素将 3D 模型托管到我们的 WPF 应用程序中。
示例
让我们来看一个简单的例子,了解如何使用 3D 图形。
创建一个名为WPF3DGraphics的新WPF项目。
以下XAML代码显示如何使用3D几何创建2D对象。
<Window x:Class = "WPF3DGraphics.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:local = "clr-namespace:WPF3DGraphics" mc:Ignorable = "d" Title = "MainWindow" Height = "500" Width = "604"> <Grid> <Viewport3D> <Viewport3D.Camera> <PerspectiveCamera Position = "2,0,10" LookDirection = "0.2,0.4,-1" FieldOfView = "65" UpDirection = "0,1,0" /> </Viewport3D.Camera> <ModelVisual3D> <ModelVisual3D.Content> <Model3DGroup> <AmbientLight Color = "Bisque" /> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D Positions = "0,0,0 0,8,0 10,0,0 8,8,0" Normals = "0,0,1 0,0,1 0,0,1 0,0,1" TriangleIndices = "0,2,1 1,2,3"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush = "Bisque" /> </GeometryModel3D.Material> </GeometryModel3D> </Model3DGroup> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D> </Grid> </Window>
编译并执行上述代码后,它将在3D中生成一个2D对象。
示例
让我们来看另一个显示3D对象的示例。
创建一个名为WPF3DGraphics1的新WPF项目
以下XAML代码创建一个3D对象和一个滑块。借助滑块,您可以旋转此3D对象。
<Window x:Class = "WPF3DGraphics1.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPF3DGraphics1" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Grid> <Viewport3D Name="viewport3D1"> <Viewport3D.Camera> <PerspectiveCamera x:Name = "camMain" Position = "6 5 4" LookDirection = "-6 -5 -4"> </PerspectiveCamera> </Viewport3D.Camera> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight x:Name = "dirLightMain" Direction = "-1,-1,-1"> </DirectionalLight> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D x:Name = "MyModel"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name = "meshMain" Positions = "0 0 0 1 0 0 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 0 1 1" TriangleIndices = "2 3 1 3 1 0 7 1 3 7 5 1 6 5 7 6 4 5 6 2 0 2 0 4 2 7 3 2 6 7 0 1 5 0 5 4"> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial x:Name = "matDiffuseMain"> <DiffuseMaterial.Brush> <SolidColorBrush Color = "Bisque"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> <ModelVisual3D.Transform> <RotateTransform3D> <RotateTransform3D.Rotation> <AxisAngleRotation3D x:Name = "rotate" Axis = "1 2 1"/> </RotateTransform3D.Rotation> </RotateTransform3D> </ModelVisual3D.Transform> </ModelVisual3D> </Viewport3D> <Slider Height = "23" HorizontalAlignment = "Left" Margin = "145,271,0,0" Name = "slider1" VerticalAlignment = "Top" Width = "269" Maximum = "360" Value = "{Binding ElementName = rotate, Path=Angle}" /> </Grid> </Window>
运行应用程序时,它将在您的窗口上生成一个3D对象和一个滑块。
滑动滑块时,窗口上的对象也会旋转。
我们建议您执行上述代码并尝试更多 3D 几何图形。
WPF - 多媒体
WPF 应用程序使用MediaElement支持视频和音频。它允许您将音频和视频集成到应用程序中。MediaElement 类的工作方式与 Image 类类似。您只需将其指向媒体,它就会渲染出来。主要区别在于它将是一个动态图像,但如果您将其指向仅包含音频而没有视频的文件(例如 MP3),它将在不显示任何屏幕内容的情况下播放音频。
WPF 支持所有类型的视频/音频格式,具体取决于机器配置。如果媒体文件可以在媒体播放器中播放,那么它在同一台机器上的 WPF 中也能工作。
示例
让我们来看一个例子,了解如何在您的应用程序中集成多媒体。
创建一个名为WPFMultimedia的新 WPF 项目。
下面的 XAML 代码创建一个媒体元素和三个按钮,并使用一些属性初始化它们。
<Window x:Class = "WPFMultimedia.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFMultimedia" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <StackPanel HorizontalAlignment = "Center" VerticalAlignment = "Center"> <MediaElement Name = "myMedia" Source = "D:\MicrosoftMVA.mp4" LoadedBehavior = "Manual" Width = "591" Height = "274" /> <StackPanel Orientation = "Horizontal" Margin = "0,10,0,0"> <Button Content = "Play" Margin = "0,0,10,0" Padding = "5" Click = "mediaPlay" /> <Button Content = "Pause" Margin = "0,0,10,0" Padding = "5" Click = "mediaPause" /> <Button x:Name = "muteButt" Content = "Mute" Padding = "5" Click = "mediaMute" /> </StackPanel> </StackPanel> </Grid> </Window>
以下是 C# 中不同按钮的 Click 事件实现。
using System; using System.Windows; namespace WPFMultimedia { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); myMedia.Volume = 100; myMedia.Play(); } void mediaPlay(Object sender, EventArgs e) { myMedia.Play(); } void mediaPause(Object sender, EventArgs e) { myMedia.Pause(); } void mediaMute(Object sender, EventArgs e) { if (myMedia.Volume == 100) { myMedia.Volume = 0; muteButt.Content = "Listen"; } else { myMedia.Volume = 100; muteButt.Content = "Mute"; } } } }
编译并执行上述代码后,将生成以下窗口。您可以使用三个按钮播放视频并控制其播放。
您可以使用这些按钮暂停、静音和播放视频。
语音合成器
WPF 具有将文本转换为语音的功能。此 API 包含在 System.Speech 命名空间中。SpeechSynthesizer 类将文本转换为语音。
示例
让我们来看一个简单的例子。
创建一个名为WPFTextToSpeech的新 WPF 项目。
我们需要添加 System.Speech 程序集作为引用,才能使SpeechSynthesizer 类正常工作。
右键单击“引用”,然后选择“添加引用”。
将打开“引用管理器”对话框。现在选中 System.Speech 复选框。
单击“确定”按钮。您可以在“引用”中看到 System.Speech 程序集。
现在从工具箱中将一个按钮和一个文本框拖到设计窗口中。
下面的 XAML 代码创建一个按钮和一个文本框,并使用一些属性初始化它们。
<Window x:Class = "WPFTextToSpeech.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:WPFTextToSpeech" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604"> <Grid> <Button x:Name = "button" Content = "Speak" HorizontalAlignment = "Left" Margin = "218,176,0,0" VerticalAlignment = "Top" Width = "75"/> <TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "23" Margin = "60,104,0,0" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "418"/> </Grid> </Window>
以下是 C# 中的简单实现,它将文本框内的文本转换为语音。
using System.Speech.Synthesis; using System.Windows; namespace WPFTextToSpeech { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void button_Click(object sender, RoutedEventArgs e) { if (textBox.Text != "") { SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(); speechSynthesizer.Speak(textBox.Text); } else { MessageBox.Show("Write some thing in the textbox!"); } } } }
编译并执行上述代码后,将生成以下窗口。现在,在文本框中输入“Hello World”,然后单击“Speak”按钮。
它将发出“Hello World”的声音。如果您没有在文本框中输入任何内容,它将显示以下消息。
我们建议您执行上述示例。