Pro C# 7 with .NET and .NET Core - Andrew Troelsen et al.
目录
- Pro C# 7 with .NET and .NET Core - Andrew Troelsen et al.
- 目录
- 第I部分 介绍C#和.NET平台
- 第II部分 核心C#程式设计
- 第III部分 用C#进行面向对象的编程
- 第IV部分 高级C#编程
- 第V部分 .NET程序集编程
- 第VI部分 .NET基类库简介
- 第VII部分 Windows Presentation Foundation
- 第VIII部分 ASP.NET
- 第IX部分 .NET Core
第I部分 介绍C#和.NET平台
第1章 .NET的哲学
Microsoft的.NET平台(和相关的C#编程语言)于2002年左右正式引入,并迅速成为现代软件开发的中流砥柱。如本书的简介中所述,本文的目的是双重的。首先要为您提供C#语法和语义的深入详细的检查。业务的第二个(同样重要的)顺序是说明许多 .NET API的使用,包括使用ADO .NET和实体框架(EF)进行数据库访问,使用Windows Presentation Foundation(WPF)进行用户界面开发,使用 Windows Communication Foundation(WCF),以及使用ASP .NET MVC的Web服务和网站开发。本书的最后部分介绍了 .NET家族的最新成员 .NET Core,它是.NET平台的跨平台版本。正如他们所说,千里之行始于一步。因此,我欢迎您进入第一章。
第一章的重点是为本书的其余部分奠定概念基础。在这里,您将找到许多与 .NET相关的主题的高级讨论,例如程序集,公共中间语言(CIL)和即时(JIT)编译。除了预览C#编程语言的一些关键字之外,您还将了解 .NET Framework各个方面之间的关系,例如公共语言运行时(CLR),公共类型系统(CTS)和公共 语言规范(CLS)。
本章还为您概述了.NET基类库(有时缩写为BCL)提供的功能。 在这里,您将获得.NET平台的语言无关和平台无关性质的概述。 正如您希望的那样,在本文的其余部分中,将进一步详细探讨其中的许多主题。
概要
本章的重点是提出本书其余部分所需的概念框架。首先,我研究了 .NET之前的技术中存在的许多局限性和复杂性,然后概述了.NET和C#如何尝试简化当前的事务状态。
.NET基本上可以归结为运行时执行引擎(mscoree.dll)和基类库(mscorlib.dll及其关联)。公共语言运行时能够托管遵守托管代码规则的任何 .NET二进制文件(即程序集)。如您所见,程序集包含CIL指令(除了类型元数据和程序集清单之外),这些指令可以使用即时编译器编译为特定于平台的指令。此外,您还探讨了公共语言规范和公共类型系统的作用。 然后检查ildasm.exe对象浏览工具。
在下一章中,您将浏览构建C#编程项目时可以使用的常见集成开发环境。您会很高兴地知道,在本书中,您将使用完全免费的(且功能丰富的)IDE,因此您可以在不花钱的情况下开始探索.NET领域。
第2章 构建C#应用程序
作为C#程序员,您可以从众多工具中进行选择以构建.NET应用程序。您选择的一种或多种工具将主要基于三个因素:与之相关的成本,用于开发软件的操作系统以及目标平台。本章的重点是概述支持C#语言的最常见的集成开发环境(IDE)。要了解,本章不会介绍每个IDE的每个细节。在阅读本文时,它将为您提供足够的信息来选择您的编程环境,并为您提供基础。
本章的第一部分将研究Microsoft提供的一组IDE,这些IDE可以在Windows操作系统(7、8.x和10)上开发.NET应用程序。就像您将看到的那样,这些IDE中的一些只能用于构建以Windows为中心的应用程序,而其他的则支持为其他操作系统和设备(例如macOS,Linux或Android)构建C#应用程序。然后,本章的后半部分将介绍一些可以在非Windows操作系统上运行的IDE。这使开发人员可以使用Apple计算机以及Linux发行版来构建C#程序。
注意: 本章将为您提供大量IDE的概述。但是,本书将假定您正在使用(完全免费的)Visual Studio 2017社区IDE。如果要在其他OS(macOS或Linux)上构建应用程序,本章将指导您正确的方向。但是,您的IDE将不同于本文中的各种屏幕截图。
在非Windows操作系统上构建.NET应用程序
在非Windows操作系统上有几种用于构建.NET应用程序的选项。 除了Xamarin Studio,还有Mac专用的Visual Studio和Visual Studio Code(也可在Linux上运行)。可以使用这些开发环境构建的应用程序类型仅限于使用 .NET Core(适用于Mac的Visual Studio Code和Visual Studio)或针对移动应用(适用于Mac的Visual Studio,Xamarin Studio)进行开发的应用程序。
这就是本书中非Windows开发工具的全部内容。但是请放心,Microsoft正在拥抱所有开发人员,而不仅仅是拥有基于Windows的计算机的开发人员。
概要
如您所见,您可以使用许多新玩具!本章的重点是向您介绍C#程序员在开发过程中可能利用的主要编程工具。如前所述,如果您只对在Windows开发计算机上构建.NET应用程序感兴趣,那么最好的选择是下载Visual Studio Community。也如前所述,本书的此版本将继续使用此特定的IDE。 因此,即将出现的屏幕截图,菜单选项和视觉设计器都将假定您正在使用Visual Studio社区。
如果要在非Windows操作系统上构建 .NET Core应用程序或跨平台移动应用程序,则Mac的Visual Studio,Visual Studio Code或Xamarin Studio将是您的最佳选择。
第II部分 核心C#程式设计
第3章 核心C#编程构造,第一部分
本章通过介绍一些在探索 .NET Framework时必须熟悉的小规模独立主题,开始对C#编程语言的正式研究。首先要做的是了解如何构建程序的应用程序对象,并检查可执行程序的入口点的组成:Main()方法。接下来,您将研究基本的C#数据类型(及其在System名称空间中的等效类型),包括检查System.String和System.Text.StringBuilder类。
在了解基本的.NET数据类型的详细信息之后,您将研究许多数据类型转换技术,包括缩小操作,扩大操作以及使用checked和unchecked关键字。
本章还将研究C#var关键字的作用,它允许您隐式定义局部变量。正如您将在本书的后面看到的那样,在使用LINQ技术集时,隐式类型是非常有用的,即使有时不是强制性的。您将通过快速检查C#关键字和运算符来结束本章,这些关键字和运算符使您可以使用各种循环和决策结构来控制应用程序的流程。
概要
本章的目的是向您介绍C#编程语言的许多核心方面。您已在可能感兴趣的任何应用程序中检查了通用结构。在检查了应用程序对象的角色之后,您了解到每个C#可执行程序都必须具有定义Main()方法的类型,该方法用作程序的入口点。在Main()的范围内,通常可以创建任意数量的对象,这些对象可以协同工作,为应用程序注入生命。
接下来,您深入研究了C#内置数据类型的细节,并了解了每个数据类型关键字(例如int)实际上是System名称空间(System.Int32,在这种情况下)。鉴于此,每种C#数据类型都有许多内置成员。同样,您还了解了扩大和缩小的作用以及checked和unchecked关键字的作用。
本章最后介绍了使用var关键字的隐式键入的作用。如上所述,隐式类型最有用的地方是使用LINQ编程模型时。最后,您快速检查了C#支持的各种迭代和决策构造。
现在您已经了解了一些基本的基本知识,下一章(第4章)将完成对核心语言功能的检查。之后,您将准备好从第5章开始研究C#的面向对象功能。
第4章 核心C#编程构造,第二部分
本章从第3章开始讨论,并完成了对C#编程语言核心方面的研究。您将开始研究使用C#语法处理数组背后的细节,并了解相关System.Array类类型中包含的功能。
接下来,您将检查有关C#方法构造的各种细节,探索out,ref和params关键字。在此过程中,您还将检查可选参数和命名参数的作用。我将通过方法重载来结束有关方法的讨论。
接下来,本章讨论枚举和结构类型的构造,包括相当详细地检查值类型和引用类型之间的区别。本章通过检查可空数据类型和相关运算符的作用来结束。
在完成本章之后,从第5章开始,您将处在学习C#的面向对象功能的完美位置。
概要
本章从检查数组开始。然后,我们讨论了允许您构建自定义方法的C#关键字。回想一下,默认情况下参数是通过值传递的; 但是,如果您用ref或out标记参数,则可以按引用传递参数。您还了解了可选参数或命名参数的作用,以及如何定义和调用采用参数数组的方法。
在研究了方法重载的主题之后,本章的大部分内容研究了一些有关如何在C#中定义枚举和结构并在.NET基类库中表示的细节。在此过程中,您检查了一些有关值类型和引用类型的详细信息,包括它们在将其作为参数传递给方法时如何响应以及如何与可为空的数据类型和可能为空的变量(例如,引用型变量和可为空的值)进行交互 类型变量)使用? 和?? 操作员。
本章的最后部分介绍了C#中期待已久的功能,元组。在了解它们是什么以及它们如何工作之后,就可以使用它们从方法中返回多个值以及解构自定义类型。
第III部分 用C#进行面向对象的编程
第5章 了解封装
在第3章和第4章中,您研究了许多核心语法结构,这些结构在您可能正在开发的任何.NET应用程序中都很常见。在这里,您将开始研究C#的面向对象功能。首要任务是检查构建支持任何数量构造函数的定义明确的类类型的过程。在理解了定义类和分配对象的基础之后,本章的其余部分将研究封装的作用。在此过程中,您将学习如何定义类属性,并了解static关键字,对象初始化语法,只读字段,常量数据和部分类的详细信息。
概要
本章的重点是向您介绍C#类类型的角色。如您所见,类可以采用任意数量的构造函数,这些构造函数使对象用户可以在创建对象时建立对象的状态。本章还说明了几种类设计技术(和相关的关键字)。回想一下,this关键字可用于获取对当前对象的访问权限,static关键字允许您定义在类(非对象)级别绑定的字段和成员,而const关键字(和readonly修饰符)使您能够 定义在初始分配后永远不会改变的数据点。
本章的大部分内容将深入探讨OOP的第一个支柱:封装。您了解了C#的访问修饰符以及类型属性,对象初始化语法和局部类的作用。有了它,您现在可以转到下一章,在该章中,您将学习使用继承和多态性构建一系列相关类。
第6章 了解继承和多态
第5章研究了OOP的第一个支柱:封装。那时,您学习了如何使用构造函数和各种成员(字段,属性,方法,常量和只读字段)构建一个定义明确的类类型。本章将重点介绍OOP的其余两个支柱:继承和多态性。
首先,您将学习如何使用继承构建相关类的族。正如您将看到的,这种形式的代码重用使您可以在父类中定义通用功能,而子类可以利用这些通用功能,也可以对其进行更改。在此过程中,您将学习如何使用虚拟成员和抽象成员将多态接口建立到类层次结构中,以及显式转换的作用。
本章将通过检查.NET基类库:System.Object中最终父类的作用来结束本章。
概要
本章探讨了继承和多态性的作用和细节。在这些页面上,向您介绍了许多新关键字和令牌,以支持上述每种技术。例如,回想一下冒号标记用于建立给定类型的父类。父类型能够定义任何数量的虚拟和/或抽象成员以建立多态接口。派生类型使用override关键字覆盖此类成员。
除了构建大量的类层次结构之外,本章还研究了如何在基类型和派生类型之间进行显式转换,并通过深入研究.NET基类库System.Object中的宇宙父类的细节来进行包装。
第7章 了解结构化异常处理
在本章中,您将学习如何通过使用结构化异常处理来处理C#代码中的运行时异常。您不仅会检查可让您处理此类事件的C#关键字(Try, catch, throw, finally, when),而且您还将了解应用程序级异常与系统级异常之间的区别以及 System.Exception基类的角色。该讨论将引发构建自定义异常的主题,最后,快速浏览一下Visual Studio的一些以异常为中心的调试工具。
概要
在本章中,您研究了结构化异常处理的作用。当方法需要将错误对象发送给调用方时,它将通过C#throw关键字分配,配置并抛出特定的System.Exception派生类型。调用者可以使用C#catch关键字和可选的finally范围来处理任何可能的传入异常。从C#6.0开始,添加了使用可选的when关键字创建异常过滤器的功能,并且C#7扩展了可以引发异常的位置。
创建自己的自定义异常时,最终将创建一个派生自System.ApplicationException的类类型,该类类型表示从当前正在执行的应用程序引发的异常。相反,从System.SystemException派生的错误对象表示CLR引发的严重(和致命)错误。最后但并非最不重要的一点是,本章说明了Visual Studio中的各种工具,这些工具可用于创建自定义异常(根据.NET最佳实践)以及调试异常。
第8章 使用接口
本章通过研究基于接口的编程主题,以当前对面向对象开发的理解为基础。在这里,您将学习如何定义和实现接口,并了解支持多种行为的建筑类型的好处。在此过程中,您还将研究许多相关主题,例如获取接口引用,显式接口实现以及接口层次结构的构建。您还将检查.NET基类库中定义的许多标准接口。正如您将看到的,您的自定义类和结构可以自由地实现这些预定义的接口,以支持许多有用的行为,例如对象克隆,对象枚举和对象排序。
概要
可以将接口定义为抽象成员的命名集合。因为接口不提供任何实现细节,所以通常将接口视为给定类型可能支持的行为。当两个或多个类实现相同的接口时,即使在唯一的类层次结构中定义了类型,也可以以相同的方式对待每个类型(基于接口的多态性)。
C#提供了interface关键字,以允许您定义新接口。如您所见,类型可以使用逗号分隔的列表来支持尽可能多的接口。此外,允许构建从多个基本接口派生的接口。
.NET库除了构建自定义接口外,还定义了许多标准(即框架提供的)接口。如您所见,您可以自由构建实现这些预定义接口的自定义类型,以获取许多理想的特性,例如克隆,排序和枚举。
第IV部分 高级C#编程
第9章 集合和泛型
您使用.NET平台创建的任何应用程序都将需要解决维护和操作内存中一组数据点的问题。这些数据点可以来自任何位置,包括关系数据库,本地文本文件,XML文档,Web服务调用或用户提供的输入。
.NET平台首次发布时,程序员经常使用System的类。集合名称空间,用于存储应用程序内使用的数据并与之交互。在 .NET 2.0中,增强了C#编程语言以支持称为泛型的功能。并进行了更改,在基类库System.Collections.Generic中引入了新的命名空间。
本章将概述.NET基类库中的各种集合(通用和非通用)名称空间和类型。如您所见,通用容器通常比非通用容器更受青睐,因为它们通常提供更大的类型安全性和性能优势。学习了如何创建和操作框架中的通用项目后,本章的其余部分将研究如何构建自己的通用方法和通用类型。当您执行此操作时,您将了解约束的作用(以及相应的C#where关键字),这些约束使您可以构建非常类型安全的类。
第10章 委托,事件和Lambda表达式
到目前为止,您开发的大多数应用程序都向Main()添加了各种代码,这些代码以某种方式将请求发送到给定的对象。但是,许多应用程序要求对象能够使用回调机制与创建该对象的实体进行通信。尽管回调机制可以在任何应用程序中使用,但它们对于图形用户界面尤为重要,因为控件(例如按钮)需要在正确的情况下(单击按钮,鼠标进入按钮表面时)调用外部方法。依此类推。
在 .NET平台下,委托类型是在应用程序内定义和响应回调的首选方法。本质上, .NET委托类型是一种类型安全的对象,它“指向”可以在以后调用的方法或方法列表。与传统的C++函数指针不同, .NET委托是具有对多播和异步方法调用的内置支持的类。
在本章中,您将学习如何创建和操作委托类型,然后研究C#event关键字,该关键字简化了使用委托类型的过程。在此过程中,您还将检查C#的几种以委托和事件为中心的语言功能,包括匿名方法和方法组转换。
我通过检查lambda表达式来结束本章。使用C#lambda运算符(=>),可以在需要强类型委托的任何地方指定代码语句块(以及传递给这些代码语句的参数)。就像您将看到的那样,lambda表达式只是变相的匿名方法而已,它为使用委托提供了简化的方法。此外,此相同的操作(自 .NET 4.6起)可用于使用简洁语法实现单语句方法或属性。
第11章 先进的C#语言功能
在本章中,您将通过研究许多更高级的主题来加深对C#编程语言的理解。首先,您将学习如何实现和使用索引器方法。这种C#机制使您可以构建自定义类型,以使用类似于数组的语法访问内部子项。学习了如何建立索引器方法后,您将看到如何重载各种运算符(+,-,<,>等),以及如何为类型创建自定义的显式和隐式转换例程(并且您将了解为什么您可能想要这样做)。
接下来,您将研究在使用以LINQ为中心的API时特别有用的主题(尽管您可以在LINQ的上下文之外使用它们),特别是扩展方法和匿名类型
总结一下,您将学习如何创建“不安全”代码上下文来直接操作非托管指针。虽然在C#应用程序中使用指针确实是很少见的活动,但是了解如何做到这一点在涉及复杂互操作性场景的某些情况下可能会有所帮助。
第12章 LINQ对象
无论使用 .NET平台创建的应用程序类型如何,程序在执行时肯定需要访问某种形式的数据。当然,可以在许多位置找到数据,包括XML文件,关系数据库,内存中的集合和原始数组。从历史上讲,基于所述数据的位置,程序员需要使用不同且不相关的API。最初在 .NET 3.5中引入的语言集成查询(LINQ)技术集提供了一种简洁,对称和强类型化的方式来访问各种数据存储。在本章中,您将专注于LINQ to Objects,从而开始对LINQ的研究。
在深入研究LINQ to Objects之前,本章的第一部分将快速回顾启用LINQ的关键C#编程结构。在学习本章的过程中,您会发现隐式类型的局部变量,对象初始化语法,lambda表达式,扩展方法和匿名类型将非常有用(如果有时不是强制性的)
在回顾了该支持基础结构之后,本章的其余部分将向您介绍LINQ编程模型及其在.NET平台中的作用。在这里,您将学习查询运算符和查询表达式的作用,这使您可以定义查询数据源以产生请求的结果集的语句。在此过程中,您将构建大量LINQ示例,这些示例与数组中包含的数据以及各种集合类型(通用和非通用)进行交互,并理解代表LINQ to Objects API的程序集,名称空间和类型。
注意本章中的信息是本书未来各节和各章的基础,包括并行LINQ(第19章),实体框架(第22章)和实体框架核心(第30章)。
第13章 了解对象生命周期
在本书的这一点上,您已经学到了很多有关如何使用C#构建自定义类类型的知识。 现在,您将看到CLR如何通过垃圾回收管理分配的类实例(又名对象)。C#程序员从不直接从内存中释放托管对象(请记住,C#语言中没有delete关键字)。而是将 .NET对象分配到称为托管堆的内存区域,在将来某个时候它们将被垃圾收集器自动销毁。
在查看了收集过程的核心细节之后,您将学习如何使用System.GC类类型与垃圾收集器进行编程交互(对于大多数,通常不需要这样做 .NET项目)。接下来,您将研究虚拟System.Object。Finalize()方法和IDisposable接口可用于构建以可预测的及时方式释放内部非托管资源的类。
您还将研究 .NET 4.0中引入的垃圾收集器的一些功能,包括后台垃圾收集和使用通用System.Lazy<>类的惰性实例化。在完成本章时,您将对CLR如何管理 .NET对象有深入的了解。
第V部分 .NET程序集编程
第14章 构建和配置类库
在本书的前四部分中,您创建了许多“独立”可执行应用程序,其中所有编程逻辑都打包在一个可执行文件(.exe)中。这些可执行程序集所使用的只不过是主要的.NET类库mscorlib.dll。尽管可能仅使用.NET基类库来构造一些简单的.NET程序,但您(或您的队友)很可能将可重用的编程逻辑隔离到自定义类库(.dll文件)中,从而可以在应用程序之间共享。
在本章中,您将学习将类型打包到自定义代码库中的各种方法。首先,您将学习将类型划分为 .NET名称空间的详细信息。之后,您将检查Visual Studio的类库项目模板,并了解私有程序集和共享程序集之间的区别。
接下来,您将确切地探索 .NET运行时如何解析程序集的位置,并且您将了解全局程序集缓存,XML应用程序配置文件(.config文件),发布者策略程序集和System.Configuration名称空间。
第15章 类型反射,后期绑定和基于属性的编程
如第14章所示,程序集是 .NET Universe中部署的基本单元。使用Visual Studio(和许多其他IDE)的集成对象浏览器,您可以检查项目引用的程序集中的类型。此外,使用ildasm.exe之类的外部工具,您可以查看给定.NET二进制文件的基础CIL代码,类型元数据和程序集清单。除了对.NET程序集进行设计时调查之外,您还可以使用System.Reflection命名空间以编程方式获取相同的信息。 为此,本章的首要任务是定义反射的作用以及 .NET元数据的必要性。
本章的其余部分研究了许多紧密相关的主题,所有这些主题都取决于反射服务。例如,您将学习 .NET客户端如何使用动态加载和后期绑定来激活其不具有编译时知识的类型。您还将学习如何通过使用系统提供的属性和自定义属性将自定义元数据插入.NET程序集。为了使所有这些(看似深奥的)主题都得到透视,本章以演示如何构建可插入可扩展的桌面GUI应用程序中的几个“快照对象”作为结尾。
第16章 动态类型和动态语言运行时
NET 4.0为C#语言引入了一个新的关键字,即dynamic关键字。此关键字允许您将类似脚本的行为合并到类型安全,分号和大括号的强类型世界中。使用这种松散类型,您可以大大简化一些复杂的编码任务,还可以与多种.NET精通的动态语言进行互操作。
在本章中,将向您介绍C#dynamic关键字,并了解如何使用动态语言运行时(DLR)将松散类型的调用映射到正确的内存对象。了解DLR提供的服务后,您将看到使用动态类型来简化如何执行后期方法调用(通过反射服务)并轻松与旧式COM库进行通信的示例。
注意不要将C#dynamic关键字与动态程序集的概念混淆(请参阅第18章)。虽然在构建动态程序集时可以使用dynamic关键字,但它们最终是两个独立的概念。
第17章 进程,AppDomain和对象上下文
在第14和15章中,您研究了CLR为解析引用的外部程序集的位置以及 .NET元数据的作用而采取的步骤。在本章中,您将更深入地了解CLR如何托管程序集的详细信息,并了解进程,应用程序域和对象上下文之间的关系。
简而言之,应用程序域(或简称为AppDomain)是给定进程中的逻辑细分,这些细分承载了一组相关的 .NET程序集。如您所见,AppDomain进一步细分为上下文边界,这些上下文边界用于将志趣相投的 .NET对象进行分组。使用上下文的概念,CLR能够确保适当处理具有特殊运行时要求的对象。
的确,您的许多日常编程任务可能并不涉及直接处理进程,AppDomain或对象上下文,但是在使用许多 .NET API(包括Windows Communication Foundation(WCF))时,了解这些主题很重要,多线程和并行处理以及对象序列化。
第18章 了解CIL和动态程序集的作用
考虑到C#(或类似的托管语言,如Visual Basic)的固有生产力和易用性,在构建全面的 .NET应用程序时,您肯定会使用C#。但是,正如您在第一章中了解到的那样,托管编译器的作用是将 .cs 代码文件转换为CIL代码,类型元数据和程序集清单。事实证明,CIL是一种成熟的 .NET编程语言,具有自己的语法,语义和编译器(ilasm.exe)
在本章中,您将了解 .NET的母语。在这里,您将了解CIL指令,CIL属性和CIL操作码之间的区别。然后,您将了解.NET程序集和各种CIL编程工具的双向工程的作用。然后,本章的其余部分将带您了解使用CIL语法定义名称空间,类型和成员的基础知识。本章将探讨System.Reflection.Emit命名空间的作用,并说明如何在运行时动态构造程序集(使用CIL指令)
当然,很少有程序员会每天需要处理原始的CIL代码。因此,我将通过研究一些原因来开始本章,其中一些原因为什么使您值得花点时间了解这种底层.NET语言的语法和语义。
第VI部分 .NET基类库简介
第19章 多线程,并行和异步编程
没有人喜欢在执行过程中运行缓慢的应用程序。而且,没有人喜欢在应用程序中启动任务(可能通过单击工具栏项来启动),该任务会阻止程序的其他部分尽可能地响应。在 .NET发行之前,构建具有执行多项任务能力的应用程序通常需要编写使用Windows线程API的复杂C++代码。值得庆幸的是,.NET平台为您提供了多种方法来构建可以在唯一的执行路径上执行复杂操作的软件,而痛苦要少得多。
本章从定义“多线程应用程序”的整体性质开始。接下来,您将重新访问 .NET委托类型,以研究其对异步方法调用的内在支持。如您所见,该技术使您可以在辅助执行线程上调用方法,而无需手动创建或配置线程本身。
接下来,将向您介绍 .NET 1.0以来提供的原始线程命名空间,特别是System.Threading。在这里,您将研究多种类型(线程,线程启动等),这些类型使您可以显式创建其他执行线程并同步共享资源,这有助于确保多个线程可以非易失性方式共享数据。
本章其余部分将研究.NET开发人员可以用来构建多线程软件的三种最新技术,特别是任务并行库(TPL),并行LINQ(PLINQ)和新的(自C#6起)内在异步关键字(async并await)。 如您所见,这些功能可以极大地简化您构建响应式多线程软件应用程序的方式。
第20章 文件I/O和对象序列化
创建桌面应用程序时,在用户会话之间保存信息的能力很常见。本章研究了从 .NET Framework的角度来看许多与I/O相关的主题。首先要做的是探索System.IO命名空间中定义的核心类型,并学习如何以编程方式修改计算机的目录和文件结构。下一个任务是探索各种读取和写入基于字符,基于二进制,基于字符串和基于内存的数据存储的方式。
学习了如何使用核心I/O类型操作文件和目录之后,您将研究对象序列化的相关主题。您可以使用对象序列化来持久化对象,并从(或从)任何System.IO.Stream派生类型检索对象的状态。当您想使用各种远程处理技术(例如Windows Communication Foundation)将对象复制到远程计算机时,序列化对象的能力至关重要。但是,序列化本身很有用,并且可能会在您的许多 .NET应用程序中(不管是否分布式)都起作用。
注意:为确保可以运行本章中的每个示例,请以管理权限启动Visual Studio(只需右键单击Visual Studio图标,然后选择“以管理员身份运行”。否则,在访问时可能会遇到运行时安全性异常) 计算机文件系统。
第21章 使用ADO.NET进行数据访问
.NET平台定义了许多命名空间,使您可以与关系数据库系统进行交互。总体而言,这些命名空间称为ADO.NET。在本章中,您将了解ADO.NET的总体作用以及核心类型和名称空间,然后继续讨论ADO.NET数据提供程序这一主题。.NET平台支持众多数据提供程序(均作为 .NET Framework的一部分提供,并且可从第三方来源获得),每个数据提供程序都经过优化以与特定的数据库管理系统(例如Microsoft SQL Server,Oracle和 MySQL)。
了解了各种数据提供者提供的通用功能之后,您将查看数据提供者工厂模式。如您将看到的,使用System.Data.Common命名空间(和相关的App.config文件)中的类型,您可以构建一个代码库,该代码库可以动态地选择和选择基础数据提供程序,而无需重新编译或重新部署应用程序的代码库。
接下来,您将学习如何直接与SQL Server数据库提供程序一起使用,创建和打开连接以检索数据,然后继续进行插入,更新和删除数据,然后研究数据库事务主题。最后,您将使用ADO.NET执行SQL Server的批量复制功能,以将记录列表加载到数据库中。
注意: 本章重点介绍原始ADO.NET。第22章介绍了实体框架(EF),这是Microsoft的对象关系映射(ORM)框架。像实体框架一样,ORM使创建数据访问代码变得更加简单(和快速)。但是他们仍然依靠ADO.NET进行数据访问。在对数据访问问题进行故障排除时,尤其是当它是由框架创建而不是由您编写的时,对ADO.NET的工作原理有扎实的了解至关重要。此外,您会遇到EF无法解决的情况(例如执行SQL批量复制),并且您需要了解ADO.NET才能解决这些问题。
第22章 实体框架6简介
上一章研究了ADO.NET的基础。自.NET平台最初发布以来,ADO.NET使.NET程序员能够使用关系数据(以相对简单的方式)进行处理。但是,Microsoft在 .NET 3.5 Service Pack 1中引入了 ADO.NET API的新组件,称为实体框架(或简称为EF)。
注意: 虽然EF的第一个版本受到广泛批评,但Microsoft的EF团队一直在努力发布新版本。完整的 .NET Framework的EF当前版本为6.1.3(在撰写本文时),该版本包含了较早版本的功能和性能增强。实体框架核心(以前称为EF 7)也可用,并且在第IX部分中与 .NET Core一起进行了介绍。
EF的总体目标是允许您使用直接映射到应用程序中的业务对象(或域对象)的对象模型与关系数据库中的数据进行交互。例如,您可以对称为实体的强类型对象的集合进行操作,而不是将一批数据视为行和列的集合。这些实体本身也是LINQ感知的,您可以使用在第12章中了解到的相同LINQ语法来对它们进行查询。EF运行时引擎代表您将LINQ查询转换为适当的SQL查询。
本章将向您介绍使用实体框架的数据访问。您将学习有关创建域模型,将模型类映射到数据库以及DbContext类的角色的知识。您还将了解导航属性,事务和并发检查。
在完成本章时,您将拥有AutoLotDAL.dll的最终版本。本书其余部分将使用此版本的AutoLotDAL.dll(直到本书稍后使用EF .NET Core对其进行重建)。
注意:使用实体设计器创建实体数据模型XML(EDMX)文件时,所有版本的Entity Framework(不超过EF 6.x)都支持。从4.1版开始,EF使用称为“代码优先”的技术添加了对纯旧CLR对象(POCO)的支持。EF Core将仅支持Code First范例,而放弃所有EDMX支持。由于这个原因(以及EDMX范式的许多其他问题),本章仅使用Code First范式。 Code First名称实际上很糟糕,因为它给人的印象是您无法在现有数据库中使用它 我更喜欢“以代码为中心”一词,但是Microsoft并未征求我的意见!
第23章 Windows Communication Foundation简介
Windows Communication Foundation(WCF)是专门为构建分布式系统的过程而设计的API的名称。与您过去可能使用过的其他特定分布式API(例如,DCOM,.NET远程处理,XML Web服务,消息队列)不同,WCF提供了一个单一,统一且可扩展的编程对象模型,您可以使用该模型与许多对象进行交互。以前多样化的分布式技术。
本章首先确定对WCF的需求,并研究其打算解决的问题。查看WCF提供的服务之后,您将把注意力转向检查代表此编程模型的关键 .NET程序集,名称空间和类型。在本章的其余部分,您将使用各种WCF开发工具来构建多个WCF服务,主机和客户端。
注意: 在本章中,您将编写要求使用管理特权(此外,您必须具有管理特权)启动Visual Studio的代码。要以正确的管理员权限启动Visual Studio,请右键单击Visual Studio图标,然后选择“以管理员身份运行”。
第VII部分 Windows Presentation Foundation
第24章 Windows Presentation Foundation和XAML简介
当.NET平台的1.0版发布时,需要构建图形桌面应用程序的程序员使用了两个API,它们分别名为Windows Forms和GDI+,这些API主要打包在System.Windows.Forms.dll和System.Drawing.dll程序集。尽管Windows Forms / GDI+仍然是用于构建传统桌面GUI的可行API,但是Microsoft从 .NET 3.0版本开始提供了另一种名为Windows Presentation Foundation(WPF)的GUI桌面API。
WPF最初的这一章首先探讨了这个新GUI框架背后的动机,这将帮助您了解Windows Forms / GDI+和WPF编程模型之间的区别。接下来,您将了解几个重要类的角色,包括Application,Window,ContentControl,Control,UIElement和FrameworkElement。
然后,本章将向您介绍名为Extensible Application Markup Language(XAML;发音为“ zammel”)的基于XML的语法。在这里,您将学习XAML的语法和语义(包括附加的属性语法以及类型转换器和标记扩展的作用)
本章最后通过构建第一个WPF应用程序来研究Visual Studio的集成WPF设计器。在这段时间里,您将学习如何拦截键盘和鼠标活动,定义应用程序范围的数据以及执行其他常见的WPF任务。
WPF背后的动机
多年来,Microsoft创建了许多图形用户界面工具包(原始C/C++/Windows API开发,VB6,MFC等)来构建桌面可执行文件。 这些API均提供了一个代码库来表示GUI应用程序的基本方面,包括主窗口,对话框,控件,菜单系统和其他基本必需品。 随着.NET平台的最初发行,Windows Forms API凭借其简单而强大的对象模型迅速成为UI开发的首选模型。
尽管已经使用Windows Forms成功创建了许多功能齐全的桌面应用程序,但事实是该编程模型相当不对称。简而言之,System.Windows.Forms.dll和System.Drawing.dll不为构建功能丰富的桌面应用程序所需的许多其他技术提供直接支持。为了说明这一点,请考虑WPF发行之前的GUI桌面开发的特殊性质(请参阅表24-1)。
如您所见,Windows Forms开发人员必须从许多不相关的API和对象模型中提取类型。虽然在语法上看起来确实可以使用这些不同的API(毕竟只是C#代码),但是您可能还同意每种技术都需要完全不同的思维方式。例如,使用DirectX创建3D渲染动画所需的技能与用于将数据绑定到网格的技能完全不同。可以肯定的是,Windows Forms程序员很难掌握每个API的多样性。
统一各种API
创建WPF的目的是将这些以前不相关的编程任务合并到一个统一的对象模型中。因此,如果您需要编写3D动画,则无需针对DirectX API进行手动编程(尽管可以),因为3D功能直接包含在WPF中。若要查看清理情况,请考虑表24-2,该表说明了从.NET 3.0开始引入的桌面开发模型。
显而易见的好处是,.NET程序员现在拥有一个对称的API,可以满足所有常见的GUI桌面编程需求。当您对主要的WPF程序集的功能和XAML的语法感到满意之后,您会惊讶地发现创建复杂UI的速度如此之快。
通过XAML提供关注点分离
也许最引人注目的好处之一是WPF提供了一种将GUI应用程序的外观与驱动它的编程逻辑完全分开的方法。使用XAML,可以通过XML标记定义应用程序的UI。然后可以将此标记(最好使用Microsoft Visual Studio或Microsoft Expression Blend等工具生成)连接到相关的C#代码文件,以提供程序的实质功能。
注意: XAML不限于WPF应用程序。任何应用程序都可以使用XAML来描述.NET对象树,即使它们与可见的用户界面无关。
在研究WPF时,您可能会惊讶于此“桌面标记”提供的灵活性。XAML不仅使您可以在标记中定义简单的UI元素(按钮,网格,列表框等),而且还可以定义交互式2D和3D图形,动画,数据绑定逻辑以及多媒体功能(例如视频播放)。
XAML还使自定义控件如何呈现其视觉外观变得容易。例如,定义使公司徽标具有动画效果的圆形按钮控件仅需要几行标记。如第27章所示,可以通过样式和模板来修改WPF控件,使您能够以最小的麻烦和麻烦来更改应用程序的整体外观。 与Windows Forms开发不同,从头开始构建自定义WPF控件的唯一令人信服的理由是,如果您需要更改控件的行为(例如,添加自定义方法,属性或事件;子类化现有控件以覆盖虚拟成员)。如果您只需要更改控件的外观(例如,圆形动画按钮),则可以完全通过标记进行更改。
提供优化的渲染模型
GUI工具包(例如Windows Forms,MFC或VB6)使用基于C的低级API(GDI)来执行所有图形渲染请求(包括UI元素(如按钮和列表框)的渲染)。 Windows操作系统已有多年历史。GDI为典型的业务应用程序或简单的图形程序提供了足够的性能;但是,如果需要UI应用程序来利用高性能图形,则需要DirectX。
WPF编程模型的不同之处在于,呈现图形数据时不使用GDI。现在,所有渲染操作(例如2D图形,3D图形,动画,控件渲染等)都使用DirectX API。第一个明显的好处是您的WPF应用程序将自动利用硬件和软件优化。 同样,WPF应用程序可以利用非常丰富的图形服务(模糊效果,抗锯齿,透明度等),而无需直接针对DirectX API进行编程的复杂性。
注意: 尽管WPF确实将所有渲染请求都推送到DirectX层,但我不想建议WPF应用程序的执行速度与直接使用非托管C++和DirectX构建应用程序的速度一样快。尽管.NET 4.7中的WPF已经取得了显着进步,但是如果您打算构建需要最快执行速度的桌面应用程序(例如3D视频游戏),则非托管C++和DirectX仍然是最好的方法。
简化复杂的UI编程
到目前为止,总而言之,Windows Presentation Foundation(WPF)是用于构建桌面应用程序的API,该API将各种桌面API集成到单个对象模型中,并通过XAML清晰地分离了关注点。除了这些要点之外,WPF应用程序还受益于一种将服务集成到程序中的简单方法,这在过去一直很复杂。以下是WPF核心功能的简要介绍:
- 许多布局管理器(远远超过Windows窗体)提供了对内容放置和重新放置的极其灵活的控制。
- 使用增强的数据绑定引擎以多种方式将内容绑定到UI元素。
- 内置样式引擎,使您可以为WPF应用程序定义“主题”。
- 使用矢量图形,可以自动调整内容大小以适合承载应用程序的屏幕的大小和分辨率。
- 支持2D和3D图形,动画以及视频和音频播放。
- 丰富的排版API,例如对XML Paper Specification(XPS)文档,固定文档(WYSIWYG),流程文档和文档注释的支持(例如,Sticky Notes API)。
- 支持与旧版GUI模型(例如Windows Forms,ActiveX和Win32 HWND)进行互操作。例如,您可以将自定义Windows Forms控件合并到WPF应用程序中,反之亦然。
现在,您已经了解了WPF的功能,让我们看一下可以使用此API创建的各种类型的应用程序。 这些功能中的许多功能将在以后的章节中详细探讨。
研究WPF程序集
WPF最终不过是.NET程序集中捆绑在一起的类型的集合而已。表24-3描述了用于构建WPF应用程序的关键程序集,在创建新项目时必须引用每个程序集。如您所愿,Visual Studio WPF项目会自动引用这些必需的程序集。
表24-3中这四个程序集共同定义了许多新的名称空间以及数百种新的.NET类,接口,结构,枚举和委托。 尽管应该查阅.NET Framework 4.7 SDK文档以获取完整的详细信息,但表24-4描述了一些(但不是全部)重要名称空间的作用。
为了开始使用WPF编程模型,您将检查System.Windows命名空间的两个成员:Application和Window,它们是任何传统桌面开发工作中常见的。
注意:如果使用Windows Forms API创建了桌面UI,请注意System.Windows.Forms.* 和System.Drawing.*程序集与WPF不相关。这些库代表原始的.NET GUI工具包Windows Forms / GDI+。
Application类的作用
System.Windows.Application类表示正在运行的WPF应用程序的全局实例。此类提供了Run()方法(用于启动应用程序),一系列事件,您可以处理这些事件以与应用程序的生命周期进行交互(例如Startup和Exit),以及一些特定于XAML浏览器应用程序(例如,当用户在页面之间导航时触发的事件)。 表24-5详细列出了一些关键属性。
构造Application类
任何WPF应用程序都需要定义一个扩展Application的类。 在此类中,您将定义程序的入口点(Main()方法),该入口点创建此子类的实例,并通常处理Startup和Exit事件(必要时)。 这是一个例子:
|
|
在启动处理程序中,您通常会处理所有传入的命令行参数并启动程序的主窗口。如您所料,可以在Exit处理程序中编写该程序的任何必要关闭逻辑(例如,保存用户首选项,写入Windows注册表)。
注意: WPF应用程序的Main()方法必须具有[STAThread]属性,该属性可确保您的应用程序使用的所有旧COM对象都是线程安全的。如果不以这种方式注释Main(),则会遇到运行时异常。
枚举Windows集合
应用程序公开的另一个有趣的属性是Windows,它提供对表示当前WPF应用程序加载到内存中的每个窗口的集合的访问。回想一下,当您创建新的Window对象时,它们会自动添加到Application.Windows集合中。这是一个示例方法,该方法将最小化应用程序的每个窗口(可能响应最终用户触发的给定键盘手势或菜单选项):
|
|
您将很快构建一些WPF应用程序,但是在此之前,让我们检查一下Window类型的核心功能,并在此过程中了解许多重要的WPF基类。
窗口类的作用
System.Windows.Window类(位于PresentationFramework.dll程序集中)表示由Application派生的类拥有的单个窗口,包括主窗口显示的所有对话框。毫不奇怪,Window有一系列父类,每个父类都为表带来了更多功能。请考虑图24-1,该图显示了通过Visual Studio对象浏览器看到的System.Windows.Window的继承链(和实现的接口)。
在学习本章及以后的各章时,您将了解这些基本类提供的功能。但是,为激起您的胃口,以下各节提供了每个基类提供的功能的细分(有关详细信息,请查阅.NET Framework 4.7 SDK文档)。
System.Windows.Controls.ContentControl的角色
Window的直接父级是ContentControl,它很可能是所有WPF类中最诱人的。此基类为派生类型提供了承载单个内容的能力,简单地说,就是通过Content属性引用放置在控件表面区域内部的可视数据。WPF内容模型使自定义内容控件的基本外观非常简单。
例如,当您想到典型的“按钮”控件时,您倾向于假定内容是简单的字符串文字(“确定”,“取消”,“中止”等)。 如果您使用XAML来描述WPF控件,并且想要分配给Content属性的值可以作为一个简单的字符串捕获,则可以在元素的开头定义内设置Content属性(不要担心确切的含义)。 标记):
|
|
注意:Content属性也可以用C#代码设置,这使您可以在运行时更改控件的内部。
但是,内容几乎可以是任何东西。例如,假设您想要一个“按钮”,其功能比简单的字符串(也许是自定义图形和文本模糊)更有趣。在其他UI框架(例如Windows Forms)中,将要求您构建自定义控件,这可能需要大量代码并需要维护一个全新的类。使用WPF内容模型,无需这样做。
如果您想将Content属性分配给无法以简单字符数组捕获的值,则无法使用控件的开始定义中的属性来分配它。相反,您必须在元素范围内隐式定义内容数据。 例如,以下 <Button> 包含 <StackPanel> 作为内容,其本身包含一些唯一的数据(准确地说是<Ellipse>和<Label>):
|
|
您还可以使用XAML的属性元素语法来设置复杂的内容。考虑下面的功能上等效的<Button>定义,该定义使用property-element语法显式设置了Content属性(同样,您将在本章后面找到有关XAML的更多信息,因此,请不要花太多时间在细节上):
|
|
请注意,并非每个WPF元素都派生自ContentControl,因此,并非所有控件都支持此唯一的内容模型(但是,大多数控件都支持)。同样,某些WPF控件对您刚刚检查过的基本内容模型进行了一些改进。第25章将更详细地研究WPF内容的作用。
System.Windows.Controls.Control的角色
与ContentControl不同,所有WPF控件均作为通用父级共享Control基类。该基类提供了许多基本的UI功能核心成员。例如,控件定义属性以建立控件的大小,不透明度,标签顺序逻辑,显示光标,背景颜色等。此外,此父类为模板服务提供支持。如第27章所述,WPF控件可以使用模板和样式完全改变其呈现外观的方式。表24-6列出了Control类型的一些关键成员,按相关功能分组。
System.Windows.FrameworkElement的角色
此基类提供了许多在WPF框架中使用的成员,例如对情节提要(在动画中使用)的支持和对数据绑定的支持,以及(通过Name属性)命名成员的能力,以获得任何由派生类型定义的资源,并建立派生类型的整体尺寸。表24-7列出了重点内容。
System.Windows.UIElement的角色
在Window的继承链中的所有类型中,UIElement基类提供了最多的功能。UIElement的关键任务是为派生类型提供大量事件,以允许派生类型接收焦点并处理输入请求。例如,此类提供大量事件来说明拖放操作,鼠标移动,键盘输入和手写笔输入(适用于Pocket PC和Tablet PC)。
第25章详细探讨了WPF事件模型。但是,许多核心事件看起来都很熟悉(MouseMove,KeyUp,MouseDown,MouseEnter,MouseLeave等)。除了定义数十个事件外,该父类还提供了许多属性来说明控件焦点,启用状态,可见性和命中测试逻辑,如表24-8所示。
System.Windows.Threading.DispatcherObject的角色
Window类型的最后一个基类(超出System.Object,我认为在书中此刻不需要进一步说明)是DispatcherObject。此类型提供一个有用的属性Dispatcher,该属性返回关联的System.Windows.Threading.Dispatcher对象。Dispatcher类是WPF应用程序事件队列的入口点,它提供了处理并发和线程的基本构造。
了解WPF XAML的语法
生产级WPF应用程序通常将使用专用工具来生成必要的XAML。尽管这些工具很有用,但了解XAML标记的整体结构是一个好主意。为了帮助您进行学习,请允许我介绍一种流行的(免费的)工具,该工具可让您轻松地尝试XAML。
介绍Kaxaml
当您第一次学习XAML语法时,使用名为Kaxaml的免费工具会有所帮助。您可以从可下载文件中的Kaxaml目录中获得此流行的XAML编辑器/解析器。
注意:对于本书的许多版本,我都已将用户指向 www.kaxaml.com,但不幸的是,该站点已淘汰。我在本书的可下载材料中拥有.msi软件包的副本,并且还将存储库分叉到了我的个人GitHub帐户(www.github.com/skimedic/kaxaml ),以确保它可以继续使用。非常感谢并感谢Kaxaml的开发人员;这是一个很棒的工具,已帮助无数开发人员学习XAML。
Kaxaml很有帮助,因为它不了解C#源代码,事件处理程序或实现逻辑。与使用成熟的Visual Studio WPF项目模板相比,它是测试XAML代码片段的直接得多的方法。同样,Kaxaml具有许多集成工具,例如颜色选择器,XAML代码段管理器,甚至是“XAML清理程序”选项,这些选项都将根据您的设置来格式化XAML。首次打开Kaxaml时,将为Page控件找到简单的标记,如下所示:
|
|
类似于Window,Page包含各种布局管理器和控件。但是,与Window不同,Page对象不能作为独立实体运行。 而是必须将它们放置在合适的主机内,例如NavigationWindow或Frame。好消息是您可以在Page或Window范围内键入相同的标记。
作为初始测试,请在工具底部的XAML窗格中输入以下标记:
|
|
现在,您应该在Kaxaml编辑器的上部看到页面渲染(参见图24-2)。
当您使用Kaxaml时,请记住,该工具不允许您编写任何需要代码编译的标记(但是,允许使用x:Name)。 这包括定义x:Class属性(用于指定代码文件),在标记中输入事件处理程序名称,或使用也需要进行代码编译的任何XAML关键字(例如FieldModifier或ClassModifier)。任何尝试这样做都会导致标记错误。
XAML XML命名空间和XAML“关键字”
WPF XAML文档的根元素(例如Window,Page,UserControl或Application定义)几乎总是引用以下两个预定义的XML名称空间:
|
|
第一个XML名称空间: http://schemas.microsoft.com/winfx/2006/xaml/presentation 映射了一系列WPF .NET名称空间(System.Windows,System.Windows.Controls,System.Windows.Data,System.Windows.Ink,System.Windows.Media等),提供给当前的 *.xaml文件。
实际上,此一对多映射是使用程序集级[XmlnsDefinition]属性在WPF程序集(WindowsBase.dll,PresentationCore.dll和PresentationFramework.dll)中进行硬编码的。例如,如果打开Visual Studio对象浏览器并选择PresentationCore.dll程序集,您将看到诸如以下的清单,这些清单实际上导入了System.Windows:
[assembly: XmlnsDefinition(“http://schemas.microsoft.com/winfx/2006/xaml/presentation”, “System.Windows”)]
第二个XML名称空间 http://schemas.microsoft.com/winfx/2006/xaml ,用于包括XAML专用的“关键字”(由于缺乏更好的用语),以及包含System.Windows.Markup名称空间,如下所示:
[assembly: XmlnsDefinition(“http://schemas.microsoft.com/winfx/2006/xaml”, “System.Windows.Markup”)]
任何格式正确的XML文档(请记住,XAML是基于XML的语法)的一个规则是,开头的根元素将一个XML名称空间指定为主名称空间,该名称空间通常是包含最常用项目的名称空间。如果根元素要求包含其他辅助名称空间(如此处所示),则必须使用唯一的标记前缀定义它们(以解决任何可能的名称冲突)。按照惯例,前缀只是x;但是,这可以是您需要的任何唯一令牌,例如XamlSpecificStuff。
|
|
定义冗长的XML名称空间前缀的明显缺点是,每次您的XAML文件需要引用此XML名称空间中定义的项之一时,都需要键入XamlSpecificStuff。鉴于XamlSpecificStuff需要许多其他的击键,只要坚持使用x替代即可。
无论如何,除了x:Name,x:Class和x:Code关键字之外,http//schemas.microsoft.com/winfx/2006/xaml XML命名空间还提供对其他XAML关键字(最常见的XAML关键字)的访问权限 如表24-9所示。
XAML关键字 | 意义 |
---|---|
x:Array | 表示XAML中的.NET数组类型 |
x:ClassModifier | 允许您定义由Class关键字表示的C#类(内部或公共)的可见性。 |
x:FieldModifier | 允许您为根的任何命名子元素(例如,Window元素中的Button)定义类型成员(内部,公共,私有或受保护)的可见性。 使用Name XAML关键字定义命名元素。 |
x:Key | 允许您为XAML项目建立键值,该键值将放入字典元素中。 |
x:Name | 允许您指定给定XAML元素的生成的C#名称 |
x:Null | 表示空引用 |
x:Static | 允许您引用类型的静态成员 |
x:Type | 等效于C#typeof运算符的XAML(它将根据提供的名称生成System.Type) |
x:TypeArguments | 允许您将元素建立为具有特定类型参数的泛型类型 |
除了这两个必需的XML名称空间声明之外,有可能(有时是必要的)在XAML文档的开始元素中定义其他标签前缀。通常,每当需要在XAML中描述外部程序集中定义的.NET类时,都需要这样做。
例如,假设您已经构建了一些自定义WPF控件,并将它们打包在一个名为MyControls.dll的库中。现在,如果要创建一个使用这些控件的新Window,则可以建立一个自定义XML名称空间,该名称空间使用clr-namespace和Assembly令牌映射到您的库。以下是一些示例标记,这些标记创建了名为myCtrls的标记前缀,可用于访问库中的控件:
|
|
将clr-namespace令牌分配给程序集中.NET命名空间的名称,而将程序集(assembly)令牌设置为外部*.dll程序集的友好名称。您可以对要在标记中使用的任何外部.NET库使用此语法。尽管目前不需要这样做,但以后的章节将要求您定义自定义XML名称空间声明以描述标记中的类型。
注意:如果需要在标记中定义一个类,该类是当前程序集的一部分,但在其他.NET命名空间中,则不带assembly= 属性定义xmlns标记前缀,如下所示: xmlns:myCtrls="clr-namespace:SomeNamespaceInMyApp”
控制类和成员变量可见性
在以后的章节中,您会看到许多这样的关键字在起作用。但是,通过一个简单的示例,请考虑以下使用ClassModifier和FieldModifier关键字以及X:Name和x:Class定义的XAML <Window>(请记住,kaxaml.exe将不允许您使用需要代码编译的任何XAML关键字,例如x:Code,x:FieldModifier或x:ClassModifier):
|
|
默认情况下,所有C#/ XAML类型定义都是公共的,而成员默认为内部。但是,根据您的XAML定义,生成的自动生成的文件包含带有公共Button变量的内部类类型。
|
|
XAML元素,XAML属性和类型转换器
建立根元素和所有必需的XML名称空间后,下一个任务是用子元素填充根元素。在实际的WPF应用程序中,子级将是布局管理器(例如Grid或StackPanel),该管理器依次包含描述用户界面的任意数量的其他UI元素。下一章将详细研究这些布局管理器,因此现在仅假设您的<Window>类型将包含一个Button元素。
如本章所述,XAML元素映射到给定.NET命名空间中的类或结构类型,而开始元素标记中的属性映射到该类型的属性或事件。为了说明,在Kaxaml中输入以下<Button>定义:
|
|
请注意,分配给每个属性的值已捕获为简单文本值。这似乎是数据类型的完全不匹配,因为如果要用C#代码制作此Button,则不会将字符串对象分配给这些属性,而是会使用特定的数据类型。例如,这是代码中编写的同一按钮:
|
|
事实证明,WPF附带了许多类型转换器类,这些类将用于将简单文本值转换为正确的基础数据类型。此过程透明(自动)进行。
尽管这一切都很好,但是在很多情况下,您需要为XAML属性分配更为复杂的值,而该值不能捕获为简单字符串。例如,假设您要构建自定义画笔来设置Button的Background属性。如果要用代码构建画笔,则非常简单,如下所示:
|
|
如何将复杂的画笔表示为字符串?好吧,你不能!值得庆幸的是,XAML提供了一种特殊的语法,该语法在需要将属性值分配给复杂对象时可以使用,称为属性元素语法。
了解XAML属性元素语法
属性元素语法允许您将复杂对象分配给属性。例如这是按钮的XAML描述,该按钮利用LinearGradientBrush设置其Background属性:
|
|
请注意,在<Button>和</Button>标记的范围内,您定义了一个名为<Button.Background>的子范围。在此范围内,您已定义了一个自定义。 (不必担心画笔的确切代码;您将在第28章中了解WPF图形)。
一般而言,可以使用property-element语法设置任何属性,该语法始终会分解为以下模式:
|
|
尽管可以使用此语法设置任何属性,但是如果可以将值捕获为简单字符串,则可以节省键入时间。例如,以下是设置按钮宽度的详细方法:
|
|
了解XAML附加属性
除了属性元素语法外,XAML还定义了一种特殊语法,该语法用于为附加属性设置值。本质上,附加属性允许子元素设置在父元素中实际定义的属性的值。遵循的通用模板如下所示:
|
|
附加属性语法的最常见用法是将UI元素放置在WPF布局管理器类之一(Grid,DockPanel等)中。下一章将详细介绍这些面板。现在,在Kaxaml中输入以下内容:
|
|
在这里,您定义了一个包含Ellipse的Canvas布局管理器。注意,椭圆能够使用附加的属性语法通知其父级(画布)将其顶部/左侧位置放置在何处。
了解XAML标记扩展
如前所述,属性值通常使用简单的字符串或通过property-element语法表示。但是,还有另一种使用标记扩展来指定XAML属性值的方法。标记扩展允许XAML解析器从专用的外部类获取属性的值。鉴于某些属性值需要大量代码语句来执行以找出该值,因此这可能是有益的。
标记扩展提供了一种通过新功能完全扩展XAML语法的方法。标记扩展在内部表示为从MarkupExtension派生的类。请注意,您需要构建自定义标记扩展的机会微乎其微。但是,XAML关键字的子集(例如x:Array,x:Null,x:Static和x:Type)是伪装的标记扩展!
标记扩展名夹在大括号之间,如下所示:
|
|
要查看实际使用的标记扩展,请将以下内容编写到Kaxaml中:
|
|
首先,请注意Page定义具有新的XML名称空间声明,该声明使您可以访问mscorlib.dll的System名称空间。 建立此XML名称空间后,您首先要使用x:Static标记扩展名并从System.Environment中的OSVersion和ProcessorCount中获取值。
x:Type标记扩展名使您可以访问指定项目的元数据描述。在这里,您只需分配WPF Button和System.Boolean类型的完全限定名称。
此标记最有趣的部分是ListBox。在这里,您将ItemsSource属性设置为完全在标记中声明的字符串数组!在这里注意x:Array标记扩展如何允许您在其范围内指定一组子项目。
注意: 前面的XAML示例仅用于说明实际使用的标记扩展。正如您将在第25章中看到的那样,有很多更容易的方法来填充ListBox控件!
现在,您已经看到了无数示例,这些示例展示了XAML语法的每个核心方面。正如您可能同意的那样,XAML很有趣,因为它允许您以声明的方式描述.NET对象树。尽管这在配置图形用户界面时非常有用,但请记住XAML可以描述任何程序集中的任何类型,只要它是包含默认构造函数的非抽象类型即可。
浏览WPF文档
在结束本章时,我想指出。.NET4.7 Framework SDK文档提供了专门讨论WPF主题的整个部分。在探索此API并阅读其余以WPF为中心的章节时,如果您尽早并经常咨询帮助系统,将会为您提供出色的服务。在这里,您将找到大量的XAML示例以及有关从3D图形编程到复杂的数据绑定操作的各种主题的详细教程。
您可以通过“文档”➤ .NET➤.NET Framework➤Windows Presentation Foundation菜单访问WPF文档。它位于 https://docs.microsoft.com/zh-cn/dotnet/framework/wpf/index。
概要
Windows Presentation Foundation(WPF)是 .NET 3.0发行版中引入的用户界面工具包。WPF的主要目标是将许多以前不相关的桌面技术(2D图形,3D图形,窗口和控件开发等)集成和统一到一个统一的编程模型中。除此之外,WPF程序通常使用XAML,它允许您通过标记声明WPF元素的外观。
回想一下XAML允许您使用声明性语法描述.NET对象的树。在本章对XAML的研究中,您接触到了一些新的语法,包括属性元素语法和附加属性,以及类型转换器和XAML标记扩展的作用。
XAML是任何生产级WPF应用程序的关键方面。本章的最后一个示例使您有机会构建一个WPF应用程序,该应用程序显示了本章讨论的许多概念。下一章将深入探讨这些概念,并介绍更多概念。
第25章 WPF控件,布局,事件和数据绑定
第24章为WPF编程模型提供了基础,包括检查Window和Application类,XAML语法以及代码文件的使用。第24章还向您介绍了使用Visual Studio设计器构建WPF应用程序的过程。在本章中,您将使用几个新的控件和布局管理器来研究更复杂的图形用户界面的构造,并逐步了解Visual Studio的WPF设计器的其他功能。
本章还将研究一些重要的WPF相关控制主题,例如数据绑定编程模型和控制命令的使用。您还将学习如何使用Ink和Documents API,使用它们可以捕获手写笔(或鼠标)输入并分别使用XML Paper Specification生成RTF文档。
WPF核心控件概述
除非您不熟悉构建图形用户界面的概念(这没关系),否则主要WPF控件的一般用途不应引起太多问题。无论您过去使用过哪种GUI工具包(例如,VB 6.0,MFC,Java AWT / Swing,Windows Forms,macOS或GTK+ / GTK#等),表25-1中列出的WPF核心控件可能看起来都会很熟悉。
WPF Ink 控件
除了表25-1中列出的通用WPF控件外,WPF还定义了用于与数字Ink API配合使用的其他控件。WPF开发的这一方面在Tablet PC开发期间非常有用,因为它使您可以从笔中捕获输入。但是,这并不是说标准的桌面应用程序无法利用Ink API,因为相同的控件可以使用鼠标捕获输入。
PresentationCore.dll的System.Windows.Ink命名空间包含各种Ink API支持类型(例如Stroke和StrokeCollection);但是,大多数Ink API控件(例如InkCanvas和InkPresenter)与通用WPF控件一起打包在PresentationFramework.dll程序集的System.Windows.Controls命名空间下。本章稍后将使用Ink API。
WPF文档控件
WPF还提供了用于高级文档处理的控件,使您可以构建包含Adobe PDF样式功能的应用程序。使用System.Windows.Documents命名空间(同样在PresentationFramework.dll程序集中)中的类型,可以创建支持缩放,搜索,用户注释(便笺)和其他RTF服务的可打印文档。
然而,在幕后,文档控件不使用Adobe PDF API。 而是使用XML Paper Specification(XPS)API。对于最终用户来说,实际上似乎没有什么区别,因为PDF文档和XPS文档具有几乎相同的外观。实际上,您可以找到许多免费的实用程序,使您可以即时在两种文件格式之间进行转换。由于篇幅所限,本版本将不涉及这些控件。
WPF通用对话框
WPF还为您提供了一些常用对话框,例如OpenFileDialog和SaveFileDialog。这些对话框在PresentationFramework.dll程序集的Microsoft.Win32命名空间中定义。使用这些对话框中的任何一个都需要创建一个对象并调用ShowDialog()方法,如下所示:
|
|
正如您希望的那样,这些类支持各种成员,这些成员使您可以建立文件过滤器和目录路径,并访问用户选择的文件。您将在以后的示例中使用这些文件对话框。您还将学习如何构建自定义对话框来收集用户输入。
使用面板控制内容布局
您还可以在其他面板(例如,包含其他项目的StackPanel的DockPanel)内混合面板控件,以提供很大的灵活性和控件。表25-2记录了一些常用的WPF面板控件的作用。
面板控件 | 意义 |
---|---|
Canvas | 提供经典的内容放置方式。在设计时,物品会完全保留在您放置它们的位置。 |
DockPanel | 将内容锁定到面板的指定侧(顶部,底部,左侧或右侧)。 |
Grid | 在表格单元格中维护的一系列单元格中排列内容。 |
StackPanel | 根据Orientation属性的要求,以垂直或水平方式堆叠内容。 |
WrapPanel | 将内容从左到右放置,将内容中断到包含框边缘的下一行。 随后的顺序从上到下或从右到左顺序发生,具体取决于Orientation属性的值。 |
在接下来的几节中,您将通过将一些预定义的XAML数据复制到在第24章中安装的kaxaml.exe应用程序中,学习如何使用这些常用的面板类型。您可以在您的第25章代码下载文件夹PanelMarkup子文件夹中找到所有这些宽松的XAML文件。使用Kaxaml时,要模拟调整窗口的大小,请更改标记中Page元素的高度或宽度。
使用嵌套面板构建窗口框架
了解WPF命令
WPF通过命令体系结构为可能被认为与控制无关的事件提供支持。典型的.NET事件是在特定的基类中定义的,并且只能由该类或其派生类使用。因此,普通的.NET事件与定义它们的类紧密相关。
相反,WPF命令是类似事件的实体,独立于特定控件,并且在许多情况下可以成功地应用于多种(看似无关)的控件类型。举几个例子,WPF支持复制,粘贴和剪切命令,您可以将其应用于各种UI元素(例如,菜单项,工具栏按钮和自定义按钮)以及键盘快捷键(例如,Ctrl+C 和 Ctrl+V)。
虽然其他UI工具包(例如Windows Forms)为此目的提供了标准事件,但使用它们通常会给您留下冗余且难以维护的代码。在WPF模型下,您可以使用命令作为替代。最终结果通常会产生更小,更灵活的代码库。
了解路由事件
您可能已经注意到上一个代码示例中的RoutedEventArgs参数而不是EventArgs。路由事件模型是对标准CLR事件模型的改进,该模型旨在确保可以以适合XAML对对象树的描述的方式来处理事件。假设您有一个名为WPFRoutedEvents的新WPF应用程序项目。现在,通过添加下面的Button控件来更新初始窗口的XAML描述,该按钮定义了一些复杂的内容:
|
|
请注意,在Button的开头定义中,您已经通过指定引发事件时要调用的方法的名称来处理Click事件。Click事件与RoutedEventHandler委托一起使用,该委托期望一个事件处理程序以一个对象作为第一个参数,并使用System.Windows.RoutedEventArgs作为第二个。如此实现此处理程序:
|
|
如果您运行应用程序,则无论您单击按钮内容的哪一部分(绿色的椭圆,黄色的椭圆,标签或按钮的表面),都会看到此消息框显示。这是一件好事。想象一下,如果您被迫为每个子元素处理Click事件,那么WPF事件处理将是多么繁琐。为Button的每个方面创建单独的事件处理程序不仅会很费力,而且您最终还会得到一些强大的令人讨厌的代码来维护。
值得庆幸的是,WPF路由事件可以确保无论自动单击按钮的哪一部分,都将调用单个Click事件处理程序。简而言之,路由事件模型自动在对象树上(或下)传播事件,以寻找合适的处理程序。
具体来说,路由事件可以利用三种路由策略。如果事件正在从源点移动到对象树中的其他定义范围,则该事件被称为冒泡事件。相反,如果事件从最外面的元素(例如,窗口)向下移动到起点,则该事件被称为隧道事件。最后,如果仅由始发元素引发和处理事件(可以将其描述为正常CLR事件),则将其称为直接事件。
路由冒泡事件的作用
在当前示例中,如果用户单击内部的黄色椭圆形,则Click事件冒出到下一级作用域(画布),然后到StackPanel,最后到处理Click事件处理程序的Button。以类似的方式,如果用户单击Label,则事件将冒泡到StackPanel,然后最终冒泡到Button元素。
有了这种冒泡的路由事件模式,您就不必担心为复合控件的所有成员注册特定的Click事件处理程序。但是,如果要对同一对象树中的多个元素执行自定义单击逻辑,则可以执行此操作。
举例说明,假设您需要以独特的方式处理externalEllipse控件的单击。首先,处理此子元素的MouseDown事件(图形呈现的类型,例如Ellipse不支持Click事件;但是,它们可以通过MouseDown,MouseUp等监视鼠标按钮的活动)。
|
|
然后实现一个适当的事件处理程序,出于说明目的,该事件处理程序将仅更改主窗口的Title属性,如下所示:
|
|
这样,您现在可以根据最终用户单击的位置(归结为外部椭圆以及按钮范围内的所有其他位置)采取不同的操作方式。
注意: 路由冒泡事件始终从原点移动到下一个定义范围。因此,在此示例中,如果单击innerEllipse对象,则事件将冒泡到Canvas,而不是到externalEllipse,因为它们都是Canvas范围内的Ellipse类型。
继续或停止冒泡
当前,如果用户单击outerEllipse对象,它将触发为此Ellipse对象注册的MouseDown事件处理程序,此时该事件会冒泡到按钮的Click事件。如果要通知WPF停止冒泡对象树,可以将EventArgs参数的Handled属性设置为true,如下所示:
|
|
在这种情况下,您会发现窗口的标题已更改,但是您将看不到Button的Click事件处理程序显示的MessageBox。 简而言之,路由冒泡事件可以使一组复杂的内容充当单个逻辑元素(例如Button)或离散项(例如Button中的Ellipse)。
路由隧道事件的作用
严格来说,路由事件实际上可以是冒泡的(如刚刚描述的)或隧穿的 隧道事件(所有事件均以Preview后缀开头-例如PreviewMouseDown)从最顶层的元素向下钻入对象树的内部范围。总的来说,WPF基类库中的每个冒泡事件都与一个相关的隧道事件配对,该事件在冒泡对象之前触发。例如,在启动冒泡的MouseDown事件之前,将先触发隧道化PreviewMouseDown事件。
处理隧道事件看起来就像处理其他任何事件一样。只需在XAML中分配事件处理程序名称即可(或者,如果需要,在代码文件中使用相应的C#事件处理语法),然后在代码文件中实现处理程序。只是为了说明隧道事件和冒泡事件之间的相互作用,首先要处理externalEllipse对象的PreviewMouseDown事件,如下所示:
|
|
接下来,通过使用传入事件args对象更新每个事件处理程序(针对所有对象)以更新当前C#类定义,以将有关当前事件的数据附加到名为mouseActivity的字符串成员变量中。这将使您观察后台触发的事件流。
|
|
注意,您不会停止任何事件处理程序的事件冒泡。如果运行此应用程序,则将根据单击按钮的位置显示唯一的消息框。图25-15显示了单击外部Ellipse对象的结果。
那么,为什么WPF事件通常倾向于成对出现(一个隧道和一个冒泡)? 答案是,通过预览事件,您可以在启动冒泡对象之前执行任何特殊的逻辑(数据验证,禁用冒泡操作等)。举例来说,假设您有一个TextBox,它只应包含数字数据。您可以处理PreviewKeyDown事件,并且如果看到用户输入了非数字数据,则可以通过将Handled属性设置为true来取消冒泡事件。
就像您猜到的那样,当您构建包含自定义事件的自定义控件时,您可以通过以下方式创作事件:事件可以在XAML树中冒泡(或隧穿)。就本章而言,我不会研究如何构建自定义路由事件(但是,该过程与构建自定义依赖项属性没有什么不同)。如果您有兴趣,请查看 .NET Framework 4.7 SDK文档中的“路由事件概述”主题。 在其中,您会找到许多可以帮助您的教程。
深入了解WPF API和控件
本章的其余部分将使您有机会使用Visual Studio构建新的WPF应用程序。目标是创建一个由包含一组选项卡的TabControl小部件组成的UI。每个选项卡将说明您可能希望在软件项目中使用的一些新的WPF控件和有趣的API。在此过程中,您还将学习Visual Studio WPF设计器的其他功能。
使用TabControl
首先,创建一个名为WpfControlsAndAPIs的新WPF应用程序。如前所述,您的初始窗口将包含一个带有三个不同选项卡的TabControl,每个选项卡都展示了一组相关的控件和/或WPF API。 将窗口的宽度更新为800,高度更新为350。
在Visual Studio工具箱中找到TabControl控件,将其放到设计器中,然后将标记更新为以下内容:
|
|
您会注意到,系统将自动为您提供两个选项卡项。要添加其他选项卡,只需在Document Outline窗口中右键单击TabControl节点,然后选择Add TabItem菜单选项(也可以在设计器上右键单击TabControl来激活相同的菜单选项),或者只是开始输入 在XAML编辑器中。使用任一方法添加一个附加选项卡。
现在,通过XAML编辑器更新每个TabItem控件,并更改每个选项卡的Header属性,将它们命名为Ink API,Data Binding和DataGrid。此时,您的窗口设计器应如图25-16所示。
要知道,当你选择一个标签进行编辑,该选项卡成为活动选项卡,你可以从工具箱窗口中拖动控制设计该选项卡。现在,您已经定义了核心的TabControl,您可以逐个标签地制定详细信息,并逐步了解WPF API的更多功能。
构建Ink API标签
第一个标签将用于显示WPF数字墨水API的总体作用,使您可以轻松地将绘画功能整合到程序中。当然,该应用程序实际上不需要是绘画应用程序;您可以将此API用于多种用途,包括捕获手写输入。
注意: 对于本章的其余大部分(以及WPF的下一章),我将直接编辑XAML,而不是使用各种窗口。尽管控件可以工作,但布局不是您想要的(Visual Studio根据控件的放置位置增加了边距和内边距),这样你将花费大量时间清理XAML。
首先将Ink API TabItem下的Grid标记更改为StackPanel并添加一个结束标记(确保从开始标记中删除“ /”)。 您的标记应如下所示:
|
|
设计工具栏
添加一个名为InkToolbar的新工具栏控件(使用XAML编辑器),高度为60。
|
|
在Border控件内部的WrapPanel内部添加三个RadioButton控件,如下所示:
|
|
当RadioButton控件未放置在父面板控件内部时,它将具有与Button控件相同的UI! 这就是为什么我将RadioButton控件包装在WrapPanel中的原因。
接下来,添加一个分隔符,然后添加一个宽度为175,边距为10,0,0,0的ComboBox。 添加三个具有红色,绿色和蓝色内容的ComboBoxItem标签,并在整个ComboBox后面添加另一个Separator控件,如下所示:
|
|
单选按钮控件
在此示例中,您希望这三个RadioButton控件互斥。在其他GUI框架中,要确保一组相关控件(例如单选按钮)是互斥的,则需要将它们放在同一组框中。您无需在WPF下执行此操作。相反,您可以简单地将它们全部分配给相同的组名。 这很有用,因为相关项目不需要实际收集在同一区域中,而是可以放置在窗口中的任何位置。
RadioButton类包含一个IsChecked属性,当最终用户单击UI元素时,该属性将在true和false之间切换。 此外,RadioButton提供了两个事件(“Checked”和“Unchecked”),可用于拦截此状态更改。
添加保存,加载和删除按钮
ToolBar控件中的最终控件将是一个网格,其中包含三个Button控件。在最后一个Separator控件之后添加以下标记:
|
|
添加InkCanvas控件
TabControl的最终控件是InkCanvas控件。 在结束的ToolBar标记之后和结束的StackPanel标记之前添加以下标记,如下所示:
|
|
预览窗口
此时,您可以测试程序了,您可以按F5键。现在,您应该看到三个互斥的单选按钮,一个带有三个选项的组合框和三个按钮(见图25-17)。
处理Ink API选项卡的事件
Ink API选项卡的下一步是处理每个RadioButton控件的Click事件。就像在本书的其他WPF项目中所做的那样,只需单击Visual Studio属性编辑器的闪电图标即可输入事件处理程序的名称。使用这种方法,将每个按钮的Click事件路由到名为RadioButtonClicked的同一处理程序。处理所有三个Click事件之后,使用名为ColorChanged的处理程序处理ComboBox的SelectionChanged事件。完成后,您应该具有以下C#代码:
|
|
您将在以后的步骤中实现这些处理程序,因此暂时将它们留空。
将控件添加到工具箱
您通过直接编辑XAML添加了InkCanvas控件。如果要使用UI进行添加,则默认情况下,Visual Studio工具箱不会向您显示所有可能的WPF组件。但是您可以更新工具箱中显示的项目。
这样做,右键单击“工具箱”区域中的任何位置,然后选择“选择项目”菜单选项。 一两分钟后,您将看到要添加到工具箱的可能组件的列表。为了您的目的,您有兴趣添加InkCanvas控件(请参见图25-18)。
InkCanvas控件
只需添加InkCanvas,即可在窗口中绘图。您可以使用鼠标,或者,如果您有启用触摸的设备,则可以使用手指或数字笔。运行该应用程序并绘制到框中(请参见图25-19)。
InkCanvas的功能不只是绘制鼠标(或手写笔)的笔划。 它还支持许多独特的编辑模式,这些模式由EditingMode属性控制。您可以从相关的InkCanvasEditingMode枚举中为该属性分配任何值。在此示例中,您对Ink Mode感兴趣,这是您刚刚看到的默认选项。Select Mode,允许用户使用鼠标选择区域以移动或调整大小;和EraseByStoke,这将删除先前的鼠标笔触。
注意: 笔触是一次鼠标下移/鼠标上移操作期间发生的渲染。InkCanvas将所有笔划存储在StrokeCollection对象中,您可以使用Strokes属性对其进行访问。
使用以下逻辑更新您的RadioButtonClicked()处理程序,该逻辑根据选定的RadioButton将InkCanvas置于正确的模式:
|
|
另外,在窗口的构造函数中,默认情况下将模式设置为“Ink”。在使用它时,请为ComboBox设置一个默认选择(下一节中有关此控件的更多详细信息),如下所示:
|
|
现在,按F5再次运行程序。进入墨水模式并绘制一些数据。接下来,进入“擦除”模式并删除您输入的上一个鼠标笔划(您会注意到鼠标图标自动看起来像一个橡皮擦)。最后,进入“选择”模式,然后使用鼠标作为套索来选择一些笔触。
圈出项目后,可以在画布上移动它并调整其尺寸。 图25-20显示了您的工作编辑模式。
ComboBox控件
填充ComboBox控件(或ListBox)后,可以通过三种方法确定所选项目。首先,如果要查找所选项目的数字索引,可以使用SelectedIndex属性(从零开始;值-1表示没有选择)。其次,如果要在已选择的列表中获取对象,则SelectedItem属性很合适。第三,SelectedValue允许您获取所选对象的值(通常使用对ToString()的调用获得)。
您需要为此选项卡添加代码的最后一位,以更改在InkCanvas上输入的笔触颜色。InkCanvas的DefaultDrawingAttributes属性返回一个DrawingAttributes对象,该对象使您可以配置笔尖的多个方面,包括其大小和颜色(以及其他设置)。使用ColorChanged()方法的以下实现更新您的C#代码:
|
|
现在回想一下,ComboBox具有ComboBoxItems的集合。如果您查看生成的XAML,则会看到以下定义:
|
|
调用SelectedItem时,将获取选定的ComboBoxItem,该ComboBoxItem存储为常规对象。将对象强制转换为ComboBoxItem之后,您将拔出Content的值,该值将为字符串Red,Green或Blue。然后,使用方便的ColorConverter实用程序类将此字符串转换为Color对象。现在再次运行您的程序。渲染图像时,您应该能够在颜色之间进行更改。
请注意,ComboBox和ListBox控件也可以包含复杂的内容,而不是文本数据列表。您可以通过打开窗口的XAML编辑器并更改ComboBox的定义来了解可能发生的事情,以便它包含一组StackPanel元素,每个元素都包含一个Ellipse和Label(请注意,组合框的宽度为175)。
|
|
请注意,每个StackPanel都为其Tag属性分配一个值,这是一种简单,快速且方便的方法来发现用户已选择的项目堆栈(有更好的方法,但是现在可以这样做)。进行此调整后,您需要更改ColorChanged()方法的实现,如下所示:
|
|
现在再次运行程序,并记下您独特的ComboBox(见图25-21)。
保存,加载和清除InkCanvas数据
此选项卡的最后一部分将使您能够保存和加载画布数据,以及通过为工具栏中的按钮添加事件处理程序清除其所有内容。 通过为单击事件添加标记来更新按钮的XAML,如下所示:
|
|
接下来,将System.IO和System.Windows.Ink命名空间导入到您的代码文件中。实现处理程序,如下所示:
|
|
现在,您应该能够将数据保存到文件中,从文件中加载它,并清除所有数据的InkCanvas。 这包含了TabControl的第一个选项卡,以及对WPF数字墨水API的检查。 可以肯定的是,关于这项技术还有更多的话要说。但是,如果您感兴趣,您应该处于很好的位置,可以进一步深入研究该主题。接下来,您将学习如何使用WPF数据绑定。
介绍WPF数据绑定模型
控件通常是各种数据绑定操作的目标。简而言之,数据绑定是将控件属性连接到在应用程序生命周期中可能发生变化的数据值的行为。这样做可以使用户界面元素在代码中显示变量的状态。例如,您可以使用数据绑定来完成以下任务:
- 根据给定对象的布尔属性检查CheckBox控件
- 从关系数据库表中显示DataGrid对象中的数据
- 将Label连接到代表文件夹中文件数的整数
使用内部WPF数据绑定引擎时,必须注意绑定操作的源和目标之间的区别。如您所料,数据绑定操作的源是数据本身(例如,布尔属性或关系数据),而目标是使用数据内容的UI控件属性(例如,CheckBox或TextBox控件)。
如前面的示例所述,除了绑定到传统数据外,WPF还支持元素绑定。这意味着您可以基于复选框的选中属性绑定(例如)属性的可见性。您当然可以在WinForms中执行此操作,但是必须通过代码来完成。WPF框架提供了一个丰富的数据绑定生态系统,几乎可以完全在标记中处理它。这也使您可以确保源和目标的值中的任何一个更改时都保持同步。
构建数据绑定选项卡
使用文档大纲编辑器,将第二个选项卡的网格更改为StackPanel。现在,使用Visual Studio的“工具箱”和“属性”编辑器来构建以下初始布局:
|
|
请注意,ScrollBar对象(在此处名为mySB)已配置为1到100之间的范围。目标是确保在重新放置滚动条的拇指(或单击向左或向右箭头)时,Label将自动用当前值更新。当前,Label控件的Content属性设置为值“0”;默认值为0。 但是,您将通过数据绑定操作来更改此设置。
建立数据绑定
可以在XAML中定义绑定的粘合剂是{Binding}标记扩展。尽管您可以通过Visual Studio定义绑定,但是直接在标记中进行操作同样容易。将名为labelSBThumb的Label的Content属性编辑为以下内容:
|
|
请注意分配给标签的内容属性的值。{Binding}语句表示数据绑定操作。ElementName值表示数据绑定操作的源(ScrollBar对象),而Path表示要绑定到的属性,在这种情况下为滚动条的Value。
如果再次运行程序,您会发现在移动拇指时,标签的内容会根据滚动条的值进行更新!
DataContext属性
您可以使用另一种格式在XAML中定义数据绑定操作,在该格式中,可以通过将DataContext属性显式设置为绑定操作的源,从而突破{Binding}标记扩展所指定的值,如下所示:
|
|
在当前示例中,如果您要以这种方式修改标记,则输出将是相同的。鉴于此,您可能想知道何时要显式设置DataContext属性。这样做会有所帮助,因为子元素可以在标记树中继承其值。
这样,您可以轻松地将相同的数据源设置为一系列控件,而不必对多个控件重复一堆多余的“ {Binding ElementName = X,Path = Y}” XAML值。例如,假设您已将以下新按钮添加到此选项卡的StackPanel中(稍后您将看到为什么它如此之大):
|
|
您可以使用Visual Studio为多个控件生成数据绑定,但是可以尝试使用XAML编辑器手动输入修改后的标记,如下所示:
|
|
在这里,您可以直接在StackPanel上设置DataContext属性。因此,当您移动拇指时,不仅会看到Label上的当前值,而且还会看到Button的字体大小根据相同的值相应地增大和缩小(图25-22显示了一种可能的输出)。
格式化绑定数据
ScrollBar类型使用双精度表示拇指的值,而不是期望的整数(例如整数)。因此,当您拖动拇指时,您会在“标签”中找到各种浮点数(例如61.0576923076923)。 最终用户会发现这不太直观,因为他最有可能期望看到整数(例如61、62和63)。
如果要格式化数据,则可以添加ContentStringFormat属性,并传入自定义字符串和.NET格式说明符,如下所示:
|
|
如果格式说明中没有任何文本,则需要以空括号开头,这是XAML的例外顺序。例如,这使处理器知道下一个字符是文字,而不是绑定语句。这是更新的XAML:
|
|
注意: 如果要绑定控件的Text属性,则可以在绑定语句中添加StringFormat名称/值对。对于内容属性,只需将其分开。
使用IValueConverter进行数据转换
如果您需要做的不仅仅是格式化数据,还可以创建一个自定义类,该类实现System.Windows.Data命名空间的IValueConverter接口。此接口定义了两个成员,这些成员使您可以执行往返于目标和目的地的转换(在双向数据绑定的情况下)。定义此类后,可以使用它来进一步限定数据绑定操作的处理。
您可以使用值转换器在Label控件中显示整数,而不是使用format属性。为此,将一个新类(名为MyDoubleConverter)添加到项目类中。接下来,添加以下内容:
|
|
当值从源(ScrollBar)传输到目标(TextBox的Text属性)时,将调用Convert()方法。您将收到许多传入的参数,但是您只需为此转换操作传入的对象,这是当前double的值。您可以使用此类型将类型转换为整数并返回新数字。
将值从目标传递到源时(如果启用了双向绑定模式),将调用ConvertBack()方法。在这里,您只需直接返回值。 这样做可以让您在TextBox中输入浮点值(例如99.9),并在用户关闭控件时将其自动转换为整数值(例如99)。之所以发生这种“免费”转换,是因为在调用ConvertBack()之后再次调用了Convert()方法。如果仅从ConvertBack()返回null,则绑定似乎不同步,因为文本框仍将显示浮点数。
要在标记中使用此转换器,首先必须创建一个本地资源,该资源代表刚构建的自定义类。不必担心增加资源的机制; 接下来的几章将更深入地探讨该主题。在打开的Window标记之后添加以下内容:
|
|
接下来,将Label控件的绑定语句更新为以下内容:
|
|
现在,当您运行该应用程序时,您只会看到整数。
在代码中建立数据绑定
您也可以在代码中注册数据转换类。首先清理数据绑定选项卡中Label控件的当前定义,以使其不再使用{Binding}标记扩展名。
|
|
确保System.Windows.Data有一个using;然后在窗口的构造函数中,调用一个名为SetBindings()的新私有帮助器函数。在此方法中,添加以下代码(并确保从构造函数中调用它):
|
|
该函数唯一看起来可能有点过时的部分是对SetBinding()的调用。注意,第一个参数调用Label类的名为ContentProperty的静态只读字段。正如您将在本章稍后学习的那样,您将指定所谓的依赖项属性。就目前而言,只知道在代码中设置绑定时,第一个参数几乎总是需要您指定需要绑定的类的名称(在本例中为Label),然后调用基础属性后缀为Property的属性。无论如何,运行该应用程序都说明了Label仅打印出整数。
构建数据网格选项卡
前面的数据绑定示例说明了如何配置两个(或多个)控件以参与数据绑定操作。尽管这很有用,但也可以从XML文件,数据库数据和内存中对象绑定数据。要完成此示例,您将设计选项卡控件的最终选项卡,以便它显示从AutoLot数据库的“库存”表获得的数据。
与其他选项卡一样,首先将当前的Grid更改为StackPanel。为此,可以使用Visual Studio直接更新XAML。现在,在新StackPanel中定义一个名为gridInventory的DataGrid控件,如下所示:
|
|
使用NuGet程序包管理器将实体框架添加到您的项目。接下来,右键单击该解决方案,选择“添加”➤“现有项目”,然后从第22章添加AutoLotDAL项目。从WpfControlsAndAPIs项目中添加对AutoLotDAL项目的引用。将连接字符串添加到App.config文件中。我的如下:
|
|
打开您的窗口的代码文件,并添加一个名为ConfigureGrid()的最终帮助程序函数;确保从构造函数调用此函数。 假设您确实导入了AutoLotDAL命名空间,您所需要做的就是添加几行代码,如下所示:
|
|
LINQ查询会创建一个新的匿名对象,该对象不包含Timestamp属性,因为对于用户而言,查看该字段没有任何价值。 如果要运行项目,则会看到数据填充网格。如果要使网格更加美观,可以使用Visual Studio属性窗口来编辑网格以使其更具吸引力。
总结了当前示例。在后面的章节中,您将看到其他一些正在使用的控件。但是,此时,您应该对在Visual Studio中构建UI以及手动使用XAML和C#代码的过程感到满意。
了解依赖属性的作用
像任何 .NET API一样,WPF使用 .NET 类型系统的每个成员(类,结构,接口,委托,枚举)和每个类型成员(属性,方法,事件,常量数据,只读字段等)。但是,WPF还支持称为依赖属性的独特编程概念。
就像“普通” .NET属性(在WPF文献中通常称为CLR属性)一样,可以使用XAML声明式设置或在代码文件中以编程方式设置依赖项属性。此外,依赖属性(如CLR属性)最终存在以封装类的数据字段,并且可以配置为只读,只写或读写。
使事情变得更有趣的是,几乎在每种情况下,您都会幸福地意识到实际上已设置(或访问)了一个依赖属性而不是CLR属性! 例如,WPF控件从FrameworkElement继承的Height和Width属性以及从ControlContent继承的Content成员实际上都是依赖项属性。
|
|
鉴于所有这些相似之处,为什么WPF会为这种熟悉的概念定义一个新术语?答案在于在类中如何实现依赖项属性。您将很快看到一个编码示例;但是,从高层次来看,所有依赖项属性都是按以下方式创建的:
- 首先,定义依赖项属性的类在其继承链中必须具有DependencyObject。
- 单个依赖项属性在DependencyProperty类型的类中表示为公共,静态,只读字段。按照惯例,通过在CLR包装器的名称后添加单词Property来命名该字段(请参见最后的要点)。
- 通过对DependencyProperty.Register()的静态调用来注册DependencyProperty变量,该调用通常发生在静态构造函数中,或者在声明该变量时内联。
- 最后,该类将定义XAML友好的CLR属性,该属性将调用DependencyObject提供的方法以获取和设置值。
一旦实现,依赖项属性将提供各种WPF技术所使用的许多强大功能,包括数据绑定,动画服务,样式,模板等。简而言之,依赖属性的动机是提供一种基于其他输入值来计算属性值的方法。下面列出了其中一些关键的好处,这些好处远远超出了使用CLR属性发现的简单数据封装的好处:
- 依赖项属性可以从父元素的XAML定义继承其值。例如,如果您在窗口的开始标记中为FontSize属性定义了一个值,则默认情况下,该窗口中的所有控件的字体大小都相同。
- 依赖关系属性支持通过其XAML范围内包含的元素来设置值的功能,例如Button设置DockPanel父级的Dock属性。 (请记住,附加属性之所以能做到这一点,是因为附加属性是依赖属性的一种形式。)
- 依赖属性允许WPF基于多个外部值来计算一个值,这对于动画和数据绑定服务而言很重要。
- 依赖关系属性为WPF触发器提供基础结构支持(在处理动画和数据绑定时也经常使用)。
现在请记住,在许多情况下,您将以与普通CLR属性相同的方式与现有的依赖项属性进行交互(这要归功于XAML包装器)。在上一节讨论了数据绑定的部分中,您看到了如果需要在代码中建立数据绑定,则必须在作为操作目标的对象上调用SetBinding()方法,并指定它将操作的依赖项属性。像这样:
|
|
在第27章的代码中检查如何启动动画时,将看到类似的代码。
唯一需要构建自己的自定义依赖项属性的时间是在编写自定义WPF控件时。例如,如果要构建定义四个自定义属性的UserControl,并且希望这些属性在WPF API中很好地集成,则应使用依赖项属性逻辑来编写它们。
具体来说,如果您的属性需要成为数据绑定或动画操作的目标,则该属性是否必须在更改后进行广播,是否必须能够以WPF样式用作Setter或是否必须能够要从父元素接收其值,普通的CLR属性是不够的。如果要使用普通的CLR属性,则其他程序员可能确实可以获取并设置一个值。但是,如果他们尝试在WPF服务的上下文中使用您的属性,则事情将无法按预期进行。因为您永远无法知道其他人可能如何与自定义UserControl类的属性进行交互,所以您应该养成在构建自定义控件时始终定义依赖项属性的习惯。
检查现有的依赖项属性
在学习如何构建自定义依赖项属性之前,让我们看一下如何在内部实现FrameworkElement类的Height属性。相关代码如下所示(并附有我的评论):
|
|
如您所见,依赖项属性需要普通CLR属性中的大量附加代码!实际上,依赖关系甚至比您在这里看到的复杂(幸运的是,许多实现比Height更简单)。
首先,请记住,如果一个类要定义一个依赖项属性,则它必须在继承链中具有DependencyObject,因为这是定义CLR包装器中使用的GetValue()和SetValue()方法的类。因为FrameworkElement是一个DependencyObject,所以可以满足此要求。
接下来,回想一下将保留属性实际值的实体(在Height情况下为double)表示为DependencyProperty类型的公共,静态,只读字段。按照惯例,应始终通过在相关CLR包装器的名称后加上单词Property来命名该字段的名称,如下所示:
|
|
给定依赖项属性声明为静态字段,它们通常在类的静态构造函数中创建(并注册)。DependencyProperty对象是通过调用静态DependencyProperty.Register()方法创建的。这种方法已经被重载了很多次。但是,在Height的情况下,DependencyProperty.Register()的调用方式如下:
|
|
概要
本章从控件工具包的概述和布局管理器(面板)的作用开始,研究了WPF控件的几个方面。第一个示例使您有机会构建一个简单的文字处理器应用程序,该应用程序说明了WPF的集成拼写检查功能,以及如何构建具有菜单系统,状态栏和工具栏的主窗口。
更重要的是,您研究了如何使用WPF命令。回想一下,您可以将这些不可知的事件附加到UI元素或输入手势,以自动继承现成的服务(例如剪贴板操作)。
您还学到了很多有关在XAML中构建复杂UI的知识,并且同时了解了WPF Ink API。您还收到了WPF数据绑定操作的介绍,包括如何使用WPF DataGrid类显示自定义AutoLot数据库中的数据。
最后,您研究了WPF如何对传统.NET编程原语(特别是属性和事件)施加独特的影响。如您所见,依赖项属性使您可以构建可以集成到WPF服务集(动画,数据绑定,样式等)中的属性。与此相关的是,路由事件为事件提供了一种在标记树上向上或向下流动的方式。
第26章 WPF图形渲染服务
在本章中,您将研究WPF的图形渲染功能。如您所见,WPF提供了三种独立的方式来呈现图形数据:形状,工程图和视觉效果。了解每种方法的利弊之后,您将开始使用System.Windows.Shapes中的类学习交互式2D图形的世界。之后,您将看到图形和几何形状如何允许您以更轻便的方式渲染2D数据。最后但并非最不重要的一点,您将学习视觉层如何为您提供最大的功能和性能。
在此过程中,您将探索许多相关主题,例如如何创建自定义画笔和笔,如何将图形转换应用于渲染以及如何执行点击测试操作。 特别是,您将看到Visual Studio的集成工具和名为Inkscape的其他工具如何简化您的图形编码工作。
注意: 图形是WPF开发的关键方面。即使您没有构建大量图形应用程序(例如视频游戏或多媒体应用程序),当您使用诸如控制模板,动画和数据绑定自定义之类的服务时,本章中的主题也至关重要。
概要
由于Windows Presentation Foundation是图形密集型GUI API,因此我们获得了多种呈现图形输出的方法也就不足为奇了。本章以检查WPF应用程序可以采用的三种方式(形状,图形和视觉效果)开始,并讨论了各种渲染图元,例如画笔,笔和变换。
请记住,当您需要构建交互式2D渲染时,形状使过程非常简单。但是,可以通过使用图形和几何图形以更好的方式渲染静态非交互式渲染,而可视层(仅在代码中可访问)可为您提供最大的控制和性能。
第27章 WPF资源,动画,样式和模板
本章向您介绍三个重要的(且相互关联的)主题,它们将加深您对Windows Presentation Foundation(WPF)API的理解。业务的首要任务是学习逻辑资源的角色。 正如您将看到的,逻辑资源(也称为对象资源)系统是在WPF应用程序中命名和引用常用对象的一种方式。 尽管逻辑资源通常是用XAML编写的,但是它们也可以在过程代码中定义。
接下来,您将学习如何定义,执行和控制动画序列。尽管您可能会想到,但WPF动画不仅限于视频游戏或多媒体应用程序。在WPF API下,动画可以像使按钮在获得焦点或扩展DataGrid中所选行的大小时看起来发光一样微妙。了解动画是构建自定义控件模板的关键方面(如本章稍后所述)。
然后,您将探索WPF样式和模板的作用。与使用CSS或ASP.NET主题引擎的网页非常相似,WPF应用程序可以为一组控件定义通用的外观。您可以在标记中定义这些样式,并将它们存储为对象资源以备后用,也可以在运行时动态地应用它们。最后一个示例将教您如何构建自定义控件模板。
概要
本章的第一部分研究了WPF的资源管理系统。您首先研究如何使用二进制资源,然后研究对象资源的作用。如您所知,对象资源被称为XAML的Blob,可以将其存储在各个位置,以便重用内容。
接下来,您了解了WPF的动画框架。在这里,您有机会使用C#代码以及XAML创建了一些动画。您了解到,如果您在标记中定义了动画,则可以使用Storyboard元素和触发器来控制执行。然后,您研究了WPF样式的机制,该机制大量使用了图形,对象资源和动画。
您检查了逻辑树和可视树之间的关系。逻辑树基本上是您编写的用于描述WPF根元素的标记的一一对应关系。在此逻辑树的后面是一棵更深的可视树,其中包含详细的渲染指令。
然后检查了默认模板的作用。请记住,在构建自定义模板时,实际上是从控件的可视树中剥离所有(或部分)并将其替换为您自己的自定义实现。
第28章 WPF通知,验证,命令和MVVM
本章将通过介绍支持Model-View-ViewModel(MVVM)模式的功能来结束对WPF编程模型的研究。第一部分介绍Model-View-ViewModel模式。接下来,您将通过可观察模型和可观察集合来了解WPF通知系统及其对可观察模式的实现。使UI中的数据准确地描绘出数据的当前状态会自动显着改善用户体验,并减少旧技术(例如WinForms)中为达到相同结果所需的手动编码。
在Observable模式的基础上,您将检查将验证添加到应用程序中的机制。验证是任何应用程序的重要组成部分-不仅要让用户知道有什么问题,还要让他们知道有什么问题。为了通知用户错误是什么,您还将学习如何将验证合并到视图标记中。
接下来,您将更深入地研究WPF命令系统并创建自定义命令来封装程序逻辑,就像在第25章中使用内置命令所做的一样。创建自定义命令有几个优点,包括(但不限于)启用代码重用,逻辑封装和关注点分离。
最后,您将在一个示例MVVM应用程序中将所有这些结合在一起。
介绍Model-View-ViewModel
在深入了解WPF中的通知,验证和命令之前,最好了解本章的最终目标,即Model-View-ViewModel模式(MVVM)。MVVM源自Martin Fowler的Presentation Model模式,它利用本章讨论的XAML特定功能来使您的WPF开发更快,更干净。名称本身描述了模式的主要组成部分:模型,视图,视图模型。
模型
模型是数据的对象表示。在MVVM中,模型在概念上与数据访问层(DAL)中的模型相同。有时它们是相同的物理类别,但是对此没有要求。阅读本章时,您将学习如何决定是否可以使用DAL模型或是否需要创建新模型。
模型通常通过数据注释和INotifyDataErrorInfo接口利用内置的(或自定义的)验证,并被配置为可观察到以绑定到WPF通知系统。您将在本章后面看到所有这些内容。
视图
视图是应用程序的UI,它的设计非常轻巧。想一想直通车餐厅的菜单板。该板显示菜单项和价格,并且具有一种机制,使用户可以与后端系统进行通信。但是,除非特别针对用户界面逻辑,例如在黑暗中打开灯,否则板子上没有内置任何智能。
MVVM视图在开发时应牢记相同的目标。任何智能都应内置到其他应用程序中。代码隐藏文件(例如MainWindow.xaml.cs)中的唯一代码应与操作UI直接相关。它不应基于业务规则或将来需要保留的任何内容。尽管这不是MVVM的主要目标,但完善的MVVM应用程序通常在其背后的代码中只有很少的代码。
视图模型
在WPF和其他XAML技术中,视图模型有两个作用。
- 视图模型为视图所需的所有数据提供了一个单一的停留点。这并不意味着视图模型负责获取实际数据;相反,它只是将数据从数据存储区移动到视图的一种传输机制。通常,视图和视图模型之间存在一对一的关联,但是存在架构差异,并且里程可能会有所不同。
- 第二项工作是充当视图的控制器。就像菜单板一样,视图模型从用户那里得到指示,并中继调用相关代码以确保采取了正确的措施。此代码通常以自定义命令的形式出现。
贫血模型或贫血视图模型
在WPF成立之初,当开发人员仍在研究如何最好地实施MVVM模式时,就在何处实施验证和Observable模式等项目进行了大量(有时甚至是激烈的)讨论。 一个阵营(贫血的模型阵营)认为,所有这些都应该放在视图模型中,因为将这些功能添加到模型中可以打破关注点的分离。另一个阵营(贫血的视图模型阵营)认为这应该全部包含在模型中,因为这样可以减少代码重复。
真正的答案当然取决于它。在模型类上实现INotifyPropertyChanged,IDataErrorInfo和INotifyDataErrorInfo时,这将确保相关代码接近代码的目标(如本章所述),并且对于每个模型仅实现一次。话虽这么说,有时您的视图模型类需要自己开发为可观察对象。最终,您需要确定最适合您的应用程序的内容,而又不会使您的代码过于复杂或牺牲MVVM的优势。
注意:有多种可用于WPF的MVVM框架,例如MVVMLite,Caliburn.Micro和Prism(尽管Prism不仅仅是一个MVVM框架)。本章讨论MVVM模式以及WPF中支持实现该模式的功能。我将它留给读者(读者),以研究不同的框架并选择最能满足您应用程序需求的框架。
WPF绑定通知系统
WinForms绑定系统中的一个重大缺陷是缺少通知。如果以编程方式更新视图中表示的数据,则还必须以编程方式刷新UI,以使其保持同步。这导致在控件上对Refresh()的大量调用,通常要比为了安全起见绝对要多。尽管通常不会包含太多对Refresh()的调用而引起的重大性能问题,但如果包含的调用过多,则可能会对用户的体验造成负面影响。
基于XAML的应用程序中内置的绑定系统通过使您可以将数据对象和集合开发为可观察对象,从而将它们挂接到通知系统中,从而解决了此问题。每当属性的值在可观察模型上更改或集合在可观察集合上更改(例如,添加,删除或重新排序项目)时,都会引发事件(NotifyPropertyChanged或NotifyCollectionChanged)。 绑定框架会自动侦听那些事件的发生,并在激发它们时更新绑定的控件。更好的是,作为开发人员,您可以控制哪些属性引发通知。听起来很完美吧?好吧,这还不是很完美。如果您手动完成所有工作,那么为可观察模型进行设置可能涉及大量代码。幸运的是,正如您将很快看到的,有一个开源框架使其变得更加简单。
可观察的模型和集合
在本部分中,您将创建一个使用可观察模型和集合的应用程序。首先,创建一个名为WpfNotifications的新WPF应用程序。该应用程序将是主从表单,允许用户使用ComboBox选择特定的汽车,然后该汽车的详细信息将显示在下面的TextBox控件中。将MainWindow.xaml更新为以下标记:
|
|
Grid控件上的IsSharedSizeScope标记设置子网格以共享尺寸。标记为SharedSizeGroup的ColumnDefinitions将自动调整为相同的宽度,而无需进行任何编程。在此示例中,如果将“宠物名称”标签更改为更长的时间,则“车辆”列(位于不同的“网格”控件中)的大小将与之匹配,从而使窗口的外观保持整洁。
接下来,在解决方案资源管理器中右键单击项目名称,选择“添加”➤“新建文件夹”,然后将文件夹命名为“Models”。在这个新文件夹中,创建一个名为Inventory的类。初始类在这里列出:
|
|
添加绑定和数据
下一步是为控件添加绑定语句。请记住,数据绑定语句围绕数据上下文,并且可以在控件本身或父控件上进行设置。在这里,您将在DetailsGrid上设置上下文,因此包含的每个控件都将继承该数据上下文。
将DataContext设置为ComboBox的SelectedItem属性。将包含详细信息控件的网格更新为以下内容:
|
|
DetailsGrid中的文本框将显示所选汽车的各个属性。将适当的文本属性和相关绑定添加到TextBox控件,如下所示:
|
|
最后,将数据添加到ComboBox。在MainWindow.xaml.cs中,创建一个新的清单记录列表,并将ComboBox的ItemsSource设置为该列表。另外,为Notifications.Models命名空间添加using语句。
|
|
运行应用程序。您会看到车辆选择器有两辆车可供选择。选择其中之一,文本框将自动填充车辆详细信息。更改其中一辆车的颜色,选择另一辆车,然后返回到您编辑的车。您会看到新颜色的确仍然附着在车辆上。这没什么了不起的;您已经在前面的示例中看到了XAML数据绑定的强大功能。
第VIII部分 ASP.NET
第29章 介绍 ASP.NET MVC
本章介绍ASP.MVC,这是一个用于创建Web应用程序的.NET框架。ASP.NET MVC从用户社区(特别是ALT.NET运动)发展而来,要求建立一个更紧密地遵循HTTP租户,更具可测试性并且遵循关注点分离的框架。
注意:本书的上一版详细介绍了 ASP.NET Web窗体。在此版本中,“Web表单”章节已移至附录,以供您参考。
本章从对MVC模式的简要说明开始,然后深入探讨如何创建MVC项目。在对MVC和生成的脚手架有了扎实的了解之后,您将为CarLotMVC构建清单页面。
第30章 介绍 ASP.NET Web API
上一章介绍了 ASP.NET MVC,模式和 .NET实现。本章向您介绍 ASP.NET Web API,这是一个主要建立在MVC机箱上的服务框架,它共享许多概念,包括路由,控制器和操作。ASP.NET Web API允许您利用您的MVC知识来构建RESTful服务,而无需进行WCF(第23章)所需的配置和检查。您将创建一个称为CarLotWebAPI的RESTful服务,该服务公开清单记录上的所有创建,读取,更新和删除(CRUD)功能。最后,您将通过更新CarLotMVC以使用CarLotWebAPI作为数据源,而不是直接调用数据访问层来完成本章。
注意: 本章将不重复上一章的内容,而是重点介绍使用 ASP.NET Web API项目创建RESTful服务的细节。如果您不熟悉 ASP.NET MVC,建议您在继续本章之前先阅读第29章。
第IX部分 .NET Core
第31章 .NET Core的哲学
2016年6月27日,Microsoft宣布发布 .NET Core 1.0版,这是一个遍及全球的.NET开发人员的革命性新平台。这个新平台是同时为Windows,macOS和Linux发布的,它基于C#和 .NET Framework,您已在最近30章中学习过。初始版本包括 .NET Core运行时,ASP.NET Core和Entity Framework Core。从那时起,又有两个附加版本:1.1和2.0(当前版本)。
除了跨平台部署功能,还引入了另一个重大更改。.NET Core平台和相关框架是完全开源的。它们不仅是开放源代码,您可以在其中查看和使用代码,而且是功能完善的开放源代码项目。拉取请求被接受,并且实际上是预期的。开发社区做出了巨大贡献,超过10,000名开发人员为 .NET Core的初始版本做出了贡献。Microsoft团队是一个相对较小的团队,他们开发.NET和相关框架,而现在,更广泛的开发社区可以提供其他功能,性能改进和错误修复。这个开源计划不仅涵盖软件,还涵盖文档。实际上,本书前面各章中提供的文档链接都指向位于 http://docs.microsoft.com的新的开源文档平台。
本章向您介绍 .NET Core的原理。然后,其余三章将介绍Entity Framework Core(第32章)和 ASP.NET Core(第33至34章)。
第32章 实体框架核心介绍
微软发布了Entity Framework Core以及 .NET Core和 ASP.NET Core。EF Core是对EF的完全重写,它利用了.NET Core的所有优点。但是,作为重写,没有时间在 .NET Core 1.0发行的时间表内重新创建EF 6中的所有功能。根据功能比较,很简单地说,第一个版本较弱。确实没有足够的功能来将其视为可投入生产的产品。
随着EF Core 1.1的发布,这一切都发生了变化。该版本带来了EF 6在线上的更多功能,并且还引入了新功能。虽然还没有完成,但对于ASP来说已经足够了。.NET开发人员开始在实际项目中使用它。EF Core 2.0在线提供了更多功能,提高了性能,并扩展了ASP.NET之外的用途。如果EF Core 1.1的功能不足以满足您的需求,那么EF Core 2.0很有可能。
注意: 本章假定您具有Entity Framework 6的使用知识。否则,请在继续之前阅读第22章。
本章的第一部分比较了EF 6和EF Core 2的功能。本章的其余部分显示了如何构建AutoLotDAL的EF Core 2版本,在接下来的两章中将使用 ASP.NET Core 2。
第33章 介绍 ASP.NET Core Web应用程序
这是介绍 ASP.NET Core的两章中的第一章。如第31章所述,随着 ASP.NET Core的发布,ASP.NET MVC和 ASP.NET Web API最终已完全合并为一个框架。MVC 5和Web API之间的细微差别(有些不是那么细微)已经消失。巧合的是,命名差异也是如此。它只是 ASP.NET Core。
ASP.NET Core建立在 .NET Core之上,以使用C#(当然还有Razor,HTML,JavaScript等)提供Web应用程序和RESTful服务的跨平台开发和部署。与EF Core一样,ASP.NET Core也不仅仅是端口。这是重建。这使团队可以大量清理代码库,集中精力于性能和功能增强。
本章从为Web应用程序构建 ASP.NET Core解决方案开始,然后介绍 ASP.NET Core的功能,并从消除CarLotMVC应用程序的画龙点睛结束。
注意: 本章假定您具有 ASP.NET MVC 5的工作知识。否则,请在继续操作之前先阅读第28章。
第34章 介绍 ASP.NET Core服务应用程序
上一章在构建AutoLotMVC_Core2 Web应用程序时详细介绍了 ASP.NET Core。本章通过创建AutoLotAPI_Core2 RESTful服务来完成 ASP.NET Core的介绍。
您将首先使用 ASP.NET Core Web API模板(是的,即使现在是全框架,该模板仍被命名为Web API)来创建解决方案和项目。然后,您将检查项目的组织和启动选项,更新NuGet程序包,并从第32章中添加AutoLotDAL_Core2数据访问库。接下来,您将检查第33章中 ASP.NET Web API 2.2未涉及的更改,然后完成RESTful服务。
最后,您将更新AutoLotMVC_Core2 Web应用程序,以将AutoLotAPI_Core2服务用作后端数据源,而不是使用AutoLotDAL_Core2。
注意: 本章假定您已经具有 ASP.NET Web API 2.2(从第30章开始)的知识,并且已经阅读并通读了 ASP.NET Core的第33章。如果不是这种情况,请先阅读第28和33章,然后再继续。