Wpf 简明教程

WPF - Quick Guide

WPF - Overview

WPF 是 Windows Presentation Foundation 的缩写。它是一个用于构建 Windows 应用程序的强大框架。本教程介绍了你需要了解的用于构建 WPF 应用程序的功能,以及它如何对 Windows 应用程序带来根本性的变化。

WPF 最初在 .NET Framework 3.0 版本中引入,然后在随后的 .NET Framework 版本中添加了许多其他功能。

WPF Architecture

在 WPF 之前,Microsoft 提供的其他用户界面框架(例如 MFC 和 Windows 窗体)只是 User32 和 GDI32 DLL 的包装器,但 WPF 仅极少使用 User32。因此,

  1. WPF 不仅仅是一个包装器。

  2. 它是 .NET Framework 的一部分。

  3. 它包含托管代码和非托管代码的混合。

WPF 架构的主要组件如下面的图形所示。WPF 最重要的代码部分是 −

  1. Presentation Framework

  2. Presentation Core

  3. Milcore

wpf architecture

presentation frameworkpresentation core 用托管代码编写。 Milcore 是非托管代码的一部分,可以与 DirectX(负责显示和渲染)紧密集成。 CLR 通过提供内存管理、错误处理等多种功能使开发过程更具效率。

WPF – Advantages

在早期 GUI 框架中,应用程序的界面外观与行为之间没有真正的区别。GUI 和行为都用同一种语言创建,例如 C# 或 VB.Net,这将要求开发人员付出更多努力来实现 UI 和与其关联的行为。

在 WPF 中,UI 元素在 XAML 中设计,而行为可以用过程化语言(例如 C# 和 VB.Net)实现。因此,将行为与设计人员代码分离非常容易。

借助于 XAML,程序员可以与设计人员并行工作。GUI 与其行为之间的分离使我们能够通过使用样式和模板轻松地更改控件的外观。

WPF – Features

WPF 是一个用于创建 Windows 应用程序功能强大的框架。它支持许多很棒的功能,其中一些列在下方 −

Feature

Description

Control inside a Control

允许将一个控件的内容定义为另一个控件中。

Data binding

在用户界面上的 UI 元素和数据对象之间显示和交互数据。

Media services

提供一个集成的系统,使用图像、音频和视频等常见的媒体元素来构建用户界面。

Templates

在 WPF 中,可以使用模板直接定义元素的外观。

Animations

在用户界面上建立交互性和移动性。

Alternative input

支持在 Windows 7 及更高版本上进行多点触控输入。

Direct3D

允许显示更复杂的图形和自定义主题。

WPF - Environment Setup

Microsoft 提供了两个用于 WPF 应用程序开发的重要工具。

  1. Visual Studio

  2. Expression Blend

这两个工具都可以创建 WPF 项目,但实际上开发人员更多地使用 Visual Studio,而设计人员更多地使用 Blend。对于本教程,我们将主要使用 Visual Studio。

Installation

Microsoft 提供了 Visual Studio 的免费版本,可以从 VisualStudio 下载。

下载文件并按照以下步骤操作,在系统上设置 WPF 应用程序开发环境。

  1. 下载完成后,运行 installer 。将显示以下对话框。

installer
  1. 单击 Install 按钮,并将开始安装过程。

installation process
  1. 安装过程成功完成后,您将看到以下对话框。

dialog box
  1. 关闭此对话框并根据需要重新启动计算机。

  2. 现在从“开始”菜单中打开 Visual Studio,这将打开以下对话框。

visual studio
  1. 完成后,您将看到 Visual Studio 的主窗口。

window of visual studio

您现在可以构建第一个 WPF 应用程序了。

WPF - Hello World

在本章中,我们将开发一个简单的 Hello World WPF 应用程序。因此,让我们按照以下步骤开始简单的实现。

  1. 单击“文件”>“新建”>“项目”菜单选项。

project menu option
  1. 将显示以下对话框。

new project dialog box
  1. 在模板下,选择 Visual C#,并在中间面板中,选择 WPF 应用程序。

  2. 为项目起个名字。在名称栏中输入 HelloWorld ,然后单击确定按钮。

  3. 默认情况下会创建两个文件,一个是 XAML 文件(mainwindow.xaml),另一个是 CS 文件(mainwindow.cs)。

  4. 在 mainwindow.xaml 中,你会看到两个子窗口,一个是 design window ,另一个是 source (XAML) window

  5. 在 WPF 应用程序中,有两种方法可以为你的应用程序设计 UI。其一是从工具箱中将 UI 元素简单拖放到设计窗口。第二种方法是通过为 UI 元素编写 XAML 标签来设计你的 UI。当使用拖放功能进行 UI 设计时,Visual Studio 会处理 XAML 标签。

  6. 在 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>
  1. 默认情况下,在页面之后,将一个 Grid 设置为第一个元素。

  2. 让我们转到工具箱,将一个 TextBlock 拖放到设计窗口。

toolbox
  1. 你将在设计窗口中看到 TextBlock。

textblock
  1. 当你查看源窗口时,你会看到 Visual Studio 已经为你生成了 TextBlock 的 XAML 代码。

  2. 让我们在 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>
  1. 现在,你也会在设计窗口中看到这一改变。

design window

当上面的代码被编译并执行时,您将看到以下窗口。

first wpf application

祝贺你!你已经设计并创建了你的第一个 WPF 应用程序。

WPF - XAML Overview

在你使用 WPF 时,你会遇到的首要问题之一就是 XAML。XAML 代表可扩展应用程序标记语言。它是一种基于 XML 的简单且声明性语言。

  1. 在 XAML 中,创建、初始化和设置具有层次关系的对象的属性非常容易。

  2. 它主要用于设计 GUI,但也可以用于其他目的,例如,在 Workflow Foundation 中声明工作流。

Basic Syntax

当你创建新的 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 文件包含不同种类的信息。以下表格简要解释每条信息的用途。

Information

Description

<Window

这是根的开放对象元素或容器。

x:Class = "Resources.MainWindow"

这是部分类声明,它将标记连接到背后定义的部分类代码。

xmlns = link:http://schemas.microsoft.com/win fx/2006/xaml/presentation["http://schemas.microsoft.com/win fx/2006/xaml/presentation"]

映射 WPF 客户端/框架的默认 XAML 命名空间

xmlns:x = link:http://schemas.microsoft.com/w infx/2006/xaml["http://schemas.microsoft.com/w infx/2006/xaml"]

用于 XAML 语言的 XAML 命名空间,将其映射为 x: 前缀

>

根的对象元素的结束。

<Grid> </Grid>

它是一个空网格对象的起始和结束标记。

</Window>

Closing the object element

XAML 的语法规则几乎与 XML 相似。如果您查看 XAML 文档,您就会注意它实际上是一个有效的 XML 文件,但 XML 文件并不一定是 XAML 文件。这是因为在 XML 中,属性的值必须是字符串,而在 XAML 中,它可以是不同的对象,称为属性元素语法。

  1. 对象元素的语法以一个左尖括号 (<) 和对象的名称开头,例如按钮。

  2. 定义该对象元素的一些属性和属性。

  3. 该对象元素必须以一个正斜杠 (/) 和紧随其后的右尖括号 (>) 结束。

Example of simple object with no child element

<Button/>

Example of object element with some attributes

<Button Content = "Click Me" Height = "30" Width = "60" />

Example of an alternate syntax do define properties (Property element syntax)

<Button>
   <Button.Content>Click Me</Button.Content>
   <Button.Height>30</Button.Height>
   <Button.Width>60</Button.Width>
</Button>

Example of Object with Child Element: StackPanel contains Textblock as child element

<StackPanel Orientation = "Horizontal">
   <TextBlock Text = "Hello"/>
</StackPanel>

Why XAML in WPF

XAML 不仅是 WPF 中最广为人知的特性,也是最容易被误解的特性之一。如果您接触过 WPF,您一定听说过 XAML;但请注意以下有关 XAML 的两个鲜为人知的事实。

  1. WPF doesn’t need XAML

  2. XAML doesn’t need 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 output

从上述示例中,显然您可以使用 XAML 做些什么来创建、初始化和设置对象属性,相同的任务也可以使用代码完成。

  1. XAML 只是设计 UI 元素的另一种简单而简单的方法。

  2. 通过 XAML,它并不意味着您可以用来设计 UI 元素的方法是唯一的方式。您可以声明 XAML 中的对象或使用代码定义它们。

  3. XAML 是可选的,但尽管如此,它仍然是 WPF 设计的核心。

  4. XAML 的目的是让视觉设计师们能够直接创建用户界面元素。

  5. WPF 的目标是从标记中控制用户界面的所有视觉方面。

WPF - Elements Tree

许多技术的元素和组件都按树形结构组织起来,这样程序员便可以轻松地处理对象和更改应用程序的行为。Windows Presentation Foundation (WPF) 使用对象形式的综合树形结构。在 WPF 中,对完整对象树的理解有两种方式——

  1. Logical Tree Structure

  2. Visual Tree Structure

借助这些树形结构,你可以轻松地创建和识别 UI 元素之间的关系。在大多数情况下,WPF 开发人员和设计师要么使用过程语言来创建应用程序,要么在 XAML 中设计应用程序的 UI 部分,并牢记对象树形结构。

Logical Tree Structure

在 WPF 应用程序中,XAML 中的 UI 元素结构表示逻辑树形结构。在 XAML 中,UI 的基本元素由开发人员声明。WPF 中的逻辑树定义了以下内容——

  1. Dependency properties

  2. Static and dynamic resources

  3. 按其名称等绑定元素。

让我们来看一下这个示例,其中创建了一个按钮和一个列表框。

<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 代码,你会看到一个树形结构,即根结点是窗口,在根结点内部只有一个子项,即 StackPanel。但是,StackPanel 包含两个子元素,按钮和列表框。列表框还有三个子列表框项。

Visual Tree Structure

在 WPF 中,视觉树的概念描述了视觉对象的结构,如 Visual Base Class 所表示的。它包含渲染到输出屏幕上的所有 UI 元素。

当程序员要为特定控件创建一个模板时,他实际上是在渲染该控件的视觉树。对于出于性能和优化原因要绘制较低级别控件的人来说,视觉树也非常有用。

在 WPF 应用程序中,视觉树用于——

  1. Rendering the visual objects.

  2. Rendering the layouts.

  3. 路由事件主要沿着视觉树而非逻辑树传播。

为了查看包含一个按钮和一个列表框的上述简单应用程序的视觉树,让我们编译并执行 XAML 代码,你将会看到以下窗口。

visual tree structure

当应用程序正在运行时,你可以在 Live Visual Tree 窗口中看到正在运行的应用程序的视觉树,它显示了此应用程序的完整层次结构,如下所示。

logical tree

视觉树通常是逻辑树的超集。你可以在此看到所有逻辑元素也都存在于视觉树中。因此,这两棵树实际上只是组成 UI 的同一组对象的两种不同视图。

  1. 逻辑树去掉了许多细节,让你能够专注于用户界面的核心结构,而忽略它如何被呈现的确切细节。

  2. 逻辑树是你用于创建用户界面的基本结构。

  3. 如果你关注呈现,那么视觉树将会很有用。例如,如果你希望自定义任何 UI 元素的外观,那么你需要使用视觉树。

WPF - Dependency Properties

在 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 。当鼠标位于按钮上方时,它会更改按钮的前景色。当鼠标离开按钮时,它会变回其原始颜色。

dependency property

Why We Need Dependency Properties

当你将其用于你的应用程序时,依赖属性会为你提供各种好处。依赖属性在以下情况下可用于 CLR 属性:

  1. 如果你想设置样式

  2. 如果你想数据绑定

  3. 如果你想使用资源(静态资源或动态资源)进行设置

  4. 如果你想支持动画

基本上,依赖属性提供了许多使用 CLR 属性无法获得的功能。

dependency properties 和其他 CLR properties 之间的主要区别如下所列:

  1. CLR 属性可以使用 gettersetter 直接从类的私有成员中读/写。相比之下,依赖属性不存储在本地对象中。

  2. 依赖属性存储在 DependencyObject 类提供的键/值成对的词典中。它还节省了许多内存,因为它在更改时存储属性。它也可以在 XAML 中进行绑定。

Custom Dependency Properties

在 .NET framework 中,还可以定义自定义依赖属性。请按照以下步骤在 C# 中定义自定义依赖属性。

  1. 使用系统调用 register 声明并注册你的 dependency property

  2. 为该属性提供 settergetter

  3. 定义一个 static handler ,该 static handler 将处理全局发生的任何更改。

  4. 定义一个 instance handler ,该 instance handler 将处理发生在特定实例上的任何更改。

以下 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();
      }
   }
}

这是 TextBlock 被定义为用户控件的 XAML 文件,并通过 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。

dependency property for user

WPF - Routed Events

routed event 是一种事件类型,可以在元素树中的多个侦听器上调用处理程序,而不仅仅是引发该事件的对象。它基本上是一个由 Routed Event 类的实例支持的 CLR 事件。它在 WPF 事件系统中注册。RoutedEvents 有三种主要的路由策略,如下所示:

  1. Direct Event

  2. Bubbling Event

  3. Tunnel Event

Direct Event

直接事件类似于 Windows 窗体中的事件,它们是由事件发源所在的元素触发的。

与标准 CLR 事件不同,直接路由事件支持类处理,并且可以在自定义控件样式中的事件设置器和事件触发器中使用。

直接事件的一个好例子是 MouseEnter 事件。

Bubbling Event

冒泡事件始于事件发源的元素。然后它沿着可视化树向上移动到可视化树中的最顶级元素。因此,在 WPF 中,最顶级元素很可能是窗口。

Tunnel Event

元素树根上的事件处理程序被调用,然后该事件沿着可视化树向下移动到所有子节点,直到到达事件发源的元素。

冒泡事件和隧道事件之间的区别在于隧道事件总是从预览开始。

在 WPF 应用程序中,事件通常被实现为隧道/冒泡对。因此,你将有一个预览 MouseDown,然后是 MouseDown 事件。

下面给出了 Routed 事件的一个简单示例,其中创建了一个按钮和三个文本块,并具有一些属性和事件。

<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";
      }

   }
}

当你编译并执行以上代码时,它将产生以下窗口:

routed event

当你单击按钮时,文本块将更新,如下所示。

click on button

如果你想在任何特定级别停止路由事件,那么你需要设置 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;
}

当你单击按钮时,你会观察到单击事件不会传递到窗口,并且会停止在堆栈面板上,并且第 3 个文本块不会更新。

click event

Custom Routed Events

在 .NET 框架中,也可以定义自定义路由事件。你需要按照下面给出的步骤在 C# 中定义一个自定义路由事件。

  1. 使用系统调用 RegisterRoutedEvent 声明并注册您的路由事件。

  2. 指定路由策略,即冒泡、隧道或直接。

  3. Provide the event handler.

让我们通过一个示例更深入地了解自定义路由事件。按照以下步骤操作:

  1. 使用 WPFCustomRoutedEvent 创建一个新的 WPF 项目

  2. 右键单击您的解决方案,然后选择“添加”>“新项…​”

  3. 将打开以下对话框,现在选择 Custom Control (WPF) 并将其命名为 MyCustomControl

custom routed events
  1. 单击 Add 按钮后,您会看到有两个新文件(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 class 的 C# 代码,它继承自 Control class ,其中为自定义控件创建了一个自定义路由事件 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>

当编译并执行上述代码时,它将生成以下包含自定义控件的窗口。

custom control

当您单击自定义控件时,它将生成以下消息。

click on custom control

WPF - Controls

Windows Presentation Foundation(WPF) 使得开发人员可以轻松地构建和创建视觉丰富的基于 UI 的应用程序。

  1. 其他 UI 框架中的传统 UI 元素或控件也在 WPF 应用程序中得到增强。

  2. 所有标准 WPF 控件都可以找到工具箱中,它属于 System.Windows.Controls。

  3. 这些控件也可以在 XAML 标记语言中创建。

WPF 控件的完整继承层次关系如下:

hierarchy of wpf

下表包含我们将在后续章节中讨论的控件列表。

Sr. No.

Controls & Description

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 一个控件,它允许用户通过沿轨道移动滑块控件来从一系列值中进行选择。

20

TextBlock 一个控件,它显示文本。

21

ToggleButton 一种可以在 2 个状态之间切换的按钮。

22

ToolTip 一个弹出窗口,显示元素的信息。

23

Window 提供最小化/最大化选项、标题栏、边框和关闭按钮的根窗口

24

3rd Party Controls 在 WPF 应用程序中使用第三方控件。

我们将会逐一讨论所有这些控件及其实现。

WPF - Layouts

控件的布局对于应用程序可用性非常重要、关键。它用于组织应用程序中的 GUI 元素组。选择布局面板时需要考虑某些重要问题 −

  1. 子元素的位置

  2. 子元素的大小

  3. 重叠的子元素在彼此顶部进行分层

当应用程序在不同的屏幕分辨率上时,控件的固定像素排列不起作用。XAML 提供了一组丰富的内置布局面板,以合适的方式排列 GUI 元素。下面列出了一些最常用、最流行的布局面板 −

Sr. No.

Panels & Description

1

Stack Panel 叠加面板是在 XAML 中一种简单、实用的布局面板。在叠加面板中,子元素可以根据方向属性排列在单行中,水平或垂直。

2

Wrap Panel 在包裹面板中,子元素按照顺序从左到右或从上到下放置,具体取决于方向属性。

3

Dock Panel DockPanel 定义了一个区域,以水平或垂直方式排列子元素彼此之间的相对方位。使用 Dock 属性,您可以轻松地将子元素停靠到顶部、底部、右侧、左侧和中心。

4

Canvas Panel 画布面板是基本布局面板,其中可以使用坐标显式地放置子元素,这些坐标相对于 Canvas 的任何一边,例如左、右、上和下。

5

Grid Panel 网格面板提供了一个灵活的区域,它由行和列组成。在网格中,子元素可以以表格形式排列。

WPF - Nesting of Layout

布局嵌套意味着在另一个布局内使用布局面板,例如:在网格内定义叠加面板。该概念被广泛用于在应用程序中利用多个布局的优势。在以下示例中,我们将使用网格内的叠加面板。

让我们看看以下 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>

当您编译并执行上述代码时,它将生成以下窗口。

output of nesting of layouts

我们建议您执行上述示例代码并尝试其他嵌套布局。

WPF - Input

借助 Windows Presentation Foundation (WPF),应用程序可以通过鼠标、键盘和触控面板等多种设备获取输入。在本篇中,我们将讨论 WPF 应用程序可处理的以下类型的输入 -

Sr. No.

Inputs & Description

1

Mouse 有不同类型的鼠标输入,如 MouseDown、MouseEnter、MouseLeave 等。

2

Keyboard 有许多类型的键盘输入,如 KeyDown、KeyUp、TextInput 等。

3

ContextMenu or RoutedCommands 路由命令可以在更语义化的层面上处理输入。它们实际上是简单的指令,如 New、Open、Copy、Cut 和 Save。

4

Multi Touch Windows 7 及其更高版本能够从多个触摸感应设备接收输入。WPF 应用程序还可以在触摸发生时发出事件,将触摸输入与鼠标或键盘等其他输入一起处理。

WPF - Command Line

命令行参数是一种机制,当 WPF 应用程序执行时,用户可以使用该机制传递一组参数或值给应用程序。这些参数对于从外部控制应用程序非常重要,例如,如果您想从命令提示符打开 Word 文档,那么您可以使用此命令“ C:&gt; start winword word1.docx ”,它将打开 word1.docx 文档。

命令行参数在 Startup 函数中处理。以下是一个简单的示例,展示了如何向 WPF 应用程序传递命令行参数。我们用名称 WPFCommandLine 创建一个新的 WPF 应用程序。

  1. 将一个文本框从工具箱拖到设计窗口中。

  2. 在此示例中,我们将以 txt 文件路径作为命令行参数传递给我们的应用程序。

  3. 程序将读取 txt 文件,然后将所有文本写入文本框。

  4. 以下 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>
  1. 现在,按如下所示在 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>
  1. 以下给出了 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;
         }
      }
   }
}
  1. 现在,在 MainWindow 类中,程序将打开 txt 文件并将所有文本写入文本框。

  2. 如果发现某些错误,那么程序将在文本框中显示错误消息。

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);
         }
      }
   }
}
  1. 当上述代码被编译并执行时,它将生成一个带有文本框的空白窗口,因为此程序需要命令行参数。所以 Visual Studio 提供了一种使用命令行参数执行应用程序的简单方法。

  2. 在解决方案资源管理器中右击您的 WPF 项目并选择属性,它将显示以下窗口。

wpf commandline
  1. 选择调试选项并在命令行参数中写入文件路径。

  2. 使用 Test.txt 创建一个 txt 文件,并在该文件中写入一些文本并将其保存在任何位置。在此情况下,txt 文件保存在“ D:\ ”硬盘驱动器上。

  3. 保存项目中的更改并编译和执行您的应用程序。您将看到 TextBox 中的文本,该文本由程序从 Text.txt 文件中读取。

output of commandline

现在,让我们尝试将计算机上的文件名从 Test.txt 更改为 Test1.txt ,然后再次执行程序,然后您将在文本框中看到错误消息。

error output of the commandline

我们建议您执行上述代码并按照所有步骤成功执行应用程序。

WPF - Data Binding

数据绑定是 WPF 应用程序中的一种机制,它为 Windows 运行时应用程序提供了一种简单而轻松的方式来显示数据和与其交互。在此机制中,数据的管理与数据的处理方式完全分离。

数据绑定允许在用户界面上的 UI 元素与数据对象之间流向数据。当建立绑定并且数据或业务模型更改时,它会自动将更新反映到 UI 元素,反之亦然。还可以绑定到不是标准数据源的其他元素,而是页面上的其他元素。

数据绑定有两种类型 one-way data bindingtwo-way data binding

One-Way Data Binding

在单向绑定中,数据从其源(即保存数据的对象)绑定到其目标(即显示数据的对象)。

  1. 让我们举一个简单的示例来详细了解单向数据绑定。首先,创建一个名为 WPFDataBinding 的新 WPF 项目。

  2. 以下 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>
  1. 两个文本框的文本属性绑定到“名称”和“年龄”,它们是下面显示的 Person 类的类变量。

  2. 在 Person 类中,我们只有两个变量 NameAge ,它的对象在 MainWindow 类中初始化。

  3. 在 XAML 代码中,我们绑定到属性“名称”和“年龄”,但是我们没有选择这些属性所属的对象。

  4. 更简单的方法是将一个对象分配给 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;
            }
         }
      }

   }
}
  1. 让我们运行此应用程序,您可以在 MainWindow 中立即看到我们已经成功绑定到 Person 对象的姓名和年龄。

output of data binding

当您按下 Show 按钮时,它将在消息框中显示姓名和年龄。

press show button

让我们在对话框中更改姓名和年龄。

changes made in data binding

如果您现在单击“显示”按钮,它将再次显示相同的消息。

same output of data binding

这是因为 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>

让我们再次运行此应用程序。

two way data binding

它将产生相同输出 −

press show button in 2 way

现在让我们更改 Name(姓名)和 Age(年龄)值 −

changes in two way

如果现在单击“显示”按钮,它将显示更新的消息。

updated output

我们建议您针对这两者执行上述代码,以更好地理解该概念。

WPF - Resources

资源通常与某个对象相关的定义,而您期望对该对象重复使用。它具有将数据存储在控件或当前窗口中(本地存储)或针对整个应用程序进行全局存储的能力。

将某个对象定义为资源允许我们从其他地方访问该对象。这意味着该对象可以重用。资源在资源词典中进行定义,任何对象都可以定义为资源,从而有效地使其成为可共享资产。唯一的键指定给 XAML 资源,通过此键可以利用 StaticResource 标记扩展进行引用。

资源可以分为两种类型 −

  1. StaticResource

  2. DynamicResource

StaticResource(静态资源)是一次性查找,而 DynamicResource(动态资源)的工作方式更加类似于数据绑定。它记住某个属性与特定的资源键相关联。如果与该键关联的对象发生更改,动态资源将更新目标属性。

Example

以下是一个 SolidColorBrush 资源的简单应用程序。

  1. 使用名称 WPFResouces 创建一个新的 WPF 项目。

  2. 拖动两个 Rectangles,并按照以下 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>
  1. 在上述 XAML 代码中,您可以看到一个矩形有 StaticResource,另一个有 DynamicResource,并且 brushResource 的颜色为淡黄色。

  2. 当您编译并执行此代码时,它将产生以下 MainWindow。

main window of resources

当您单击“更改资源”按钮时,您将看到具有 DynamicResource 的矩形变为红色。

change resources

Resource Scope

资源在 resource dictionaries 中进行定义,但可以在许多地方定义资源词典。在上述示例中,资源词典在 Window/page 级别上得到定义。资源在哪个词典中得到定义当即限制了该资源的作用域。因此作用域(即您可以在哪里使用资源)取决于您在何处对其进行定义。

  1. 在网格的资源词典中定义资源,则该网格及其子元素才可以访问它。

  2. 在某个窗口/页面上对其进行定义,则该窗口/页面上的所有元素都可以访问它。

  3. 可以在 App.xaml 资源词典中找到应用程序根。它是我们应用程序的根,因此此处定义的资源适用于整个应用程序。

就资源的范围而言,最常见的是应用程序级别、页面级别和特定的元素级别,如 Grid、StackPanel 等。

resource scope

上面的应用程序在其 Window/page 级别中有资源。

Resource Dictionaries

XAML 应用程序中的资源词典意味着资源词典保存在单独的文件中。几乎所有 XAML 应用程序中都遵循此做法。在单独的文件中定义资源可能具有以下优点 -

  1. 在资源词典中定义资源与 UI 相关代码之间分离。

  2. 在单独的文件(如 App.xaml)中定义所有资源,将使它们在整个应用程序中可用。

那么,如何在单独的文件中的资源词典中定义我们的资源?很简单,只需通过 Visual Studio 按照以下给定的步骤添加一个新资源词典 -

  1. 在你的解决方案中,添加一个新文件夹并将其命名为 ResourceDictionaries

  2. 右键单击此文件夹,从“添加”子菜单项中选择“资源词典”,并将其命名为 DictionaryWithBrush.xaml

Example

让我们现在采用相同的示例,但在这里,我们将定义应用程序级别的资源词典。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>

编译并执行上述代码时,将产生以下输出 -

resource dictionaries output

当你单击“更改资源”按钮时,矩形将变为红色。

change resource dictionaries

我们建议你执行上述代码并尝试更多资源(例如,背景颜色)。

WPF - Templates

模板描述控件的整体外观和视觉效果。对于每个控件,都有一个与其关联的默认模板,它赋予控件外观。在 WPF 应用程序中,当你希望自定义控件的视觉行为和视觉外观时,可以轻松创建你自己的模板。

可以通过数据绑定来实现逻辑和模板之间的连接。 stylestemplates 之间的主要区别如下 -

  1. 样式只能使用该控件的默认属性来更改控件的外观。

  2. 使用模板,你可以访问控件比样式中更多的部分。你还可以指定控件的现有和新行为。

最常用的两种模板类型 −

  1. Control Template

  2. Data Template

Control Template

控件模板定义控件的视觉外观。所有 UI 元素既具有一定的外观又有行为,例如,按钮具有外观和行为。单击事件或鼠标悬停事件是响应单击和悬停而触发的行为,另外按钮还具有一定的默认外观,可以通过控件模板更改。

Example

我们来看个简单的示例。我们将创建两个按钮(一个带模板,另一个是默认按钮),并用一些属性初始化它们。

<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。

control template

当你将鼠标移动至带有自定义模板的按钮上时,该按钮的颜色会发生变化,如下所示。

mouse over with control template

Data Template

数据模板定义并指定数据集合的外观和结构。它提供了格式化和定义在任何 UI 元素中演示数据的功能。它主要用于与数据相关的项目控件,例如 ComboBox、ListBox 等等。

Example

  1. 我们通过一个简单的示例来了解数据模板的概念。创建一个名为 WPFDataTemplates 的新 WPF 项目。

  2. 在以下 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>

这是 implementation in 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 类的对象数据,这些数据显示在标签和文本框上。

data template

WPF - Styles

NET 框架提供了多种策略,用于个性化和自定义应用程序的外观。通过样式,我们可以灵活地设置对象的某些属性,并在多个对象之间重复使用这些特定设置,以便实现一致的外观。
  1. 在样式中,你只能设置对象的现有属性,例如高度、宽度、字体大小等等。

  2. 只能指定控件的默认行为。

  3. 可以将多个属性添加到单个样式中。

样式用于为一组控件提供统一的外观。隐式样式用于对给定类型的控件的所有控件应用外观,并简化应用程序。设想有三个按钮,它们都必须看起来相同,宽度和高度相同,字体大小相同,前景色相同,等等。我们可以在按钮元素上设置所有这些属性,并且对于所有按钮来说,这仍然完全是可以的。查看以下图表。

styles

但在实际应用程序中,你通常会有更多需要看起来完全相同的控件。当然不只是按钮,你通常会希望你的文本块、文本框和组合框等在整个应用程序中看起来相同。当然,一定有更好的方法来实现这一点,被称为 styling 。可以将样式视为将一组属性值应用到多个元素的便捷方式。查看以下图表。

styles is defined

Example

我们通过一个简单的示例来了解这个概念。首先创建一个新的 WPF 项目。

  1. 将三个按钮从工具箱拖拽到设计窗口。

  2. 以下 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>

当您查看上面的代码时,您会看到所有按钮的高度、宽度、前景色、字体大小和边距属性均相同。现在,编译并执行上述代码后,将显示以下窗口。

output of three buttons

现在,我们来看同一个示例,但这一次,我们将使用 style

<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 标记扩展设置样式属性,将样式分配给具有唯一键的每个按钮。

当您编译并执行上述代码时,它将显示以下窗口(相同的输出)。

output of three buttons

这样做的好处显而易见,我们可以在其作用域内的任何地方重用该样式;如果需要更改它,我们只需在样式定义中更改它一次,而不是在每个元素上更改。

样式定义的级别会立即限制该样式的作用域。因此,作用域(即您可以在其中使用样式的位置)取决于您定义样式的位置。样式可以在以下级别上定义:

Sr.No

Levels & Description

1

Control Level 在控件级别定义样式只能应用于该特定控件。以下是控件级别的示例,其中按钮和 TextBlock 具有自己的样式。

2

Layout Level 在任何布局级别上定义样式将使其仅可由该布局及其子元素访问。

3

Window Level 在窗口级别上定义样式可以使其在该窗口上的所有元素都可以访问。

4

Application Level 在应用程序级别上定义样式可以使其在整个应用程序中都可以访问。让我们举同样的例子,但在这里,我们将样式放在 app.xaml 文件中,以使其在整个应用程序中都可以访问。

WPF - Triggers

触发器基本上使您可以根据属性的值更改属性值或执行操作。因此,它允许您动态更改控件的外观和/或行为,而无需创建新控件。

触发器用于在满足某些条件时更改任何给定属性的值。触发器通常在样式中或文档根部中定义,并应用于该特定控件。有三种类型的触发器:

  1. Property Triggers

  2. Data Triggers

  3. Event Triggers

Property Triggers

在属性触发器中,当一个属性发生变化时,它将立即或以动画形式更改另一个属性。例如,您可以使用属性触发器来更改按钮外观,当鼠标悬停在按钮上时。

以下示例代码展示了如何在鼠标悬停在按钮上时更改按钮的前景色。

<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>

当你编译并执行以上代码时,它将产生以下窗口:

property triggers

当鼠标悬停在按钮上时,其前景色将变为绿色。

color change in trigger

Data Triggers

数据触发器在绑定数据满足某些条件时执行某些操作。让我们来看一下以下 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>

编译并执行上述代码时,将产生以下输出 -

data trigger

当您勾选复选框时,文本块将将其前景色更改为红色。

data trigger color change

Event Triggers

事件触发器在触发特定事件时执行某些操作。它通常用于在控件上完成某些动画,例如 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>

当你编译并执行以上代码时,它将产生以下窗口:

event trigger

点击此按钮后,您将观察到它将开始按两个维度展开。

click button in event trigger

我们建议您编译并执行以上示例,并且将触发器应用于其他属性。

WPF - Debugging

这是一种识别并修复代码中的错误或缺陷而且它们不会按照您预期的那样工作的系统机制。调试子系统紧密耦合的复杂应用程序不容易,因为在一个子系统中修复错误可能会在另一个子系统中创建错误。

Debugging in C

在 WPF 应用程序中,程序员处理两种语言例如 C# 和 XAML。如果您熟悉在任何过程语言(例如 C# 或 C/C++)中的调试并且您还知道中断点的用法,那么您可以轻松调试应用程序的 C# 部分。

我们通过一个简单的示例来演示如何调试 C# 代码。创建新的 WPF 项目,项目名称为 WPFDebuggingDemo 。从工具箱中拖拽四个标签、三个文本框,以及一个按钮。查看以下 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.");
         }
      }
   }
}

当您编译并执行以上代码时,它将生成以下窗口。现在在文本框中输入值并按下 Total 按钮。您将在所有文本框中输入值的总和后获得总值。

debugging

如果您尝试输入除实际值之外的值,则上述应用程序将崩溃。为了找到并解决问题(为什么它崩溃),您可以在按钮点击事件中插入中断点。

我们如下一文所示在项 1 中写入“abc”。

write in item1

点击 Total 按钮后,您将看到程序在中断点处停止

program is crashing

现在将光标移动到 textbox1.Text,您将看到程序正尝试将 abc 值与其他值相加,这就是程序崩溃的原因。

Debugging in XAML

如果您希望在 XAML 中进行同样的调试,您会惊讶地知道至今仍无法像调试任何其他过程语言代码一样调试 XAML 代码。当您在 XAML 代码中听到术语“调试”时,它意味着尝试并找到错误。

  1. In data binding, your data doesn’t show up on screen and you don’t know why

  2. Or an issue is related to complex layouts.

  3. Or an alignment issue or issues in margin color, overlays, etc. with some extensive templates like ListBox and combo box.

Debugging an XAML program is something you typically do to check if your bindings work; and if it is not working, then to check what’s wrong. Unfortunately setting breakpoints in XAML bindings isn’t possible except in Silverlight, but we can use the Output window to check for data binding errors. Let’s take a look at the following XAML code to find the error in data binding.

<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 properties of two text blocks are set to “Name” and “Title” statically, while other two text blocks Text properties are bind to “FirstName” and “Title” but class variables are Name and Title in Employee class which is shown below.

We have intentionally written an incorrect variable name so as to understand where can we find this type of a mistake when the desired output is not shown.

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,但是 name 没有绑定。

debugging in xaml

为了检查 name 发生了什么,让我们来看看生成了很多日志的输出窗口。

找到错误很容易,只需搜索错误,您就会找到以下错误,“BindingExpression 路径错误:在 'object' 'Employe' 上未找到 'FirstName' 属性”。

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 时,您会看到所需的输出。

UI Debugging Tools for XAML

引入了 UI 调试工具,在 Visual Studio 2015 中用 XAML 在运行时检查 XAML 代码。在这些工具的帮助下,XAML 代码以运行的 WPF 应用程序的视觉树的形式呈现,还有树中的不同 UI 元素属性。要启用这些工具,请按照以下步骤操作。

  1. 转到“工具”菜单,然后从“工具”菜单中选择“选项”。

  2. 这将打开以下对话框。

debugging tools
  1. 转到左侧 Debugging 项下的常规选项。

  2. 勾选突出显示的选项,即,“为 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>

当您执行应用程序时,它将显示 Live Visual Tree,其中所有元素都显示在树中。

live visual tree

此 Live Visual Tree 显示了完整的布局结构,以了解 UI 元素位于何处。但此选项仅在 Visual Studio 2015 中可用。如果您使用的是较旧版本的 Visual Studio,则无法使用此工具,但是,还有另一个可以与 Visual Studio 集成的工具,如 Visual Studio 的 XAML Spy。您可以从 xamlspy 下载它。

WPF - Custom Controls

WPF 应用程序允许创建自定义控件,这使得创建功能丰富且可定制的控件非常容易。当 Microsoft 提供的所有内置控件都不满足您的条件或您不想为第三方控件付费时,就会使用自定义控件。

在本章中,您将学习如何创建自定义控件。在我们开始了解自定义控件之前,我们先快速了解一下用户控件。

User Control

用户控件提供了一种将不同的内置控件集合并打包到可重复使用的 XAML 中的方法。用户控件用于以下场景中:

  1. 如果控件包含现有控件,即,您可以创建多个已有控件的单个控件。

  2. 如果控件不需要对主题的支持。用户控件不支持复杂的定制、控件模板和难以设置样式。

  3. 如果开发人员喜欢使用代码隐藏模型编写控件,其中一个视图然后有一个直接的代码隐藏部分用于事件处理程序。

  4. 在不同应用程序中不会共享控件。

Example

让我们来看一个用户控件示例,并按照以下步骤操作。

  1. 创建一个新的 WPF 项目,然后右键单击解决方案并选择添加 > 新建项…​

user controls
  1. 将打开以下窗口。现在选择 User Control (WPF) 并将其命名为 MyUserControl。

new item in user controls
  1. 单击添加按钮,您将看到在解决方案中添加了两个新文件(MyUserControl.xaml 和 MyUserControl.cs)。

以下是 MyUserControl.xaml 文件中使用一些属性创建按钮和文本框的 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>

当您编译并执行上述代码时,它将生成以下窗口。

output of user control

单击“单击我”按钮后,您将注意到文本框中的文本已更新。

button clicked in user control

Custom Controls

自定义控件是一个类,它提供自己的样式和模板,这些通常在 Generic.xaml 中定义。自定义控件用于以下场景:

  1. 如果控件不存在,您必须从头开始创建它。

  2. 如果您希望通过添加额外属性或额外功能来扩展或向现有控件添加功能,以符合您的特定场景。

  3. 如果您的控件需要支持主题和样式。

  4. 如果希望在不同应用程序中共享控件。

Example

让我们通过一个示例来了解自定义控件的工作原理。创建一个新的 WPF 项目,然后右键单击解决方案并选择添加 > 新建项…​

custom controls

它将打开以下窗口。现在选择 Custom Control (WPF) 并将其命名为 MyCustomControl

new item in custom controls

单击添加按钮,您将看到在解决方案中添加了两个新文件(Themes/Generic.xaml 和 MyCustomControl.cs)。

以下是 Generic.xaml 文件中针对自定义控件设置样式的 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 中的实现,用来添加自定义控件和一个文本块。

<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>

编译并执行上述代码后,它将生成以下窗口,该窗口带有一个自定义控件,该控件是一个自定义按钮。

output of custom controls

点击自定义按钮后,您会看到文本块中的文本被更新了。

customized button

WPF - Exception Handling

异常是在程序执行期间遇到的任何错误条件或意外行为。异常可以因多种原因而引发,其中一些原因如下:

  1. 代码中的故障或在您调用的代码中(例如共享库),

  2. Unavailable operating system resources,

  3. 公共语言运行时遇到的意外情况(例如无法验证的代码)

Syntax

异常有能力将程序的流程从一部分转移到另一部分。在 .NET 框架中,异常处理有以下四个关键字:

  1. try — 在此块中,程序识别引发某个异常的特定条件。

  2. catch — catch 关键字表示捕获异常。 try 块后跟一个或多个 catch 块,以在程序中您希望处理问题的位置用异常处理程序来捕获异常。

  3. finally − 无论是否抛出异常,finally 块都用于执行给定的语句组。例如,如果你打开了一个文件,那么无论是否引发异常,都必须关闭它。

  4. 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 语句。

Hierarchy

NET 框架中几乎所有的异常类直接或间接地派生自 Exception 类。派生自 Exception 类最重要的异常类有:
  1. ApplicationException class — 它支持由程序生成的异常。当开发人员想要定义异常时,类应该派生自此类。

  2. SystemException class — 它是所有预定义的运行时系统异常的基类。以下层次结构显示了运行时提供的标准异常。

hierarchy

下表列出了运行时提供的标准异常和您应该创建派生类的条件。

Exception type

Base type

Description

Exception

Object

所有异常的基类。

SystemException

Exception

所有运行时生成错误的基类。

IndexOutOfRangeException

SystemException

当数组索引不当时,仅由运行时抛出。

NullReferenceException

SystemException

只有在引用 null 对象时才由运行时抛出。

AccessViolationException

SystemException

只有在访问无效内存时才由运行时抛出。

InvalidOperationException

SystemException

在无效状态下,由方法抛出。

ArgumentException

SystemException

所有参数异常的基类。

ArgumentNullException

ArgumentException

在不允许参数为 null 的情况下由方法抛出。

ArgumentOutOfRangeException

ArgumentException

在验证参数位于给定范围内的情况下由方法抛出。

ExternalException

SystemException

适用于运行时外部环境中发生的或针对该环境的异常的基类。

SEHException

ExternalException

封装 Win32 结构化异常处理信息的异常。

Example

让我们用一个简单的示例更深入地理解这个概念。首先创建一个带有名称 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();
            }
         }
      }
   }
}

当你编译并执行以上代码时,它将在其中一个文本框内显示一个文本的窗口。

exceptional handling output

当抛出一个异常或手动抛出一个异常时(如以下代码中所示),它会显示一个带错误的消息框。

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();
            }
         }
      }
   }
}

当在执行以上代码时抛出一个异常时,它将显示以下消息。

exception message

我们建议你执行以上的代码并体验其功能。

WPF - Localization

本地化是根据应用程序支持的特定区域文化将应用程序资源翻译成本地化版本。

当你的应用程序仅开发了一种语言时,那么你正在限制客户的数量和业务的规模。如果你想增加你的客户群,那么你的产品必须可以供全球受众访问并能够为其服务,由此你的业务也会随之增加。对你的产品进行经济高效的 localization 是接触更多客户的最佳且最经济有效的方法之一。

在 WPF 中,可以通过 resx 文件(这是本地化的最简单解决方案)轻松创建可本地化的应用程序。让我们通过一个简单的示例来理解它是如何工作的 −

  1. 创建一个带有名称 WPFLocalization 的新 WPF 项目。

  2. 在 Solution Explorer 中,你将在 Properties 文件夹下看到 Resources.resx 文件。

wpf localization
  1. 将访问修饰符从 internal 更改为 public,以便在 XAML 文件中可以访问。

changes in wpf localization 1
  1. 现在添加我们将用在应用程序中的以下字符串的名称和值。

changes in wpf localization 2
  1. 制作 Resources.resx 文件的两个副本,并将其命名为 Resources.en.resx 和 Resources.ru-RU.resx。这些是特定于语言和国家/地区名称的命名约定,且可以在国家语言支持 (NLS) API 参考 ( https://msdn.microsoft.com/en-us/goglobal/bb896001.aspx ) 页面中找到。

  2. 将 Resources.ru-RU.resx 中的值更改为俄语单词,如下所示。

changes in wpf localization 3
  1. 让我们转到设计窗口并拖放三个文本框、三个标签和三个按钮。

  2. 在 XAML 文件中,首先添加 name space 声明,以使用本地化资源 xmlns:p = "clr-namespace:WPFLocalization.Properties"

  3. 如以下所示,设置所有控件的属性。在此示例中,我们不会在 XAML 文件中对标签、按钮以及窗口的标题的内容使用硬编码字符串。我们将使用在 *.resx 文件中定义的字符串。例如,对于窗口的标题,我们使用在 *.resx 文件中定义的 Title 字符串,类似于 “Title = "{x:Static p:Resources.Title}"”

  4. 以下是创建控件并使用不同属性对其进行初始化的 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>
  1. 编译并执行上述代码后,你将看到包含不同控件的以下窗口。

localization example
  1. 默认情况下,程序使用默认的 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");
      }
   }
}

运行应用程序后,你将看到所有俄语文本。

run application

我们建议你执行上述代码并为其他区域性创建 resx 文件。

WPF - Interaction

在 WPF 中,交互显示的是视图中控件之间的交互方式。最常见的交互有两种类型 −

  1. Behaviors

  2. Drag and Drop

Behaviors

行为是随 Expression Blend 3 引入的,它可以将一些功能封装到一个可重用的组件中。为了添加其他行为,你可以将这些组件附加到控件。行为为轻松设计复杂用户交互提供了更大的灵活性。

让我们来看一个简单的示例,其中 ControlStoryBoardAction 行为附加到控件。

  1. 使用 WPFBehavior 名称创建新的 WPF 项目。

  2. 以下 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>

编译并执行上述代码后,它将生成以下包含椭圆和两个按钮的窗口。

interaction output1

当您按下播放按钮时,它会从左向右运动,然后返回到其原点。停止按钮将停止椭圆的运动。

interaction output2

Drag and Drop

用户界面上的拖放功能可以显着提高应用程序的效率和生产力。由于人们认为实施起来困难,因此很少有应用程序使用拖放功能。在一定程度上,处理拖放功能确实有难度,但在 WPF 中,您可以轻松实现这一点。

让我们通过一个简单的示例来了解其工作原理。我们将创建一个应用程序,其中您可以从一个矩形将颜色拖放到另一个矩形。

  1. 使用名称 WPFDragAndDrop 创建一个新的 WPF 项目。

  2. 将五个矩形拖放到设计窗口并设置属性,如下面的 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>
  1. 第一个矩形是目标矩形,因此,用户可以将颜色从其他矩形拖放到目标矩形。

  2. 下面给出了拖放 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;
      }
   }
}

运行您的应用程序时,它将产生以下窗口。

drag and drop output1

如果您将颜色从右侧矩形拖放到左侧的大矩形,您会立即看到其影响。

让我们将右侧的第四个拖过来。

drag and drop output2

您会看到目标矩形的颜色已经改变。我们建议您执行上面的代码并尝试其功能。

WPF - 2D Graphics

WPF 提供了广泛的 2D 图形,可以根据您的应用程序要求进行增强。WPF 支持用于绘制图形内容的 Drawing 和 Shape 对象。

Shapes and Drawing

  1. Shape 类派生自 FrameworkElement 类,Shape 对象可以在面板和大多数控件内使用。

  2. WPF 提供了派生自 Shape 类的基本形状对象,例如 Ellipse、Line、Path、Polygon、Polyline 和 Rectangle。

  3. 另一方面,Drawing 对象不派生自 FrameworkElement 类,并提供了一个更轻量级的实现。

  4. 与 Shape 对象相比,Drawing 对象更为简单。它们的性能特性也更好。

Example

让我们通过一个简单的示例来了解如何使用不同的形状对象。

  1. 使用名称 WPF2DGraphics 创建一个新的 WPF 项目。

  2. 以下代码创建了不同类型的形状。

<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>

当您编译并执行上述代码时,它将产生椭圆、直线、圆弧和多边形。

shapes

Example

我们来看另一个演示如何用绘图绘制面积的示例。

  1. 使用名称 WPF2DGraphics1 创建一个新的 WP 项目。

  2. 以下 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>

当您运行您的应用程序时,它将生成以下输出 −

shapes example

我们建议您执行上述代码并尝试更多的 2D 形状和绘图。

WPF - 3D Graphics

Windows演示文稿框架 (WPF) 提供了一项功能,按照您的应用程序要求绘制、转换和动画处理 3D 图形。它不支持完整的 3D 游戏开发,但是在某种程度上,您可以创建 3D 图形。

通过组合 2D 和 3D 图形,您还能够创建丰富的控件,提供复杂的数据插图或增强应用程序界面用户体验。Viewport3D 元素承载 3D 模型进入到我们的 WPF 应用程序中。

Example

我们来举一个简单的示例,了解如何使用 3D 图形。

  1. 使用名称 WPF3DGraphics 创建一个新的 WPF 项目。

  2. 以下 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 graphics

Example

我们来看另一个演示 3D 对象的示例。

  1. 使用名称 WPF3DGraphics1 创建一个新的 WPF 项目。

  2. 以下代码创建一个 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 output1

当您滑动滑动条时,您窗口上的对象也将旋转。

3d output2

我们建议您执行上述代码并尝试更多的 3D 几何图形。

WPF - Multimedia

WPF 应用程序使用 MediaElement 支持视频和音频。它允许您将音频和视频集成到应用程序中。MediaElement 类的工作方式类似于 Image 类。您只需将它指向媒体,它就会呈现媒体。主要区别在于它将是一个动态图像,但是,如果您将它指向仅包含音频且不包含视频(比如 MP3)的文件,它将播放该文件而不会在屏幕上显示任何内容。

WPF 支持所有类型的视频/音频格式,具体取决于计算机配置。如果媒体文件在媒体播放器中播放,它在同一台计算机上的 WPF 中也可以播放。

Example

让我们用一个示例,来了解如何在应用程序中集成多媒体。

  1. 使用名称 WPFMultimedia 创建一个新的 WPF 项目。

  2. 下列 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# 实现不同按钮的点击事件。

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";
         }
      }
   }
}

当您编译并执行以上代码时,它将生成以下窗口。你可以播放视频,并使用三个按钮控制播放。

multimedia

借助这些按钮,你可以暂停、静音和播放视频。

Speech Synthesizer

WPF 具有将文本转换为语音的功能。此 API 包含在 System.Speech 命名空间中。 SpeechSynthesizer 类将文本转换为语音。

Example

我们来看一个简单的示例。

  1. 使用名称 WPFTextToSpeech 创建一个新的 WPF 项目。

  2. 我们将需要 System.Speech 程序集,来将它用作 SpeechSynthesizer 类工作的引用。

  3. 右键单击引用并选择添加引用。

wpf text to speech
  1. “引用管理器”对话框将打开。现在选中 System.Speech 复选框

reference manager dialog
  1. 单击确定按钮。你可以在引用中看到 System.Speech 程序集。

wpf text to speech2
  1. 现在从工具箱中将一个按钮和一个文本框拖到设计窗口中。

  2. 下列 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>
  1. 这是使用 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,然后单击“说话”按钮。

multimedia output1

它会产生“Hello World”的语音。如果您没有在文本框中键入任何内容,则会闪烁以下消息。

multimedia output2

我们建议您执行上述示例。