首页 > 代码库 > 自学WPF系列(1)

自学WPF系列(1)

介绍

使用WPF工作6个多月了,是时候写一些WPF的基础知识了。在这个主题上我已经写了几篇文章了。他们都是基于处理一些具体的问题而完成的。现在我抛砖引玉,并让您理解如何/为什么WPF作为革命性的UI开发走向了我们。

由于这是一篇适合初学者和中级水平的程序员的文章,我将尽量给出尽可能多的基本的例子。

Windows Presectation Foundation

正如名字所示,WPF实际上是.NET Framework3.0引入的几个framework.它实际上是提出了一套新的类和程序集并允许我们更加有效和灵活的来编程。它是使用Direct3D渲染并使用显卡来展现在显示器上。因此这种形式的绘图是很平滑的并且有好好利用您的机器中安装的硬件能力的机会。针对传统的GDI应用程序,它不能使用先进的图形处理能力,因此Windows Forms应用程序和WPF应用程序相比总是很低效。还有另外一个很重要的事情我必须标注一下,GDI Windows Form应用程序是利用操作系统控件来建立它的应用程序。因此它基本上很难在你的应用程序中自定义他们。WPF控件实际上是在你的屏幕上绘制的,因此如果需要的话,你可以完全自定义控件并且修改他们的行为。

WPF 的特点

WPF有很多优势,让我先介绍几个:

与设备无关的像素(DPI)

WPF为应用程序引入了与设备无关的DPI设置来构建应用程序。对于一个窗口来说,对于计算在屏幕中能够绘制多少DPI是非常重要的。这通常是由应用程序运行的硬件设备和操作系统和设备应用的DPI设置决定的。任何用户都可以自定义这些设置,因此使得引用程序看起来很糟糕。Windows Form 应用程序使用基于像素的方法以便在DPI设置变更的时候,每个控件都能够改变它的尺寸和外观。

WPF解决了这个问题,并且使它能够独立于计算机的DPI设置,让我们看看它是怎么做到的:

比方说,如图中所示你画了一个框,它是1英寸长的96DPI屏幕,现在如果你看到120DPI设置的相同的应用程序,这个框就会变小。这是因为,我们在屏幕中看到的东西都完全依赖于DPI设置。

在WPF中,这种修改是采用基于密度的方法。这意味着当像素的密度被修改使,该元素将相应的调整他们,从而使得WPF应用程序的项目是与设备无关的像素。正如你在途中看到的,这个WPF中的控件在它需要更多的像素的情况例如120DPI下,应用程序合适的调整它,使得它大小保持不变。

内置支持的图形和动画

WPF应用程序作为DirectX的环境渲染,它具有图形和动画功能的主要支撑能力。有一组单独的类来实现处理动画效果和图形。在屏幕中绘制的图形也是基于矢量的也是面向对象的。这就是说,当你在WPF应用程序中绘制一个矩形,你能够很容易的从屏幕中删除它以为Rectangle实际上是一个你一直都在掌控中的对象。在传统的基于Windows的应用程序,当你绘制了一个矩形,你不能单独的选择它。因此WPF的程序设计和传统的Windows程序设计相比是完全不同的,也是更加复杂的。我们将在后面讨论图形和动画的更多细节。

重新定义样式和控件模板

除了图形和动画方面的能力,WPF中还附带了巨大的灵活性来定义样式和控件模板。样式基于的技术就和你也许碰到过的CSS差不多,它是一组定义。它定义控件渲染到屏幕上时,看起来是个什么样子。在传统的Windows应用程序当中,样式和每个控件是紧耦合的,因此你需要为每个单独的控件定义颜色,样式等等使得它看起来不同。对WPF来说,样式和UIElement是完全分离的。一旦你定义了一个样式,你可以这个样式应用到这个元素上市的你可以改变它的外观和风格。

通常我们处理的大多数的UIElemnet实际上都不仅仅是一个Element在用。WPF引用了一个模板的新概念,你可以使用它来重新定义整个控件本身。例如上,你有一个CheckBox,它有一个Rectangle和ContentPresenter(TextBox展现的地方).然后你可以重新定义你的CheckBox然后把一个ToggleButton放在里面,使得这个Check看起来更像是个ToggleButton而不是Rectangle。这个是非常有趣的,在后续的文章中我们将探究更多样式和控件模板的细节。

每个控件都可以使用的资源

WPF中另一个重要的特点是可访问的资源。在传统的Windows应用程序中,重新定义一个样式是非常麻烦的。因此如果你有1000个Button,你想要给每个Button的颜色设置为Gold,你需要创建1000个对象来给每个单独的元素分配。因此这样使得资源看起来很庞大。

在WPF中,你能够存储样式,控件,动画,甚至吧一个对象做为资源。然而每个资源在Form加载的时候,都要声明一下,你可以进一步把资源分配给控件。你可以在一个单独的ResourceDictionary文件中维护一个全局的样式。这里的样式可以被整个应用程序使用。因此WPF应用程序很容器被应用某个主题。

新的系统属性和绑定能力

在接下来的步骤中,我必须要介绍一下WPF中的新的属性系统。每个WPF元素都定义了大量的依赖属性。依赖属性比普通属性有更强的能力。因此使得我们定义的新属性,可以很容易的给我们任何想要注册的对象注册属性。浙江给每个对象都关联了一个相同的观察者。因为每个元素都是从DependencyObject继承的,每个元素都包含了Dependency Observer。一旦你注册了一个变量为依赖属性,在Observer上它将创建一个区域来把Control和它的值关联起来。

什么是XAML

根据定义,XAML是一种基于XML的生命是标记语言,用于指定和设置类的特性。换句话说,XAML是一种由WPF,Silverlight或者其他可以声明自己的类的应用程序使用的语言。因此,在你的应用程序中你可以为任何类定义变量,定义属性并直接使用。当渲染应用程序的时候XAML解析器将会自动转化和创建实际的对象。

XAML通常是用来在元素和对象中静态和可视化方面定义UI布局的。我们不能使用XAML来定义程序流。因此即使有了XAML的强大的能力,它实际上不是编程语言。它是用来为应用程序设计UI的。因此XAML使用C#,VB.NET等其他编程语言在背后定义代码的逻辑。

WPF架构

对于每一个新技术,对它的架构有个清晰的概念都是非常必要的。因此在开始构建你的应用程序之前,你必须要掌握几个概念。如果你不喜欢了解WPF的一些细节,请跳过这一段。正如前面提到过的,WPF实际上是构架整个Framework的一组程序集。这些程序集可以这样分类:

托管层

非托管层

核心API

托管层:WPF的托管层是使用一些程序集构建的。这些程序集构建了WPF的Framework来与底层的非托管API通信,以便渲染它的内容。构成WPF框架的一些程序集是:

PresentationFramework.dll:创建顶层的布局容器,控件,窗体,样式等等。

PresentationCore.dll:它拥有基本的类型,例如派生于PresentationFramework.dll中的所有形状和控件中的UIElement和可视化元素。

WindowsBase.dll:他们拥有更多能够在WPF环境外面使用的能力的基本的元素,例如Dispatcher, Dopendency,我将稍后逐一介绍他们。

非托管类型(milcore.dll):WPF中的非托管层称为milcore或者是媒体整合库核心。它基本上是处理WPF更高层次的对象的处理,例如布局容器,按钮,动画等等来达到Direct3D的预期。它是WPF主要的渲染引擎。

WindowsCodecs.dll:这个是WPF应用程序中用来处理图像处理支持的更低层次的API。WindowsCodecs.dll包括很多将图像转换为矢量图形的编码/解码器,这些图像然后被渲染到屏幕上。

Direct3D:这个是WPF图形渲染的低层次的API。

User32:他是每个程序都要使用的主要的核心API。它实际是管理内存和进程分离的。

GDI&Device Drivers :GDI和设备驱动是操作系统特有的,它也被用来让应用程序访问底层的API。

从上面的途中,就如上面我们讨论的,你可以看到不同的Framework之间的元素进行通信有什么不同。

在继续探索之前需要知道的几件事情

在开始构建WPF应用程序之前,有几件事情,你必须要知道。Dispatcher和Thread Affinity意味着什么?当WPF应用程序启动时,它是家上是自动创建了两个线程。一个是渲染线程,这个对程序员是隐藏的,因此在你的程序中你不能直接使用渲染线程。另外一个是Dispatcher Thread,实际上它包含了所有的UI元素。换句话说,你可能会想Dispatcher是实际上就是UI Thread,在WPF应用程序中它约束了所有创建的元素。相反,WPF需要所有的UI元素都与Dispather Thread 关联起来。这就是所谓的线程关联。因此你不能在其他任何线程更改在Dispatcher线程中创建的元素。它遵循Win32 API相同的限制。然而,它允许你基于API去互操作任何WPF组件为HWND。Dispatcher是一个处理线程关联的类,实际上所有的元素都是通过渠道的优先消息循环来处理的。每一个UIElement都是从定义了一个叫做dispatcher属性的DispatherObject对象继承的,这个dispather属性指向了UI Thread。因此从其他的线程,如果你想要调用或者访问UI组件,你需要通过Dispatcher Thread来调用。DispatcherObject 实际上有两个主要的职责,来检查和确认是否有访问对象的权限。
什么是可视化树和逻辑树?

每个编码样式都包含一些不同种类的逻辑树,从而组成了整个程序。逻辑树包含在XAML中列出来的元素,因此他们只包含您在XAML中声明的控件。

另外的可视化树, 包括构成单个控件的组成部分。你通常不需要直接处理可视化树。但是你应该知道每个控件是怎么构成的,因此使用它将更容易构建自定义模板。

我个人在使用它之前这哦你是喜欢看看可视化树。ExpressionBuilder是一个能够生成实际控件的一个工具。

为什么要有路由事件?

在C#语言中RoutedEvent是很新的, 但是对那些从JavaScript/Web 技术过来的人说,你可能在你的浏览器中发现了它。实际上有两种RoutedEvent,一种是通过VisualTree元素到另外一个元素冒泡,另外一个是在VisualTree中的元素隧道。当然也有不通过冒泡和隧道的直接路由事件。

当调用了一个注册了的路由事件,它通过冒泡/隧道可视化的元素,然后在VisualTree内逐个元素的调用被注册的所有关联的RoutedEventHandler。

两者之间的区分,WPF用这样的方法来划分,Preview**作为隧道路由事件,**作为冒泡事件。例如,IsPreviewMouseDown是当鼠标抬起时在可视的元素中的隧道事件。因此在IsPreviewMouseDown时,鼠标按下最外层的元素首先被调用;在MouseDown时,最里面的元素的MouseDown事件将被调用。

为什么要使用DependencyObject呢?

每个WPF控件都是从DependencyObject继承来的。DependencyObject是支持依赖属性的类,属性系统是WPF中新建的。每个对象都是从DependencyObject继承而来,因此它可以关联它自己支持WPF内置的各种特性,例如EventTriggers, PropertyBindings, Animations等等。

每个DependyencyObject实际上有个观察者列表,它声明三个方法ClearValue, SetValue, GetValue来添加/编辑/删除这些属性。因此当你用SetValue存储一些东西的时候,DependencyProperty将仅仅创建它自己。这也是资源节约的一种方式。我们还将在其他的文章中对DependencyProperty进行详细讨论。

针对硬件加速和图形渲染在WPF中怎么样呢 ?

另外一个你应该知道的很重要的事情是,你应该知道WPF的图形是怎么渲染的。事实上WPF渲染会自动检测当前的操作系统支持多少硬件加速并据此来调整它自己。图形渲染能检测合适的层来相应的渲染输出。

针对硬件渲染来说,影响很大的几件事情是:

1.Video RAM:这将决定应用程序用来渲染输出的缓存大小和数量

2.Pixel Shader:这是一个图像工具用来处理每个像素上的加速效果

3.Vertex Shader:这是一个图形处理工具,对输出的顶点进行数学计算。他们也用来在3D环境中对对象添加特殊的效果。

4.MultiTexture Blending:这是一个特殊函数, 它允许您将两个或两个以上的纹理引用在一个对象上。

现在WPF的渲染引擎来决定哪一层是适合当前应用程序然后应用合适的渲染层

TIER 0:没有硬件图形加速发生,一切都是使用软件渲染的。任何版本的Direct 9或更少的版本能够呈现这样的输出。

TIER 1:部分硬件和软件渲染,你也许使用DirectX9或者使用比这更高的层。

TIER 2:全硬件加速。DirectX9或者以上都可以渲染该输出。

对象层次结构

在WPF控件中有几个对象。让我们逐个进行讨论,如图所示(抽象类用椭圆标注,实体类用矩形标注)

DispatcherObject:所有的WPF控件的基类,在UI Thread中进行维护

DependencyObject:构建依赖属性的观察者

Visual:连接托管库和milcore

UIElement:添加WPF的特性支持,例如layout, input, event 等等。

FrameworkElement: 继承自UIElement

Shape:所有基本形状的基类

Control:和用户交互的UI对象。能够利用模板更改它的外观

ContentControl:所有具有单一内容的基类

ItemsControl:所有显示集合的控件的基类

Panel:所有的容器的基类,容器中可以防止一个或多个控件

构建你的第一个WPF应用程序

现在是时候开始创建你第一个WPF应用程序了。要创建程序,首先让我们打开Visual Studio 2008/2010/2012/2013,这个例子,我用了Visual Studio 2008.创建一个新的项目。你将看到一个新的窗体。XAML看起来应该像这样:

<Window x:Class="FirstWindowsApplication.Window1"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  x:Name="Window1"  Title="Window1" Height="300" Width="300">   <Grid>   </Grid></Window>

这里生成了一个空白的窗口。Height/Width代表窗体的大小。Title确定显示在窗口的标题栏上的文本。每个在XAML中的控件都应该利用x:Name属性来命名,被用来在XAML中引用Window对象。x:Class属性代表和当前Window有关联的类。正如我已经告诉你的,XAML不是自给自足的,因此为了定义具体逻辑你需要用C#或VB.NET写的类。

Grid是WPF应用程序中一个主要的布局对象。Grid可以有多个孩子元素。现在让我们把一些控件放在Grid当中来。

<Grid>        <Grid.RowDefinitions>            <RowDefinition Height="Auto" />        </Grid.RowDefinitions>        <Grid.ColumnDefinitions>            <ColumnDefinition MinWidth="50" />            <ColumnDefinition Width="Auto" />            <ColumnDefinition Width="*" />        </Grid.ColumnDefinitions>        <TextBlock Text="Enter Name :" Grid.Row="0" Grid.Column="0" />        <TextBox x:Name="txtName" Grid.Row="0" Grid.Column="1" MinWidth="50"/>        <Button Content="Click Me" Grid.Row="0" Grid.Column="2" Click="Button_Click"/>    </Grid>

你可以看到,我定义了一个RowDefination和一个ColumnDefination。这将允许你将Grid划分为单元格以便你能把你的控件精确的放到你想要放置的地方。针对每个RowDefination和ColumnDefination,你可以使用它的Width和Height。你能看到我用了50,Auto和 * 作为Width的值。Auto代表单元格的尺寸将在控件被定义时定义。* 表示它将占据剩余的所有控件。因此,你可以看到button占据了这个列当中剩余的所有空间。

现在,在后置代码中,我方了一个MessageBox来显示TextBox的内容。

private void Button_Click(object sender, RoutedEventArgs e) {     MessageBox.Show(string.Format("Hi {0}", this.txtName.Text)); }

因此你可以看到系统会提示你你的名字,是不是很有趣!

如果你看了这个小小的XAML,你也许很好奇在其他的控件中我怎么才能定义这个Grid属性。就像我在每个控件中定义Grid.Row一样。使用依赖属性使得这成为可能。这是WPF新引入的一个特性。我们将在以后详细讨论。

结论

这是WPF系列的第一部分,这包括WPF应用程序的初步讨论,我将深入到WPF程序的其他方面,以及你如何利用WPF控件工作并知道您构建一个解决方案。

希望您能喜欢阅读这篇文章