1.md 175.0 KB
Newer Older
W
wizardforcel 已提交
1
# 第一部分:Direct2D
W
wizardforcel 已提交
2 3 4

Direct2D 是一个图形应用编程接口(Application Programming Interface),旨在渲染二维矢量和光栅图形。它建立在 Direct3D 应用编程接口之上,而 Direct3D 应用编程接口又建立在 DXGI (DirectX 图形基础设施)之上。它可以与 Direct3D 结合使用,渲染场景的任何二维部分。它具有高性能,利用图形处理器实现高效、复杂的二维图形。

W
wizardforcel 已提交
5
| ![](img/note.png) | 注:在这本书里,我会多次提到图形处理器(图形处理单元的简称)。GPU 这个术语通常指的是一个专用的图形卡,然而,我将更一般地使用这个术语来指在计算机中执行大多数图形处理的硬件。这包括专用显卡、板载显卡或 WinRT 设备中 NVidia Tegra 芯片中的执行单元。 |
W
wizardforcel 已提交
6 7 8 9 10

该应用编程接口由许多接口(组件对象)组成,用于与图形硬件通信。它可以渲染矢量图元,如直线和椭圆,还可以用纯色或渐变填充形状,以及显示光栅图像。光栅图形由像素组成,屏幕(或图像)上的每个点对应一个像素。每个像素都有决定其颜色的值,它们共同排列在一个大网格中。

Direct2D 对于可视化数据很重要,因为许多图表类型(折线图、散点图等)都是如此。)在设计上基本上是二维的。使用 Direct2D 和使用 Direct3D 渲染二维图形最重要的区别是简单。Direct3D 比 Direct2D 快几个数量级,但编程更复杂。除此之外,Direct2D 项目模板是标准 Windows 8 XAML 和 Direct2D 的完美结合。这允许程序员使用标准的 Windows 8 控件和 XAML 页面来处理用户输入,而 Direct2D 处理所有的图形处理。DirectX 和 XAML 的这种结合是仅在 Windows 8 应用程序中可用的功能。

W
wizardforcel 已提交
11
![](img/image002.png)
W
wizardforcel 已提交
12 13 14 15 16 17 18 19 20

图 1:主要 DirectX 组件之间的关系

图形驱动程序是描述的最低级别;它直接控制硬件。这上面是 DirectX 图形基础设施(DXGI),然后是 Direct3D,最后是 Direct2D。软件光栅化器用于代替图形硬件,当专用图形处理器不可用时,它使用中央处理器来渲染图形。

## 第 1 章:Direct2D (XAML)模板

我们将首先创建一个标准的 Direct2D (XAML)模板项目,并熟悉其结构。打开 Visual Studio 2012,在**文件菜单**上,点击**新建项目**

W
wizardforcel 已提交
21
![](img/image003.jpg)
W
wizardforcel 已提交
22 23 24 25 26 27 28 29 30

图 2:创建新的 Direct2D 应用程序(XAML)

点击左侧面板的 **Visual C++** ,然后从中间面板的项目模板中选择 **Direct2D App (XAML)** 。在**名称**框中输入项目名称,然后点击**确定**

Visual Studio 将为新项目创建许多文件,其中包含样板代码和一些其他有用的帮助方法。解决方案资源管理器应该如图 3 所示。

要在调试模式下运行应用程序,请按 F5,或在**文件**菜单上单击**调试** > **开始调试**。在 Visual Studio 生成并链接您的项目文件后,它将执行应用程序。

W
wizardforcel 已提交
31
![](img/image004.jpg)
W
wizardforcel 已提交
32 33 34

图 3: Direct2D 应用程序(XAML)解决方案资源管理器

W
wizardforcel 已提交
35
![](img/image005.png)
W
wizardforcel 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

图 Direct2D 应用程序(XAML)模板的输出

资产文件夹

该文件夹包含新应用程序的多个巴布亚新几内亚图像:

*   Logo.png:此图像显示为 Windows 8 起始页上的图块。它类似于以前版本的 Windows 中的桌面图标。
*   SmallLogo.png:这是应该显示较小图标时使用的图标图像,例如当用户在 Windows 8 中搜索“所有应用程序”时。
*   SplashScreen.png:在执行应用程序时,闪屏会短暂出现。
*   这是你的应用出现在视窗商店的标志。

普通的

该文件夹包含一个 XAML 文件,描述了 XAML 文件之间的常见设置。

外部依赖关系:

此文件夹包含项目可能依赖的大量外部文件。有些是按项目生成的,有些是标准的 Windows C++头文件。您不应该更改此列表中的文件,尤其是标准的窗口标题。

应用

App.xaml、App.cpp 和 App.h 文件定义了您的应用程序。XAML 文件包含整个应用程序的一些全局设置。CPP 和 H 文件定义了一个类,该类具有执行程序的起始点。这个类拥有一个名为 m_directXPage 的成员变量,它是主要的 Direct2D 渲染类。它还控制一些重要的系统级操作,如在程序暂停时保存和恢复应用程序的状态。

巴斯蒂米尔

基本定时器头定义了一个类,可以用于任何基于时间的任务,如物理或动画。

xxx_TemporaryKey.pfx

这是您的应用程序的 ClickOnce 数字证书。它用于帮助确保应用程序不是恶意软件。如果应用程序没有签名,Windows 将警告用户该应用程序“来自未知的发行商”,并询问他们是否确定要运行该程序。

DirectXBase

DirectXBase 类定义在两个文件中:DirectXBase.h 和 DirectXBase.cpp。这个类包含了启动和运行 Direct2D 的大部分样板代码。它包含初始化设备、工厂、设备上下文和许多其他东西的代码。它可以用于二维和三维图形。它有许多助手功能,使我们能够快速开始 DirectX 编程,而无需键入极其冗长的样板代码。我们鼓励读者彻底调查这个文件,因为它确切地显示了 DirectX 应该如何初始化。

DirectXHelper

这个文件由一个函数组成,DX::ThrowIfFailed。这是一个将 HRESULT 转换为托管 C++异常的助手函数。DirectX 函数调用返回一个 HRESULT。我们将检查的许多代码都围绕着对这个方法的调用来调用 DirectX 函数,这样程序员就有机会检查 DirectX 抛出的任何错误。如果您在这一行设置了断点,Visual Studio 将在抛出异常时中断,并允许您检查出了什么问题。错误会给你一个错误号,你可以研究它的含义,或者使用 DirectX SDK 附带的错误搜索应用程序来查找它。

DirectXPage

这是你申请的 XAML 主页。Direct2D (XAML)模板应用程序包含一个简单的页面,其中两个句子写在 XAML 表单上。上面一句是用 XAML 写的,下面一句是用 DirectX 写的。这是呈现最上面一行代码的类。

Package.appxmanifest

这是您的应用程序的主要清单。它包含关于您的应用程序的所有信息,包括发行者是谁,以及应用程序需要什么功能(互联网访问、网络摄像头访问等)。).

预编译头文件

预编译头文件(pch.h)包含编译成中间格式的头文件,以便在重新编译整个项目时节省时间。为了正确工作,您添加到项目中的大多数类都将包含此文件。

简单文本渲染器

这是这个 DirectX 应用程序的核心类。这个类在屏幕上呈现下面的句子。因为 SimpleTextRenderer 类是控制 DirectX 在屏幕上显示什么的主要类,所以我们将详细研究它。

### 简单文本渲染器类

这个类使用 Direct2D 向屏幕呈现一行文本。在这一节中,我们要研究的不是类本身,而是它的运行方式。我们将在未来章节中构建的图形渲染器类将主要基于这个类。打开 SimpleTextRenderer.h 文件。

该类从 DirectXBase 类派生。它包含一个默认构造函数和几个方法,在资源分配过程中调用(`CreateDeviceIndependentResources``CreateDeviceResources``CreateWindowSizeDependentResources`)。

W
wizardforcel 已提交
98
| ![](img/note.png) | 注意:Resources 是一个通用术语,指的是存储在内存(系统内存或图形处理器的专用内存)中并由 DirectX 使用的许多不同类型的对象和结构。资源在使用之前必须被创建和初始化。我们将研究的大多数资源都是在主 DirectX 对象之后不久创建的。当应用程序关闭时,这些资源会被销毁。资源可以在初始化主 DirectX 对象后的任何时候创建和销毁,因为资源创建方法属于这些对象。 |
W
wizardforcel 已提交
99 100 101 102 103 104 105

渲染方法是 DirectX 进行所有渲染的地方。此类还定义了一个更新方法,可用于执行计算,以确定对象应该移动到场景中的什么位置。`UpdateTextPosition``BackgroundNextColor``BackgroundPreviousColor`功能是这个模板特有的,自己开发的时候不需要。它们允许用户操纵 DirectX 绘制的文本的位置,以及在一些预定义的背景颜色之间循环。

使用`SaveInternalState``LoadInternalState`方法保存和恢复应用程序的状态;例如,当 WinRT 平板电脑进入睡眠状态,然后被唤醒。

这些方法后面有几个成员变量,用于维护和操作文本的位置。除了`m_renderNeeded`变量之外,这些变量大多数都是特定于应用程序的,并且很可能不是您的应用程序所必需的。应用程序使用`m_renderNeeded`变量来确定是否应该调用渲染方法。如果场景中没有任何变化,那么再次渲染就没有意义了。下图描述了这个应用程序中最重要的类之间的关系。以菱形结束的线条表示所有权(AppXAML 类拥有 DirectXPage 类型的成员),以三角形结束的线条表示继承(SimpleTextRenderer 从 DirectXBase 类继承)。

W
wizardforcel 已提交
106
![](img/image006.png)
W
wizardforcel 已提交
107 108 109 110 111

图 Direct2D 应用程序(XAML)模板的类关系

实时图形应用程序通常以某个预定义的时间间隔渲染帧,该时间间隔用每秒显示的帧数(FPS)来描述。帧是游戏或电影的单个静止图像。为了创建平滑动画的错觉,稍微不同的帧被连续显示给观众。DirectX 应用程序通常以固定的刷新率渲染帧,例如 60fps 甚至 100fps。不太可能每隔 60 <sup></sup>或 100 <sup></sup>重新渲染一次制图应用程序的帧。它们通常长时间保持完全相同。用户可以平移或缩放图表,这将需要场景的重新渲染,但是这个动作不像更新实时游戏的帧那样对时间很关键。

W
wizardforcel 已提交
112
| ![](img/note.png) | 注意:这个模板中这个类和其他类的成员变量有一个“m_”前缀。这意味着它们是成员变量,而不是函数的局部变量。没有必要,但是用这个前缀命名所有成员变量是个好主意。 |
W
wizardforcel 已提交
113 114 115

接下来,在解决方案资源管理器中打开 SimpleTextRenderer.cpp 文件。在文件的顶部,您将看到预编译头(`pch.h`)的`#include`指令。下面是`SimpleTextRenderer.h,``#include`和该类使用的名称空间列表。在使用指令下,您将看到预定义的背景颜色顺序,用户可以在运行应用程序时循环使用这些颜色。

W
wizardforcel 已提交
116
```cpp
W
wizardforcel 已提交
117 118 119 120
    static const ColorF BackgroundColors[] = {   }

```

W
wizardforcel 已提交
121
用户可以通过在屏幕上右键单击鼠标,或者在使用触摸屏设备时滑动指针,并选择**下一步****上一步**来循环切换颜色和更改应用程序的背景。这是一个特定于应用程序的数组,其他应用程序不太可能使用它。在下面,我们看到了类的默认构造函数。
W
wizardforcel 已提交
122

W
wizardforcel 已提交
123
```cpp
W
wizardforcel 已提交
124 125 126 127 128 129 130 131
    SimpleTextRenderer::SimpleTextRenderer():m_renderNeeded(true),     m_backgroundColorIndex(0),m_textPosition(0.0f, 0.0f) { }

```

默认构造函数初始化几个变量;它通过选择索引 0 将`backcolor`设置为`CornflowerBlue`(这是对前面代码示例中定义的`BackgroundColors`数组的引用)。它还初始化文本位置,并将 m_ `renderNeeded`布尔值设置为真,这样第一帧将被绘制到屏幕上。此时不会创建或分配资源;DirectX 工厂和上下文也不存在。

接下来是三种资源分配方法。首先是`CreateDeviceIndependentResources`法。

W
wizardforcel 已提交
132
```cpp
W
wizardforcel 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
    void SimpleTextRenderer::CreateDeviceIndependentResources() {
         DirectXBase::CreateDeviceIndependentResources();
         DX::ThrowIfFailed(
              m_dwriteFactory->CreateTextFormat(
                    L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_NORMAL,
                    DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
                    42.0f, L"en-US", &m_textFormat));
         DX::ThrowIfFailed(
              m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)
              );
    }

```

`CreateDeviceIndependentResources`方法用于创建和初始化任何独立于设备的 Direct2D 对象。这个方法从调用基类的同名方法开始。基类方法创建 DirectX 工厂,比如下一行使用的`m_dwriteFactory`,应用程序可以使用它来创建更多的 DirectX 对象。

W
wizardforcel 已提交
149
| ![](img/note.png) | 注意:DirectX 中的资源都来自两大类之一:设备资源或与设备无关的资源。设备是显卡,设备资源驻留在显卡本身。独立于设备的资源驻留在系统内存中,渲染速度往往较慢,因为它们需要 CPU 周期来传输到显卡。 |
W
wizardforcel 已提交
150

W
wizardforcel 已提交
151
```cpp
W
wizardforcel 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    void SimpleTextRenderer::CreateDeviceResources() {
         DirectXBase::CreateDeviceResources();
         DX::ThrowIfFailed(
              m_d2dContext->CreateSolidColorBrush(
                    ColorF(ColorF::Black), &m_blackBrush));
         Platform::String^ text = "Hello, DirectX!";
         DX::ThrowIfFailed(m_dwriteFactory->CreateTextLayout(
                    text->Data(), text->Length(),
                    m_textFormat.Get(),
                    700, // maxWidth.
                    1000, // maxHeight.
                    &m_textLayout));
         DX::ThrowIfFailed(m_textLayout->GetMetrics(&m_textMetrics));
    }

```

`CreateDeviceResources`方法创建并初始化设备相关资源。该方法调用同名的基类方法,该方法创建 Direct3D 设备的实例和应用程序使用的上下文(`m_d3dcontext``m_d3dDevice`)。

W
wizardforcel 已提交
171
| ![](img/note.png) | 注意:设备和上下文是 DirectX 中的两个重要术语。设备可以看作是显卡本身;这个类用于初始化硬件,查询其功能,并创建资源,如纹理和着色器。上下文是设备的特定用途;它使用设备上的资源将内容呈现到屏幕上。通常有一个设备,但可能有多个上下文。例如,打印示例使用三个上下文:一个用于渲染,另一个用于打印预览,第三个用于打印本身。图 6 显示了这些类各自负责的一些任务。 |
W
wizardforcel 已提交
172

W
wizardforcel 已提交
173
![](img/image009.png)
W
wizardforcel 已提交
174 175 176 177 178

图 6:设备与环境

画笔是设备资源;此方法创建一个黑色画笔来绘制文本。要写入屏幕的实际字符串在设备上使用`CreateTextLayout`方法创建为`TextLayout`对象。之后,使用`GetMetrics`方法将弦的尺寸和比例保存到`m_textMetrics`中。

W
wizardforcel 已提交
179
| ![](img/note.png) | 注意:CreateTextLayout 方法创建 IDWriteTextLayout 设备资源。此资源包含有关要打印的字符串、打印该字符串的边界框及其位置的信息。CreateTextFormat 方法(在 CreateDeviceIndependentResources 方法中)创建一个 IDWriteTextFormat 对象,该对象用于指定要呈现的文本的字体、大小和属性。 |
W
wizardforcel 已提交
180

W
wizardforcel 已提交
181
```cpp
W
wizardforcel 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    void SimpleTextRenderer::CreateWindowSizeDependentResources() {
         DirectXBase::CreateWindowSizeDependentResources();
         // Add code to create window size dependent objects here.
    }

    void SimpleTextRenderer::Update(float timeTotal, float timeDelta) {
         (void) timeTotal; // Unused parameter.
         (void) timeDelta; // Unused parameter.
         // Add code to update time dependent objects here.
    }

```

前面两个方法在模板中是空的。`CreateWindowSizeDependentResources`方法用于创建其设置取决于屏幕大小或方向的任何对象(设备或独立设备)。`Update`法也是空的;它控制应用程序的物理或其他逻辑,通常是依赖于时间的东西。以下代码是模板的`Render`方法的示例。

W
wizardforcel 已提交
197
```cpp
W
wizardforcel 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    void SimpleTextRenderer::Render() {
         m_d2dContext->BeginDraw();
         m_d2dContext->Clear(ColorF(BackgroundColors[m_backgroundColorIndex]));
         // Position the rendered text.
         Matrix3x2F translation = Matrix3x2F::Translation(
              m_windowBounds.Width / 2.0f 
              m_textMetrics.widthIncludingTrailingWhitespace / 2.0f +
              m_textPosition.X,
              m_windowBounds.Height / 2.0f 
              m_textMetrics.height / 2.0f + m_textPosition.Y
              );

         // Note that the m_orientationTransform2D matrix is post-multiplied here
         // in order to correctly orient the text to match the display orientation.
         // This post-multiplication step is required for any draw calls that are
         // made to the swap chain's target bitmap. For draw calls to other targets,
         // this transform should not be applied.
         m_d2dContext->SetTransform(translation * m_orientationTransform2D);
         m_d2dContext->DrawTextLayout(Point2F(0.0f, 0.0f),
              m_textLayout.Get(),  m_blackBrush.Get(),
              D2D1_DRAW_TEXT_OPTIONS_NO_SNAP);

         // Ignore D2DERR_RECREATE_TARGET. This error indicates that the device
         // is lost. It will be handled during the next call to Present.
         HRESULT hr = m_d2dContext->EndDraw();
         if (hr != D2DERR_RECREATE_TARGET) {
              DX::ThrowIfFailed(hr);
         }
         m_renderNeeded = false;
    }

```

场景的实际绘制就是在渲染方法中进行的。场景的大部分绘制都是由 m_d2dContext 对象完成的。渲染方法从陈述`m_d2dcontext->BeginDraw`开始;这条线耦合到底部附近的`m_d2dContext->EndDraw`方法调用。您应该将所有的 Direct2D 绘图放在这两个函数调用之间。BeginDraw 用于指定一些代码的开始,这些代码为渲染目标构建一批渲染命令。EndDraw 指定该批命令已经完成,可以进行渲染。

W
wizardforcel 已提交
233
下一行调用`Clear`方法,传递用户当前选择的颜色。这导致屏幕被清除为纯色,这是先前定义的`BackgroundColors`数组,用户可以在其中循环。
W
wizardforcel 已提交
234

W
wizardforcel 已提交
235
| ![](img/tip.png) | 提示:在渲染方法中将屏幕清除为黑色以外的颜色是一个好主意,即使您的后续绘图将完全覆盖清除的屏幕。如果您不这样做,并且程序有问题,您可能会盯着黑屏(或随机闪烁的颜色或像素),根本无法知道渲染方法是否正在被调用。 |
W
wizardforcel 已提交
236 237 238 239 240

在调用`Clear`之后,建立一个矩阵。诸如缩放、旋转和平移(或平移,这就是我们在这里所做的)等变换都由矩阵控制。这个特定的矩阵是一个翻译矩阵;它移动文本,以便用户可以在屏幕上拖动它。这个矩阵定义中的计算将文本放在屏幕中间,当用户拖动它时会有一些偏移。它使用`TextMetrics`对象和`WindowBounds`对象来找到文本应该去的地方。

一旦定义,翻译矩阵必须应用于上下文。这发生在调用`SetTransform`的下一行。在应用了适当的转换之后,可以呈现文本本身。这发生在呼叫`DrawTextLayout`的下一条线路上。然后调用`EndDraw`结束绘制,图像呈现给用户。

W
wizardforcel 已提交
241
| ![](img/tip.png) | 提示:当 m_renderer 对象在其 OnRendering 事件处理程序方法中调用 Present()时,渲染场景的实际屏幕刷新发生在 DirectXPage.xaml.cpp 文件中。非常重要的一点是,DirectXPage 类呈现了场景。当您添加调用 Present()本身的更复杂的呈现类时,从 DirectXPage 类中移除 Present()调用是很重要的。否则,您可能会出现()两次,这将导致首先将实际场景翻转到屏幕上,但立即用其他图像覆盖它。 |
W
wizardforcel 已提交
242 243 244

剩下的方法是事件处理程序和其他特定于此的东西。我建议刚刚使用 Visual Studio 2012 熟悉 DirectX 的程序员在继续下一节之前花一些时间来改变这个模板的工作方式。熟悉这个模板对于理解本书 Direct2D 部分的其余章节至关重要。

W
wizardforcel 已提交
245
| ![](img/tip.png) | 提示:Direct2D 设计用于在渲染几何图形时自动使用 CPU 的多个内核。如果在 DirectXBase.cpp 文件中创建设备上下文时使用 d2d 1 _ DEVICe _ CONTENT _ OPTIONS _ ENable _ 多线程 _OPTIMIZATIONS 选项,自动多线程可能会大大提高代码的速度,但代价是占用更多的系统内核。 |
W
wizardforcel 已提交
246 247 248 249 250 251 252

### 同步、交换链和缓冲

电脑显示器以固定的速度更新显示。每秒 60 次是常见的,称为 60 Hz,但也有其他类似的 75 Hz 和 100 Hz。像素数据存储在图形处理器的缓冲区中,称为前缓冲区。监视器上的图像每秒钟被来自该缓冲区的数据刷新 60 次。在显示器刷新显示的同时,图形处理器正忙于渲染要显示的帧。图形处理器将像素数据写入缓冲区。

这个系统有一个问题,导致了令人不快的伪像。问题是 GPU 和显示器不一定以相同的速度更新帧。这导致了一种叫做撕裂的假象(见图 7)。显示器将一帧的一半绘制到屏幕上,将前一帧的一半绘制到屏幕上,因为当显示器部分通过更新其显示时,GPU 会更新前缓冲区中的帧。

W
wizardforcel 已提交
253
![](img/image012.png)
W
wizardforcel 已提交
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274

图 7:撕裂

为了避免这种情况,GPU 不会渲染到前缓冲区。相反,它呈现到后缓冲区,除了没有呈现到屏幕上之外,它在各个方面都与前缓冲区相同。

监视器通过将屏幕左上角的像素渲染到右下角来刷新显示,然后重置并重复该操作。它从右下角重置回顶部的时间段称为垂直回扫。为了避免撕裂伪影,图形处理器等待监视器处于垂直回扫阶段,然后翻转缓冲器(通过复制像素数据或交换指针来交换前后缓冲器)。这被简称为垂直同步或垂直同步。当显示器完成自我重置时,图形处理器可以将整个帧复制到前缓冲区。这样就不会撕裂,显示器永远不会显示一帧的一半和另一帧的一半。

使用交换链对象协调缓冲区。这是一个专用于控制缓冲区交换的类。在我们的应用中,有两个缓冲区:前缓冲区和后缓冲区。有时,使用多个后台缓冲区并逐个渲染帧,对要显示的帧进行排队是有益的。

## 第 2 章:使用 WinRT 设备进行调试

本书中的所有代码都适用于 Windows 8 电脑以及 WinRT 设备。如果您正在为 WinRT 平板电脑创作软件,并且有一个真实的设备,那么使用它来调试和测试您的应用程序而不是仿真器(通常是默认的)是非常有益的。C++和 DirectX 中的大部分代码在 Windows 8 PC 以及 WinRT 设备(为 ARM 目标编译)上运行良好。模拟器很好,但永远无法与真实设备的确切特征相匹配。

安装远程工具

将 Visual Studio 2012 的远程工具安装到设备上。这可从微软网站上获得(可从[http://www . Microsoft . com/visualstudio/eng/downloads # d-additional-software](http://www.microsoft.com/visualstudio/eng/downloads#d-additional-software)获得)。它是一种与 Visual Studio 开发机器连接的服务,用于在设备上运行和调试应用程序。Visual Studio 提供了所有常规调试机制,如断点、检查 ARM 寄存器和内存窗口。您需要知道设备的名称,以便在其上部署应用程序。您还需要让设备运行前面提到的安装附带的删除调试监视器。每个构建配置(发布 x86、调试 x86、发布 ARM 等。)您希望设备运行的项目设置中必须有该设备的名称。

将应用程序更改为 ARM

如果您要部署到的 WinRT 设备是基于 ARM 的,例如微软 Surface,您可以通过选择 **ARM** 从主菜单中更改项目的配置。

W
wizardforcel 已提交
275
![](img/image013.png)
W
wizardforcel 已提交
276 277 278 279 280 281 282

图 8:配置

将调试更改为远程计算机

如果尚未设置,您应该将调试更改为远程计算机。

W
wizardforcel 已提交
283
![](img/image014.jpg)
W
wizardforcel 已提交
284 285 286 287 288 289 290

图 9:机器名称

指定远程计算机的名称

从 Visual Studio 的主菜单中打开**项目** > **【名称】属性**页面,或者在解决方案资源管理器中右键单击项目名称,然后在上下文菜单中单击**属性**。这将打开项目的属性页。点击左侧面板的**调试**,在标有**机器名称**的空白处输入您的远程机器名称。

W
wizardforcel 已提交
291
![](img/image015.png)
W
wizardforcel 已提交
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

图 10:远程机器

运行远程调试器

在设备上运行远程调试服务,您应该可以像往常一样从 Visual Studio 2012 开始调试(按 F5 或单击开始调试按钮)。您将在设备(Visual Studio 远程调试器的窗口)上看到的第一件事是,它已连接到开发计算机,并显示如下消息:

`3/01/2013 2:48:40 PM [MachineName]\[ComputerName] connected`

此后不久,您将在 Visual Studio 的输出窗口中看到一条消息,称它正在将程序上传到设备。这需要一些时间,但是一旦上传完成,应用程序应该会运行。

如果您无法从设备调试应用程序,或者应用程序没有按预期运行,以下是一些想法:

*   确保设备上安装了正确的远程调试工具。安装 Visual Studio 2012 的工具。请务必直接从 Microsoft 网站下载此文件,并下载任何可用的更新,以确保当前的远程调试支持您的特定设备。
*   请确保在项目属性中正确拼写了远程计算机的名称。远程计算机名是在首次安装 Windows RT 时选择的。如果您忘记或不确定远程计算机的名称,可以在“远程调试器”窗口中看到远程计算机的名称。目前,项目属性中名称的大小写无关紧要,但是机器使用所有大写字母,因此您可能会尝试匹配机器使用的确切大小写。
*   确保当前配置具有在属性页的调试字段中指定的远程计算机的名称。您需要在每个配置中输入设备的名称。例如,如果您使用调试和发布,您需要在两者中指定远程机器的名称。

最后,如果应用程序没有按预期执行而是正在运行,请确保您使用的代码完全可移植到 WinRT。请注意,这些设备没有专用显卡。它们依赖于缩小规模、便携且节能的中央处理器/图形处理器组合。在这些设备上运行的 DirectX 11 版本也在缩小。它不包含 DirectX 11 标准的全部功能。操作系统本身(Windows RT)是完整 Windows 8 的缩小版本,许多功能缺失(例如,自由访问文件结构)。

## 第 3 章:开始图形渲染应用

图 11 是一个基本的条形图。这个特殊的是使用开放办公室计算与随机数据生成的。它由标题、背景、轴标签、网格、键和代表数据的条组成。

W
wizardforcel 已提交
315
![](img/image016.jpg)
W
wizardforcel 已提交
316 317 318 319 320 321 322 323 324 325 326

图 11:条形图

图表的每个部分都可以被认为是一个独特的对象。每个对象一个接一个地呈现,从背景开始,然后是网格、数据,然后是标签。图形本身由几个对象组成,它一个接一个地绘制这些对象,以构建数据的完整图形表示。上一张图表的许多内容是通用的,适用于不同的图表类型。例如,网格可以用于散点图、折线图或直方图,就像这里使用的一样。

我们的制图应用程序将以同样的方式工作。我们将开发一个图表对象的集合,可以随意在图表中添加和删除。这些对象将是非常基本的,以维护一个通用的和可用的 Direct2D 图表应用程序的基础。图形本身将是一个名为 GraphRenderer 的类,它将基于我们刚刚检查的 SimpleTextRenderer 类。构成图形渲染器的每个对象都是简单文本渲染器的缩小版本。

在 Visual Studio 2012 中为 Windows 8 创建新的 Direct2D (XAML)应用程序。这将成为我们应用的起点。我已经调用了我的应用程序 graph 标绘。如果复制代码进行测试,您需要将对此命名空间的任何引用更改为应用程序的名称。

首先,我们可以删除表单上的 XAML 文本。通过在解决方案资源管理器中双击其名称,打开 DirectXPage.xaml 文件。这将在可视化设计器中显示页面。选择**你好,XAML!**并右键单击。点击上下文菜单中的**删除**

W
wizardforcel 已提交
327
![](img/image017.jpg)
W
wizardforcel 已提交
328 329 330 331 332

图 12:删除文本

在这个面板的下侧还有一个隐藏栏,可以删除。它是当用户在屏幕上右键单击时出现的条,允许背景颜色改变。该栏在设计器中不可见,因此从 XAML 代码中删除它是最容易的。我已经突出显示了要删除的行。

W
wizardforcel 已提交
333
```cpp
W
wizardforcel 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
    <Page
        x:Class="GraphPlotting.DirectXPage"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:GraphPlotting"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <SwapChainBackgroundPanel x:Name="SwapChainPanel" PointerMoved="OnPointerMoved" PointerReleased="OnPointerReleased"/>

          <Page.BottomAppBar>
              <AppBar Padding="10,0,10,0">
                  <Grid>
                 Lots of XAML code here!!!
                  </Grid>
              </AppBar>
          </Page.BottomAppBar>

```

打开 **DirectXPage.xaml.cpp** 文件。删除`OnPreviousColorPressed``OnNextColorPressed`事件处理程序。在这个类的构造函数中,创建了一个 SimpleTextRenderer 对象。我们需要将它改为一个 GraphRenderer 构造函数调用。也可以删除状态保存方式,`SaveInternalState``LoadInternalState`。GraphRenderer 类使用了一个名为`PointerMoved`的方法,而不是`UpdateTextPosition`。该方法在代码列表中已被重命名。整个 DirectXPage.xaml.cpp 文件应该如下所示:

W
wizardforcel 已提交
356
```cpp
W
wizardforcel 已提交
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
    // DirectXPage.xaml.cpp
    #include "pch.h"
    #include "DirectXPage.xaml.h"
    using namespace GraphPlotting;
    using namespace Platform;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Graphics::Display;
    using namespace Windows::UI::Input;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;

    DirectXPage::DirectXPage() : m_renderNeeded(true), m_lastPointValid(false) {
         InitializeComponent();
         m_renderer = ref new GraphRenderer();
         m_renderer->Initialize(Window::Current->CoreWindow,     SwapChainPanel,
              DisplayProperties::LogicalDpi);

         Window::Current->CoreWindow->SizeChanged +=
              ref new TypedEventHandler<CoreWindow^,
                    WindowSizeChangedEventArgs^>(this,
                    &DirectXPage::OnWindowSizeChanged);

         DisplayProperties::LogicalDpiChanged +=
              ref new DisplayPropertiesEventHandler(this,
                    &DirectXPage::OnLogicalDpiChanged);

         DisplayProperties::OrientationChanged +=
            ref new DisplayPropertiesEventHandler(this,
              &DirectXPage::OnOrientationChanged);

         DisplayProperties::DisplayContentsInvalidated +=
              ref new DisplayPropertiesEventHandler(this,
                    &DirectXPage::OnDisplayContentsInvalidated);

         m_eventToken = CompositionTarget::Rendering::add(ref new
              EventHandler<Object^>(this, &DirectXPage::OnRendering));
         m_timer = ref new BasicTimer();
    }

    void DirectXPage::OnPointerMoved(Object^ sender, PointerRoutedEventArgs^ args) {
         auto currentPoint = args->GetCurrentPoint(nullptr);
         if (currentPoint->IsInContact) {
              if (m_lastPointValid) {
                    Windows::Foundation::Point delta(
                         currentPoint->Position.X - m_lastPoint.X,
                         currentPoint->Position.Y - m_lastPoint.Y
                         );
                    m_renderer->PointerMoved(delta);
                    m_renderNeeded = true;
              }
              m_lastPoint = currentPoint->Position;
              m_lastPointValid = true;
         }
         else {
              m_lastPointValid = false;
         }
    }

    void DirectXPage::OnPointerReleased(Object^ sender, PointerRoutedEventArgs^ args) {
         m_lastPointValid = false;
    }

    void DirectXPage::OnWindowSizeChanged(CoreWindow^ sender,
              WindowSizeChangedEventArgs^ args) {
         m_renderer->UpdateForWindowSizeChange();
         m_renderNeeded = true;
    }
    void DirectXPage::OnLogicalDpiChanged(Object^ sender) {
         m_renderer->SetDpi(DisplayProperties::LogicalDpi);
         m_renderNeeded = true;
    }
    void DirectXPage::OnOrientationChanged(Object^ sender) {
         m_renderer->UpdateForWindowSizeChange();
         m_renderNeeded = true;
    }
    void DirectXPage::OnDisplayContentsInvalidated(Object^ sender) {
         m_renderer->ValidateDevice();
         m_renderNeeded = true;
    }
    void DirectXPage::OnRendering(Object^ sender, Object^ args) {
         if (m_renderNeeded)  // Comment out this line to make real-time updating
         {
              m_timer->Update();
              m_renderer->Update(m_timer->Total, m_timer->Delta);
              m_renderer->Render();
              m_renderer->Present();
              m_renderNeeded = false;
         }
    }

```

打开 DirectXPage.xaml.h 文件,删除我们从 CPP 文件中删除的`OnPreviousColorPressed``OnNextColorPressed`事件处理程序的声明,并将 include 从 SimpleTextRenderer.h 更改为 GraphRenderer.h,并将成员变量声明从 SimpleTextRenderer^更改为 GraphRenderer^.同时删除 SaveInternalState 和 LoadInternalState 方法的声明。该文件应该如下所示(因为我们还没有声明 GraphRenderer 类,所以 Visual Studio 将用红色下划线标出引用)。

W
wizardforcel 已提交
458
```cpp
W
wizardforcel 已提交
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    // DirectXPage.xaml.h
    #pragma once
    #include "DirectXPage.g.h"
    #include "GraphRenderer.h"
    #include "BasicTimer.h"
    namespace GraphPlotting{
        [Windows::Foundation::Metadata::WebHostHidden]
          public ref class DirectXPage sealed {
          public:
                DirectXPage();
          private:
                void OnPointerMoved(Platform::Object^ sender,
                      Windows::UI::Xaml::Input::PointerRoutedEventArgs^ args);
                void OnPointerReleased(Platform::Object^ sender,
                      Windows::UI::Xaml::Input::PointerRoutedEventArgs^ args);
                void OnWindowSizeChanged(Windows::UI::Core::CoreWindow^ sender,
                      Windows::UI::Core::WindowSizeChangedEventArgs^ args);
                void OnLogicalDpiChanged(Platform::Object^ sender);
                void OnOrientationChanged(Platform::Object^ sender);
                void OnDisplayContentsInvalidated(Platform::Object^ sender);
                void OnRendering(Object^ sender, Object^ args);
                Windows::Foundation::EventRegistrationToken m_eventToken;
                GraphRenderer^ m_renderer;
                bool m_renderNeeded;
                Windows::Foundation::Point m_lastPoint;
                bool m_lastPointValid;      
                BasicTimer^ m_timer;
          };
    }

```

打开 **App.xaml.cpp** 文件,删除对`LoadInternalState``SaveInternalState`方法的两个引用:

W
wizardforcel 已提交
493
```cpp
W
wizardforcel 已提交
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
    // App.xaml.cpp
    // Implementation of the App class.
    #include "pch.h"
    #include "DirectXPage.xaml.h"
    using namespace GraphPlotting;
    using namespace Platform;
    using namespace Windows::ApplicationModel;
    using namespace Windows::ApplicationModel::Activation;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Storage;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;

    App::App() {
          InitializeComponent();
          Suspending += ref new SuspendingEventHandler(this, &App::OnSuspending);
    }

    void App::OnLaunched(LaunchActivatedEventArgs^ args) {
          m_directXPage = ref new DirectXPage();
          // Place the page in the current window and ensure that it is active.
          Window::Current->Content = m_directXPage;
          Window::Current->Activate();
    }

    void App::OnSuspending(Object^ sender, SuspendingEventArgs^ args) {
          (void) sender; // Unused parameter.
          (void) args; // Unused parameter.
    }

```

下面的 GraphRenderer 类将代替 Direct2D (XAML)模板中提供的 SimpleTextRenderer,这样我们就可以从项目中删除 SimpleTextRenderer。在解决方案资源管理器中选择定义简单文本渲染器的两个文件。要删除它们,右键单击并从上下文菜单中选择**删除**

向项目中添加两个文件,GraphRenderer.h 和 GraphRenderer.cpp。随着图表的发展,这些文件会经常变化,但下面是它们的初始列表。

W
wizardforcel 已提交
537
```cpp
W
wizardforcel 已提交
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
    // GraphRenderer.h
    #pragma once
    #include "DirectXBase.h"
    //
    // Additional headers for graph objects here
    //

    // This class represents a graph
    ref class GraphRenderer sealed : public DirectXBase{
    public:
          // Public constructor
          GraphRenderer();

          // DirectXBase methods.
          virtual void CreateDeviceIndependentResources() override;
          virtual void CreateDeviceResources() override;
          virtual void CreateWindowSizeDependentResources() override;
          virtual void Render() override;

          // Capture the pointer movements so the user can pan the chart
          void PointerMoved(Windows::Foundation::Point point);

          // Method for updating time-dependent objects.
          void Update(float timeTotal, float timeDelta);

    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;
    };
    // GraphRenderer.cpp
    #include "pch.h"
    #include "GraphRenderer.h"

    using namespace D2D1;
    using namespace DirectX;
    using namespace Microsoft::WRL;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Core;

    GraphRenderer::GraphRenderer() {
    }

    void GraphRenderer::CreateDeviceIndependentResources() {
    DirectXBase::CreateDeviceIndependentResources();
    }

    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();
    }

    void GraphRenderer::CreateWindowSizeDependentResources() {
    DirectXBase::CreateWindowSizeDependentResources();
    }

    void GraphRenderer::Update(float timeTotal, float timeDelta) {
    }

    void GraphRenderer::PointerMoved(Windows::Foundation::Point point)
    {
    // Allow the user to set the current pan value with the mouse or pointer
    m_pan.X += point.X;
    m_pan.Y += point.Y;
    }

```

此时编译并测试您的应用程序。你会看到整个屏幕变成浅蓝色。

## 第四章:图形背景

我们定义的第一个图形对象是背景。图表的背景充当画布,在其上呈现其他对象。它可以是简单的单一颜色、渐变,甚至是图像。图表通常是为了清晰地描述信息,背景不应该掩盖数据。

### 纯色背景

最简单的图表背景是单一的纯色,通常是白色或一些其他不饱和色素。这些是常见的,因为它们不会将观众的注意力从所表示的数据上引开,并且它们快速且易于呈现。

W
wizardforcel 已提交
615
| ![](img/note.png) | 注意:我们可以将呼叫中的颜色从`CornflowerBlue`更改为其他颜色。相反,我们将在一个单独的类中封装背景的呈现。一旦我们的图表清除了屏幕,您就可以删除清除到文本蓝色。 |
W
wizardforcel 已提交
616 617 618

下面的代码定义了一个呈现纯色背景的类。

W
wizardforcel 已提交
619
```cpp
W
wizardforcel 已提交
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    // SolidBackground.h
    #pragma once
    #include "DirectXBase.h"
    // Defines a background consisting of a solid color
    class SolidBackground {
    private:
          D2D1::ColorF color;     // The color of this background
    public:
          // Creates a new SolidBackground set to the specified color
          SolidBackground(D2D1::ColorF color);
          // Draw the background
          void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
    };

```

W
wizardforcel 已提交
636
```cpp
W
wizardforcel 已提交
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
    // SolidBackground.cpp
    #include "pch.h"
    #include "SolidBackground.h"

    SolidBackground::SolidBackground(D2D1::ColorF col):  color(col) { }

    void SolidBackground::Render(
          Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    context->Clear(color); // Clear the screen to the color
    }

```

此类在其构造函数中接受一个颜色参数,将其保存到一个成员变量中,并使用它在其呈现方法中清除屏幕。为了创建一个实体背景的实例,我们需要将它添加到 GraphRenderer 类中。打开 GraphRenderer.h,为 SolidBackground.h 文件添加一个#include(我已经用蓝色突出显示了添加或更改的行)。

W
wizardforcel 已提交
652
```cpp
W
wizardforcel 已提交
653 654 655 656 657 658 659 660 661 662 663 664 665
    // GraphRenderer.h
    #pragma once
    #include "DirectXBase.h"
    //
    // Additional headers for graph objects here
    //

      #include "SolidBackground.h"

```

在 GraphRenderer.h 文件的底部声明一个成员变量。

W
wizardforcel 已提交
666
```cpp
W
wizardforcel 已提交
667 668 669 670 671 672 673 674 675 676 677 678
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;

            SolidBackground* m_solidBackground;

```

};

打开 GraphRenderer.cpp 文件,使用“new”在 GraphRenderer 类构造函数中创建`m_solidBackground`的实例。

W
wizardforcel 已提交
679
```cpp
W
wizardforcel 已提交
680 681 682 683 684 685 686 687 688 689
    GraphRenderer::GraphRenderer() {

      m_solidBackground = new SolidBackground(D2D1::ColorF::Bisque);

```

}

最后,必须更改 GraphRenderer 的呈现方法,以调用新的 m_solidBackground 的呈现方法。不再需要调用 m_d2dContext 的 Clear 方法,可以将其删除。

W
wizardforcel 已提交
690
```cpp
W
wizardforcel 已提交
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
    void GraphRenderer::Render(){
    m_d2dContext->BeginDraw();
    // Clear to some color other than blank

      // m_d2dContext->Clear(D2D1::ColorF(ColorF::CornflowerBlue));

```

//平移图表

matrix 3 x2f panMatrix = matrix 3 x2f::Translation(m _ pan。x,m_pan。y);

m _ D2 dconte-> settlement(panmatrix * m _ orientation transform 2d);

//

//在此绘制对象

//

m_solidBackground->渲染(m _ D2 dconte);

//忽略 D2DERR_RECREATE_TARGET 错误

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR _ RECREATE _ TARGET)DX::ThrowIfFailed(HR);

}

您可以删除渲染方法中对`m_d2dContext::Clear()`的调用,因为它不再需要。现在是编译和运行应用程序的好时机。

### DirectX 颜色

可以使用`D2D1::ColorF`枚举来指定颜色,该枚举定义了大约 140 种预定义颜色的列表。

W
wizardforcel 已提交
727
```cpp
W
wizardforcel 已提交
728 729 730 731 732 733
    D2D_COLOR_F copyOfPredefinedColor = D2D1::ColorF(D2D1::ColorF::AliceBlue);
    // Alternative syntax using the derived helper class would be:
    D2D1::ColorF copyOfPredefinedColor2 = D2D1::ColorF(D2D1::ColorF::AliceBlue);

```

W
wizardforcel 已提交
734
| ![](img/note.png) | 注意:有关 D2D1::ColorF 枚举中可用的预定义颜色的完整列表,请右键单击 AliceBlue 或其他颜色标识符,然后从上下文菜单中选择“转到定义”。这将打开定义预定义颜色列表的 Direct2DHelper.h 文件。 |
W
wizardforcel 已提交
735

W
wizardforcel 已提交
736
| ![](img/note.png) | 注意:D2D1::ColorF 类继承自 D2D_COLOR_F 类。它是相同的,但是它定义了一些有用的函数和预定义颜色的枚举。 |
W
wizardforcel 已提交
737 738 739

您也可以通过将颜色的红、绿和蓝的数量指定为浮点值来创建自己的颜色:

W
wizardforcel 已提交
740
```cpp
W
wizardforcel 已提交
741 742 743 744 745 746 747 748 749 750 751 752 753 754
    D2D1::ColorF brightMagenta = D2D1::ColorF(1.0f, 0.0f, 1.0f);

```

ColorF 类的构造函数采用三个参数,第四个参数可选(默认为 1.0f,表示不透明度或 alpha 通道)。前三个参数是颜色中红色、绿色和蓝色的数量。这里我定义了 100%红色、0%绿色和 100%蓝色。这种组合产生了明亮的洋红色。在此颜色模型中,组件的范围是从 0.0f 到 1.0f,其中 0.0f 表示完全没有,1.0f 表示完全饱和或 100%。

您经常会看到颜色初始化如下:

`D2D1::ColorF myColor = D2D1::ColorF(D2D1::ColorF::**PredefinedColor**);`

预定义颜色是 D2D1Helper.h 文件中定义的颜色枚举中的一种颜色。这是对 ColorF 类的复制构造函数的调用。预定义颜色的嵌套引用是要复制的值。

您也可以使用十六进制表示以类似于 HTML 颜色的样式定义颜色。

W
wizardforcel 已提交
755
```cpp
W
wizardforcel 已提交
756 757 758 759 760 761 762 763
    D2D1::ColorF coffee = 0xCEAA7A;

```

这里,该值是一个无符号整数,通常写成六个十六进制数字,每个数字代表三个无符号字节,范围从 0 到 255。最低的两位数字表示蓝色的量(示例中的 7A),它们的范围可以从 00(无)到 FF (255 或 100%饱和度)。接下来的两位数(示例中的 AA)以类似的方式表示绿色的量,最高的两位数(示例中的 CE)是红色的量。

红色、绿色和蓝色的示例颜色模型称为 RGB。有时还有一个称为 alpha 通道的附加通道,通常用于表示颜色的不透明度。0%的 alpha 值表示完全透明,100%表示完全不透明。带有附加 alpha 通道的 RGB 颜色系统称为 ARGB 颜色系统,因为 32 位无符号整数的最高位用于存储 alpha 通道。

W
wizardforcel 已提交
764
| ![](img/tip.png) | 提示:小端系统(如 x86 和 ARM)上的 RGB 颜色模型导致像素颜色的字节顺序实际上是 BGR,相反,当存储在内存中时。蓝色字节是内存中最低的,红色字节是最高的。使用 ARGB 模型时,字节顺序是 BGRA。 |
W
wizardforcel 已提交
765 766 767 768 769

### 渐变背景

扎实的背景介绍了清除屏幕;下一个背景将介绍 Direct2D 的渐变画笔。我们在 Direct2D 中绘制的几乎所有东西都是使用画笔完成的。有几种不同类型的刷子。我们之前看到使用纯色画笔渲染文本。在渲染数据之前,可以通过使用线性渐变画笔为整个渲染目标着色来创建渐变背景。若要创建 GradientBackground 类,请向项目中添加两个文件,`GradientBackground.h``GradientBackground.cpp`

W
wizardforcel 已提交
770
```cpp
W
wizardforcel 已提交
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
    // GradientBackground.h
    #pragma once
    #include "DirectXBase.h"
    // Gradient background
    class GradientBackground {
    private:
          D2D1_COLOR_F *colors;   // The colors in the gradient
          float *stops;// Positions of the colors
          int count;  // The number of different colors used
          D2D1_RECT_F m_ScreenRectangle;     // The size of the rectangle we're filling

          // The linear gradient brush performs the painting
          Microsoft::WRL::ComPtr<ID2D1LinearGradientBrush> m_linearGradientBrush;

    public:
          // Creates a new gradient background
          GradientBackground(D2D1_COLOR_F colors[], float stops[], int count);  
          // Release dynamic memory
          ~GradientBackground();
          void CreateWindowSizeDependentResources
                (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
          void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
    };

```

W
wizardforcel 已提交
797
```cpp
W
wizardforcel 已提交
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
    // GradientBackground.cpp
    #include "pch.h"
    #include "GradientBackground.h"

    GradientBackground::GradientBackground(D2D1_COLOR_F colors[],
         float stops[], int count) {
    // The constructor just saves the colors and stops
    this->count = count;
    this->colors = new D2D1_COLOR_F[count];
    this->stops = new float[count];

    for(int i = 0; i < count; i++) {
          this->colors[i] = D2D1_COLOR_F(colors[i]);
          this->stops[i] = stops[i];
          }
    }
    void GradientBackground::CreateWindowSizeDependentResources
          (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context)
    {
    // Create a gradient stops array from the colors and stops
    D2D1_GRADIENT_STOP *gradientStops = new D2D1_GRADIENT_STOP[count];
    for(int i = 0; i < count; i++) {
          gradientStops[i].color = colors[i];
          gradientStops[i].position = stops[i];
          }    
    // Create a Stop Collection from this using the
    // context's create method:
    ID2D1GradientStopCollection *gradientStopsCollection;
    DX::ThrowIfFailed(
          context->CreateGradientStopCollection (
                gradientStops,    // Stops
                count,                  // How many?
                &gradientStopsCollection     // Output object
                ));
    // Create a linear gradient brush from this:
    DX::ThrowIfFailed(
          context->CreateLinearGradientBrush(
          D2D1::LinearGradientBrushProperties (
                D2D1::Point2F(0, 0),// Start point of gradient
                D2D1::Point2F(    // Finish point of gradient
                      context->GetSize().width,
                      context->GetSize().height
                      )),
          gradientStopsCollection,
          &m_linearGradientBrush));

    // Also save the rectangle we're filling
    m_ScreenRectangle = D2D1::RectF(0, 0, context->GetSize().width,
         context->GetSize().height);
    }

    void GradientBackground::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // The fill the whole screen with the gradient
    context->FillRectangle(&m_ScreenRectangle, m_linearGradientBrush.Get());
    }

    GradientBackground::~GradientBackground() {
    delete[] colors;
    delete[] stops;
    }

```

这个类的构造函数只不过是将作为参数传递的值保存到成员变量中,这样它们就可以在资源分配方法中用来创建画笔。最重要的方法是创建窗口大小相关的资源。我们希望我们的渐变填充整个渲染目标,所以我们应该把它放在窗口大小相关的资源方法。在代码示例中,您将看到在对 CreateLinearGradientBrush 的方法调用中指定的两个点。

W
wizardforcel 已提交
863
```cpp
W
wizardforcel 已提交
864 865 866 867 868 869 870 871 872 873 874 875 876 877
                D2D1::Point2F(0, 0), // Start point of gradient
              D2D1::Point2F(       // Finish point of gradient
                    context->GetSize().width,
                    context->GetSize().height
                    )),

```

当我们创建渐变画笔时,我们首先创建一个停靠点的集合,这些停靠点是混合颜色的位置,然后我们从这里创建渐变画笔本身。我还将渲染目标大小保存到了一个成员变量中,这样就不会在渲染方法中计算它了。

我使用了屏幕坐标(0,0)和(宽度,高度),使得渐变从左上角延伸到右下角。如果你想要一个从屏幕顶部到底部的垂直渐变,你可以用 0 代替屏幕宽度作为第二个点的第一个参数。同样,如果你想要一个水平延伸的渐变,你可以用 0 替换`context->GetSize().height`

创建线性渐变画笔需要指定颜色列表和颜色变化的位置列表。如果颜色为红色、绿色和蓝色,色标为 0.0f、0.2f 和 1.0f,渐变的颜色将混合如下:

W
wizardforcel 已提交
878
![](img/image019.jpg)
W
wizardforcel 已提交
879 880 881 882 883

梯度为 0%时为红色,梯度为 20%时为绿色,梯度为 100%时为蓝色。您可以根据需要添加任意多的停靠点和颜色。停靠点可以高于 100% (1.0f)。这意味着渐变的混合比可见区域延伸得更远。如果停止点不是从 0.0f 开始,渐变的开始将采用第一种颜色。同样,如果停靠点不以 1.0f 结尾,渐变画笔的其余部分将使用最后一种颜色。

为了创建一个实例并渲染渐变背景,我们遵循与纯色背景相似的模式。然而,实心背景不需要任何资源,而渐变背景在设备上创建一个笔刷,所以我们需要调用`CreateWindowSizeDependentResources`方法让渐变背景创建这个笔刷。

W
wizardforcel 已提交
884
| ![](img/note.png) | 注意:我们正在检查线性渐变,但是 Direct2D 中也有径向渐变画笔。它们从同心圆的中心点向外辐射梯度。 |
W
wizardforcel 已提交
885 886 887

用对 GraphRenderer.h 文件中新 GradientBackground.h 头的引用替换对 SolidBackground.h 头的引用。

W
wizardforcel 已提交
888
```cpp
W
wizardforcel 已提交
889 890 891 892 893 894 895 896 897 898
    //
    // Additional headers for graph objects here
    //

      #include "GradientBackground.h"

```

用新的 GradientBackground 的声明替换 SolidBackground 成员变量的声明。

W
wizardforcel 已提交
899
```cpp
W
wizardforcel 已提交
900 901 902 903 904 905 906 907 908 909 910 911
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;

            GradientBackground* m_gradientBackground;

```

};

将对 SolidBackground 构造函数的调用替换为对 GraphRenderer 的构造函数中的新 GradientBackground 构造函数的调用。

W
wizardforcel 已提交
912
```cpp
W
wizardforcel 已提交
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
    GraphRenderer::GraphRenderer() {

      D2D1_COLOR_F colors[] = {
            D2D1::ColorF(ColorF::PaleGoldenrod),
            D2D1::ColorF(ColorF::PaleTurquoise),
            D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f)
            };

      float stops[] = {
            0.0f,
            0.5f,
            1.0f
            };

      m_gradientBackground = new GradientBackground(colors, stops, 3);

```

}

W
wizardforcel 已提交
933
| ![](img/note.png) | 注意:标准颜色,如 ColorF::Red,在枚举中声明为整数 RGBA 值。我们的 GradientBackground 类需要一个 D2D1_COLOR_F 结构的数组,每个结构将一种颜色表示为四个不同的浮点值。这就是在对 D2D1::ColorF()的调用中包装标准颜色的原因。 |
W
wizardforcel 已提交
934 935 936

调用方法在 GraphRenderer 的`CreateWindowSizeDependentResources`方法中创建背景的窗口大小相关资源。

W
wizardforcel 已提交
937
```cpp
W
wizardforcel 已提交
938 939 940 941 942 943 944 945 946 947 948
    void GraphRenderer::CreateWindowSizeDependentResources() {
    DirectXBase::CreateWindowSizeDependentResources();

      m_gradientBackground->CreateWindowSizeDependentResources(m_d2dContext);

```

}

最后,用渲染我们的新 GradientBackground 的调用替换渲染 SolidBackground 的调用。

W
wizardforcel 已提交
949
```cpp
W
wizardforcel 已提交
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
    void GraphRenderer::Render() {
    m_d2dContext->BeginDraw();

      // Reset the transform matrix so our gradient does not pan
      m_d2dContext->SetTransform(m_orientationTransform2D);
      m_gradientBackground->Render(m_d2dContext);

```

//平移图表

matrix 3 x2f panMatrix = matrix 3 x2f::Translation(m _ pan。x,m_pan。y);

m _ D2 dconte-> settlement(panmatrix * m _ orientation transform 2d);

//

//在此绘制对象

//

//忽略 D2DERR_RECREATE_TARGET 错误

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR _ RECREATE _ TARGET)DX::ThrowIfFailed(HR);

}

我假设渐变背景不受图表平移(平移)的影响。这只是为了平移数据。为此,我添加了在重置转换矩阵后立即呈现的调用。

集合中的梯度数量可以非常大,并且可以生成而不是存储,或者硬编程到代码中。下面创建的渐变背景产生了一个随机的彩色彩虹,这可以替换我们放入 GraphRenderer 的构造函数中的代码:

W
wizardforcel 已提交
983
```cpp
W
wizardforcel 已提交
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
    const int count = 500;   
    D2D1_COLOR_F *cols = new D2D1_COLOR_F[count];
    float* stops = new float[count];

    for(int i = 0; i < count; i++) {
         cols[i] = D2D1::ColorF(
              0.75f+(float)(rand()%192)/192.0f, // Random pastels
              0.75f+(float)(rand()%192)/192.0f,
              0.75f+(float)(rand()%192)/192.0f);
         stops[i] = (float)i / (count - 1);
         }

    m_gradientBackground = new GradientBackground(cols, stops, count);

```

示例代码产生了一个相当令人满意的渐变,看起来应该如图 13 所示。

W
wizardforcel 已提交
1002
![](img/image021.jpg)
W
wizardforcel 已提交
1003 1004 1005 1006 1007 1008 1009 1010 1011

图 13:彩虹渐变

渐变背景非常适合绘制图表,尤其是在资源有限的设备上,因为它们比标准的纯色背景更吸引人。它们比位图背景渲染和初始化更快,并且不会占用任何磁盘空间,也不会因为存储图像而使应用程序膨胀。

### 位图背景

接下来,我们将通过创建位图背景来检查图像的加载和显示。您可以使用 WIC(窗口成像组件)加载位图图像(或其他几种标准图像格式)并将其显示为背景。位图背景提供了最大的灵活性,但在渲染性能方面成本更高。

W
wizardforcel 已提交
1012
| ![](img/note.png) | 注意:由于 WIC 解码器的灵活性,这个类将能够加载许多标准的图像文件格式。Windows 8 附带了 JPEG、TIFF、PNG、BMP 和其他格式的解码器。代码没有变化,我们的图表应该能够加载任何这些图像格式。 |
W
wizardforcel 已提交
1013 1014 1015

首先要做的是向项目中添加一个图像文件。在解决方案资源管理器中右键单击项目名称,然后单击**添加现有项目...**如图 14 所示。

W
wizardforcel 已提交
1016
![](img/image022.jpg)
W
wizardforcel 已提交
1017 1018 1019 1020 1021

图 14:添加现有项目

在出现的**添加现有项目**窗口中找到您想要使用的图像文件。在这个例子中,我将使用一个名为 background5.jpg 的图像文件。选择文件后,按图 15 点击**添加**

W
wizardforcel 已提交
1022
![](img/image023.jpg)
W
wizardforcel 已提交
1023 1024 1025 1026 1027

图 15:添加图像

现在我们已经向项目中添加了位图,我们可以通过添加 BitmapBackground.h 和 BitmapBackground.cpp 文件来创建 BitmapBackground 类。

W
wizardforcel 已提交
1028
```cpp
W
wizardforcel 已提交
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
    // BitmapBackground.h
    #pragma once
    #include "DirectXBase.h"

    // Defines a background consisting of a bitmap image
    class BitmapBackground {
    private:
          ID2D1Bitmap * m_bmp; // The image to draw
          D2D1_RECT_F m_screenRectangle; // Destination rectangle

    public:
          // Constructor for bitmap backgrounds
          BitmapBackground();

          // Release dynamic memory
          ~BitmapBackground();

          void CreateDeviceDependentResources
                (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context,
                IWICImagingFactory2 *wicFactory, LPCWSTR filename);
          void CreateWindowSizeDependentResources(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
          void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
    };

```

W
wizardforcel 已提交
1056
| ![](img/tip.png) | 提示:id2d 1 位图是设备相关资源。很多 WinRT 设备没有专用的 GPU RAM,所以位图会存储在有限的系统内存中。为了减少对系统资源的压力,最好只加载相对较小的位图,或者如果您希望应用程序在这些设备上平稳运行,最好只加载少量位图。 |
W
wizardforcel 已提交
1057

W
wizardforcel 已提交
1058
```cpp
W
wizardforcel 已提交
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
    // BitmapBackground.cpp
    #include "pch.h"
    #include "BitmapBackground.h"

    // This constructor must be called at some point after the
    // WIC factory is initialized!
    BitmapBackground::BitmapBackground() { }

    BitmapBackground::~BitmapBackground(){
    m_bmp->Release();
    }

    void BitmapBackground::CreateDeviceDependentResources
          (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context,
         IWICImagingFactory2 *wicFactory, LPCWSTR filename) {
    // Create a WIC decoder
    IWICBitmapDecoder *pDecoder;

    // Decode a file, and make sure you've added the file to the project first:
    DX::ThrowIfFailed(wicFactory->CreateDecoderFromFilename(filename,
          nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &pDecoder));

    // Read a frame from the file (png, jpg, bmp, and other images only have one frame so
    // the index is always 0):
    IWICBitmapFrameDecode *pFrame = nullptr;
    DX::ThrowIfFailed(pDecoder->GetFrame(0, &pFrame));

    // Create format converter to ensure data is the correct format despite the
    // file's format.
    // It's likely the format is already perfect but we can't be sure:
    IWICFormatConverter *m_pConvertedSourceBitmap;
    DX::ThrowIfFailed(wicFactory->CreateFormatConverter(&m_pConvertedSourceBitmap));
    DX::ThrowIfFailed(m_pConvertedSourceBitmap->Initialize(
          pFrame, GUID_WICPixelFormat32bppPRGBA,
          WICBitmapDitherTypeNone, nullptr,
          0.0f, WICBitmapPaletteTypeCustom));

    // Create a Direct2D bitmap from the converted source
    DX::ThrowIfFailed(context->CreateBitmapFromWicBitmap(
         m_pConvertedSourceBitmap, &m_bmp));

    // Release the dx objects we used to create the bmp
    pDecoder->Release();
    pFrame->Release();
    m_pConvertedSourceBitmap->Release();
    }

    void BitmapBackground::CreateWindowSizeDependentResources(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // Save a rectangle the same size as the area to draw the background
    m_screenRectangle = D2D1::RectF(0, 0, context->GetSize().width, context->GetSize().height);
    }

    void BitmapBackground::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    context->DrawBitmap(m_bmp, &m_screenRectangle);
    }

```

构造函数是空的,析构函数只是释放位图。这个背景的大部分代码都围绕着用 WIC 工厂和相关组件加载和解码图像。这是通过`BitmapBackground::CreateDeviceDependentResources`方法完成的。我们首先使用威驰工厂的`CreateDecoderFromFile`方法创建一个解码器。然后我们用它从文件中读取一个帧。JPEG、PNG 或位图图像中只有一帧,因此我们传递的帧索引为 0(在对 pDecoder 的`GetFrame`方法的调用中)。有可能我们刚刚读取的帧格式不是我们想要使用的标准 RGB 像素。使用 WIC 格式转换器转换数据,最后,创建一个`ID2D1Bitmap`对象。然后,我们可以将转换后的帧渲染到屏幕上。

这个类的构造函数是空的,位图的创建委托给`CreateDeviceDependentResources`方法,因为我们需要使用 WIC 解码器,但是当调用图形渲染器的构造函数时,WIC 解码器没有初始化。

W
wizardforcel 已提交
1122
| ![](img/note.png) | 注意:在 GraphRenderer 类中,调用构造函数和方法来创建资源的顺序是构造函数、创建设备独立资源、创建设备资源,然后创建窗口大小依赖资源。这个顺序是在 DirectXBase.cpp 文件中指定的。DirectXPage 也通过调用构造函数在这个排序中发挥作用。 |
W
wizardforcel 已提交
1123

W
wizardforcel 已提交
1124
![](img/image025.png)
W
wizardforcel 已提交
1125 1126 1127 1128 1129

图 16:资源创建方法的顺序

要创建我们类的新实例并呈现位图背景,请在 GraphRenderer.h 文件中将 GradientBackground.h 头的#include 替换为新的 BitmapBackground.h 头的#include。

W
wizardforcel 已提交
1130
```cpp
W
wizardforcel 已提交
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
    //
    // Additional headers for graph objects here
    //

      #include "BitmapBackground.h"

```

用 GraphRenderer.h 文件底部名为`m_bitmapBackground`的新 BitmapBackground 成员变量的定义替换 GradientBackground 成员变量的定义。

W
wizardforcel 已提交
1141
```cpp
W
wizardforcel 已提交
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;

            BitmapBackground* m_bitmapBackground;

```

};

用对我们的新类的构造函数的调用来替换 GraphRenderer 的构造函数中用于构造渐变背景的代码。

W
wizardforcel 已提交
1154
```cpp
W
wizardforcel 已提交
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
    GraphRenderer::GraphRenderer() {

      m_bitmapBackground = new BitmapBackground();

```

}

在 Graph Renderer 的同名方法中调用新位图背景的`CreateDeviceResources`方法(记得将文件名改为你使用的实际图像)。

W
wizardforcel 已提交
1165
```cpp
W
wizardforcel 已提交
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

      // Load the bitmap for our background
      m_bitmapBackground->CreateDeviceDependentResources(
            m_d2dContext,
            m_wicFactory.Get(),
            L"Background5.jpg");

```

}

将对 GraphRenderer 的`CreateWindowSizeDependentResources`方法中的`m_gradientBackground->CreateWindowSizeDependentResources`方法的调用替换为对新类的`CreateWindowSizeDependentResources`的调用。

W
wizardforcel 已提交
1181
```cpp
W
wizardforcel 已提交
1182 1183 1184 1185 1186 1187 1188 1189 1190
    void GraphRenderer::CreateWindowSizeDependentResources() {
    DirectXBase::CreateWindowSizeDependentResources();
    m_bitmapBackground->CreateWindowSizeDependentResources(m_d2dContext);
    }

```

最后,在应用平移矩阵之前,将对 GradientBackground 的渲染方法的调用替换为对图形渲染器的渲染方法中新的 BitmapBackground 的渲染方法的调用。

W
wizardforcel 已提交
1191
```cpp
W
wizardforcel 已提交
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    void GraphRenderer::Render() {
    m_d2dContext->BeginDraw();

    // Reset the transform matrix so our background does not pan
    m_d2dContext->SetTransform(m_orientationTransform2D);

      m_bitmapBackground->Render(m_d2dContext);

```

//平移图表

matrix 3 x2f panMatrix = matrix 3 x2f::Translation(m _ pan。x,m_pan。y);

m _ D2 dconte-> settlement(panmatrix * m _ orientation transform 2d);

//

//在此绘制对象

//

W
wizardforcel 已提交
1214
| ![](img/note.png) | 注意:使用位图背景的缺点是会增加应用程序的占用空间。图像会占用硬盘空间,但根据格式的不同,这可能可以忽略不计。然而,一旦图像被解码并作为 id2d 1 位图加载到我们的应用程序中,它将不再使用文件格式的压缩算法。例如,一个 2247 × 1345 像素的 JPEG 只消耗不到 1 MB 的磁盘空间,但是当加载到应用程序中时,它会增加将近 9 MB 的内存使用。这 9 MB 是因为图像中有 2247 × 1345 个像素,每个像素存储为红色、绿色和蓝色字节。所以整个图像是 2247 × 1345 × 3 字节,约 8.6 MB。 |
W
wizardforcel 已提交
1215 1216 1217 1218 1219 1220 1221 1222 1223

## 第 5 章:二维数据图

我们现在将通过研究如何绘制一些数据来继续在 Direct2D 中渲染一些形状。这将是一个在 Direct2D 中渲染矢量图形的小介绍。为了保持示例代码的简单性,我将使用随机生成的值数组作为将要绘制的数据。当在实际应用程序中使用时,这些数据可能来自数据库或其他实际数据源。

图形变量基类

我们将在以下部分创建散点图和折线图。散点图和折线图都有许多共同的特征,所以我们将实现一个小的类层次结构。散点图和折线图都将继承自下面的基类,称为 GraphVariable 类。向项目中添加两个文件来定义这个基类,GraphVariable.h 和 GraphVariable.cpp。

W
wizardforcel 已提交
1224
```cpp
W
wizardforcel 已提交
1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
    // GraphVariable.h
    #pragmaonce

    #include "DirectXBase.h"

    // This class represents a generic plottable variable
    // It is the base class for the ScatterPlot and the LineChart
    // classes.
    class GraphVariable abstract
    {
    protected:
         D2D1_POINT_2F* m_points; // These are the x values and y values of each node
         int m_nodeCount;     // This is a record of the total number of nodes

         // Record of smallest point
         float m_minX, m_minY; // Used to auto pan
    public:
         // Getters for min values
         float GetMinX() { return m_minX; }
         float GetMinY() { return m_minY; }

         GraphVariable(float* x, float* y, int count);

         virtual void CreateDeviceDependentResources
    (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) = 0;
         virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context)
     = 0;
    };

```

该类由作为 D2D1_POINT_2F 结构的数组的节点,以及节点集合中最小 x 和 y 值的计数和记录组成。我们记录最小的 x 和 y 值的原因是为了以后自动平移数据,以便在图表的初始视图中可以看到一些数据。

W
wizardforcel 已提交
1258
```cpp
W
wizardforcel 已提交
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
    // GraphVariable.cpp
    #include "pch.h"
    #include "GraphVariable.h"

    GraphVariable::GraphVariable(float* x, float* y, int count)
    {
    this->m_nodeCount = count;

    // Assume the minimum is the first value
    m_minX = x[0]; m_minY = y[0];

    // Create an array of points from the *x and *y.
    // We can't assume that the *x and *y are permanent so
    // allocate seperate heap space for a copy of the data
    // as D2D1::Point2F's:
    m_points = new D2D1_POINT_2F[count];
    for(int i = 0; i < count; i++)
         {
         m_points[i] = D2D1::Point2F(x[i], y[i]);

         // Check if the point is lower than the current minimum
         if(x[i] < m_minX) m_minX = x[i];
         if(y[i] < m_minY) m_minY = y[i];
         }
    }

```

为此基类定义的唯一方法是将两个浮点数组(*x 和*y)复制到成员变量 m_points 的构造函数。它还会查找并记录*x 和*y 数组中的最小值,以便以后自动平移。

### 散点图

散点图是二维数据最常见和最有用的描述之一。它通常在图表上表示为节点的集合,每个节点都绘制为一个基本形状,如圆形、正方形或三角形。每个节点代表二维空间中的某个点,每个节点都有各种属性,包括位置、颜色和大小。散点图通常用于表示二维数据,其中每个维度都是参数化的(如重量和高度)。这些值不是按 x 轴排序的,它们不会像折线图那样显示连续性。散点图需要两个变量:一个变量的值决定一个节点向左或向右多远,而另一个变量的值决定一个节点向上或向下多远。散点图非常适合显示可能相关的数据,因为相关数据看起来像是围绕一条直线收集的,或者像是一簇簇节点。

如果您不需要保持稳定的 60 帧/秒(由视频同步决定),您可以将交换链的同步间隔更改为 2、3 或 4。默认情况下,交换链将尝试在视频同步后以 60 帧/秒的速度出现。将调用的第一个参数的值更改为以下值:

`HRESULT hr = m_swapChain->Present1(1, 0, &parameters);`

将 DirectXBase.cpp 文件中调用的第一个参数的值更改为 2、3 或 4 将导致交换链最多休眠应用程序 4 次 V-syncs,而不是通常的 1 次。这将以平滑帧速率为代价,获得更好的功耗。输入 1 表示 60 fps,2 表示 30 fps,3 表示 20 fps,4 表示 15 fps。充分绘制数据所需的每秒帧数通常不是 60 fps。对于大多数应用,30 fps 或 20 fps 应该可以。即使 15 fps 在图表应用程序中看起来也相当流畅。

下面的散点图继承了之前定义的 GraphVariable 基类。向您的项目中添加两个文件,散点图. h 和散点图. cpp。

W
wizardforcel 已提交
1301
```cpp
W
wizardforcel 已提交
1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
    // ScatterPlot.h
    #pragmaonce

    #include "DirectXBase.h"
    #include "GraphVariable.h"

    // Two different example shapes
    enum NodeShape { Circle, Square };

    // This class represents data to be drawn as a scatter plot
    class ScatterPlot: public GraphVariable {
         float m_HalfNodeSize;// Half size of the nodes
         D2D1::ColorF m_NodeColor; // The color of the nodes
         ID2D1SolidColorBrush* m_brush; // Solid brush for painting nodes
         NodeShape m_NodeShape;    // The shape of the nodes

    public:
         // Public constructor
         ScatterPlot(float* x, float* y, float nodeSize,
              D2D1::ColorF nodeColor, NodeShape nodeShape, int count);

         virtual void CreateDeviceDependentResources(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;
         virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;
    };

```

W
wizardforcel 已提交
1329
```cpp
W
wizardforcel 已提交
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396
    // ScatterPlot.cpp
    #include "pch.h"
    #include "ScatterPlot.h"

    using namespace D2D1;
    using namespace DirectX;

    ScatterPlot::ScatterPlot(float* x, float* y, float nodeSize,
         D2D1::ColorF nodeColor, NodeShape nodeShape, int count):
              m_NodeColor(0), GraphVariable(x, y, count) {
    // Save half the node size. The nodes are drawn with
    // the point they're representing at the middle of the shape.
    this->m_HalfNodeSize = nodeSize / 2;

    this->m_NodeShape = nodeShape;
    this->m_NodeColor = nodeColor;
    }

    void ScatterPlot::Render(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    switch(m_NodeShape) {    
         // Draw as circle nodes
         case NodeShape::Circle:
              for(int i = 0; i < m_nodeCount; i++) {
                    context->FillEllipse(D2D1::Ellipse(m_points[i], m_HalfNodeSize,
                         m_HalfNodeSize), m_brush);
                    }
              break;

         // Draw as square nodes
         case NodeShape::Square:
              for(int i = 0; i < m_nodeCount; i++) {
                    context->FillRectangle(D2D1::RectF(m_points[i].x -
                         m_HalfNodeSize,
                    m_points[i].y - m_HalfNodeSize, m_points[i].x + m_HalfNodeSize,
                    m_points[i].y + m_HalfNodeSize), m_brush);
                    }
              break;

         // Additional shapes could follow

         default:
              break;
         }
    }

    void ScatterPlot::CreateDeviceDependentResources
         (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // Create a brush of the specified color for painting the nodes
    DX::ThrowIfFailed(context->CreateSolidColorBrush(ColorF(m_NodeColor),
          &m_brush));
    }

```

构造函数将颜色和节点形状设置保存到成员变量中。这个类的 Render 方法显示了如何呈现两个基本形状,椭圆和矩形。这些形状是用上下文的 FillXXX 方法绘制的,其中 XXX 是一些原始形状。这些方法需要画笔(在`CreateDeviceDependentResources`方法中创建),以及要绘制的形状。

FillEllipse 方法将椭圆作为其第一个参数。这可以预先生成或动态计算。椭圆有一个位置和 x、y 半径。例如,要创建一个中心位于像素(100,150),宽 60 度,高两倍的椭圆,可以使用 D2D1::Ellipse(100,150,60,120)。我通过指定 x 和 y 半径为相同的值来创建圆。

FillRectangle 方法将矩形作为其第一个参数。这需要指定矩形左上角的 x 坐标和 y 坐标,以及宽度和高度。

ID2D1DeviceContext 接口有两个版本的每种图元绘制方法;一个填充形状(圆角、填充矩形等)。)而另一个只呈现一个空形状的轮廓(上下文的 DrawEllipse 或 DrawRectangle 方法)。

我们现在将向我们的 GraphRenderer 添加散点图,但是我们的图表应用程序有一些大问题,这些问题将因此变得很明显,并将很好地引导到下一部分。

将#include 添加到 GraphRenderer.h 文件的顶部(我还包含了一个渐变背景)。

W
wizardforcel 已提交
1397
```cpp
W
wizardforcel 已提交
1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
    //
    // Additional headers for graph objects here
    //

      #include "GradientBackground.h"
      #include "ScatterPlot.h"

```

向图形渲染器类添加一个`GraphVariable`指针成员。这将作为本章中的`ScatterPlot`,但也将作为后面的`LineChart`

W
wizardforcel 已提交
1409
```cpp
W
wizardforcel 已提交
1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;

            // Background
            GradientBackground *m_gradientBackground;
           // Plottable data
      GraphVariable* m_graphVariable;

```

在 GraphRenderer.cpp 文件的`GraphRenderer's`构造函数中调用`GradientBackground`和新`ScatterPlot`的构造函数。

W
wizardforcel 已提交
1423
```cpp
W
wizardforcel 已提交
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
    GraphRenderer::GraphRenderer()
    {

      // Create the gradient background:
      D2D1_COLOR_F colors[] = {
           D2D1::ColorF(ColorF::PaleGoldenrod),
           D2D1::ColorF(ColorF::PaleTurquoise),
           D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f)
           };
      float stops[] = { 0.0f, 0.5f, 1.0f };
      m_gradientBackground = new GradientBackground(colors, stops, 3);

      // Create the scatter plot:
      const int count = 500;    // Create 500 nodes
      float* x = new float[count];
      float* y = new float[count];

      // Create random points to plot, these
      // would usually be read from some data source:
      for(int i = 0; i < count; i++) {
           x[i] = (float)(rand() % 2000);
           y[i] = (float)(rand() % 1000);
           }

      m_graphVariable = new ScatterPlot(x, y, 10.0f,
           D2D1::ColorF::Chocolate,
           NodeShape::Circle, count);

      delete[] x;
      delete[] y;

```

}

调用`ScatterPlot's` `CreateDeviceDependentResources`方法和`GradientBackground's` `CreateWindowSizeDependentResources`方法。在下面的代码中,我还设置了`m_pan`成员变量的初始值,这样当应用程序启动时,数据将是可见的。否则,数据将从屏幕上消失。

W
wizardforcel 已提交
1461
```cpp
W
wizardforcel 已提交
1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

      // Call the create device resources for our graph variable
      m_graphVariable->CreateDeviceDependentResources(m_d2dContext);

```

}

void graph renderer::createwindowsize dependent resources(){ 0

DirectXBase::createwindowsize dependent resources();

//为渐变背景创建窗口大小资源

m _ gradientbackground-> createwindowsize dependents(m _ D2 dcontext);

//设置初始平移值,使最低节点在角落可见

m_pan。X = -m_graphVariable->GetMinX();

m_pan(消歧义)。y =-m _ D2 dcocontext-> get size()。height-m _ graph variable-> getminy();

}

最后,在`GraphRenderer::Render`方法中渲染`GradientBackground``ScatterPlot`

W
wizardforcel 已提交
1490
```cpp
W
wizardforcel 已提交
1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527
    void GraphRenderer::Render()
    {
    m_d2dContext->BeginDraw();

      // Reset the transform matrix so our background does not pan
      m_d2dContext->SetTransform(m_orientationTransform2D);
      // Render the background
      m_gradientBackground->Render(m_d2dContext);

```

//平移图表

matrix 3 x2f panMatrix = matrix 3 x2f::Translation(m _ pan。x,m_pan。Y + m_d2dContext->GetSize()。高度);

m _ D2 dconte-> settlement(panmatrix * m _ orientation transform 2d);

//

//在此绘制对象

//

//渲染图形变量

m _ graph variable-> Render(m _ d2d context);

//忽略 D2DERR_RECREATE_TARGET 错误

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR _ RECREATE _ TARGET)DX::ThrowIfFailed(HR);

}

运行应用程序时,您应该会看到类似于图 17 的内容。

W
wizardforcel 已提交
1528
![](img/image026.jpg)
W
wizardforcel 已提交
1529 1530 1531 1532 1533

图 17:散点图截图

我们图表的问题是,这看起来像散点图,但实际上 y 轴是反的。在计算机图形学中,点(0,0)是指屏幕左上角的像素。y 值在屏幕下方增加,而 x 值在屏幕右侧增加。y 轴中的这种行为与图表通常呈现的方式完全相反。通常左下角的点用来表示原点(0,0)。如果数据从随机变为 x 轴和 y 轴的坐标递增,您将看到一条从左上角向右下角延伸的节点对角线。

W
wizardforcel 已提交
1534
```cpp
W
wizardforcel 已提交
1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554
    // Create the scatter plot:
    const int count = 500;    // Create 500 nodes
    float* x = new float[count];
    float* y = new float[count];

    // Create random points to plot, these
    // would usually be read from some data source:
    for(int i = 0; i < count; i++) {
         x[i] = i; //(float)(rand() % 2000);
         y[i] = i; //(float)(rand() % 1000);
         }

    m_graphVariable = new ScatterPlot(x, y, 10.0f,
         D2D1::ColorF::Chocolate,
         NodeShape::Circle, count);

```

运行应用程序时,您应该会看到类似图 18 的内容。

W
wizardforcel 已提交
1555
![](img/image027.jpg)
W
wizardforcel 已提交
1556 1557 1558 1559 1560 1561 1562 1563 1564

图 18:以直线渲染的散点图

这是一张 500 个节点排成一行的图像,它们靠得很近,完全没有间隙。问题是整个 y 轴需要翻转。通过将散点图的每个 y 值乘以-1 并考虑屏幕高度,我们可以相当容易地做到这一点。我们可以在创建散点图数据或渲染节点时这样做。然而,还有一个更好的方法。通过应用一个变换,GPU 只需几行代码就可以轻松地为我们翻转 y 轴。

### 二维变换

DirectX 场景的移动、旋转、缩放和许多其他方面都可以使用变换矩阵来实现。通过创建可以控制渲染几乎所有方面的矩阵来应用变换:从对象的大小和旋转设置,到它在屏幕上的位置和最终像素。

W
wizardforcel 已提交
1565
| ![](img/note.png) | 注意:显卡是专门为执行这种操作而构建的。它将矩阵变换应用于点或顶点的集合比中央处理器更有效。它是一个固有的并行设备,有许多低功耗执行单元(可能有数百个内核),而多核 CPU 只有几个高功耗内核。 |
W
wizardforcel 已提交
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579

矩阵使图形编程变得更加容易。矩阵乘法是累积的,所以如果你用一个旋转矩阵乘以一个平移矩阵,就会得到一个既平移又旋转的矩阵。当我们使用 DirectX 渲染时,这正是各种变换应用于我们的对象的方式。

图形编程是引入矩阵运算的绝佳方式。使用结果绘制场景时,很容易看到矩阵相乘的效果。二维和三维图形中使用的矩阵通常非常小,可能由 3 到 16 个组件组成。矩阵乘法的步骤可以在网上很多地方找到,本书不做考查。

我不会详细描述矩阵乘法,但是矩阵乘法和常规算术最重要的区别之一是矩阵乘法不可交换。如果我们有两个矩阵,一个用于平移,另一个用于旋转,它们相乘的顺序非常重要:

*   平移*旋转←先平移再旋转
*   旋转*平移←先旋转再平移

顶部产品首先平移,然后围绕平移点旋转。下积将围绕原点旋转,然后沿旋转方向平移点。此外,当两个矩阵相乘时,结果是一个矩阵,它做了两个原始矩阵所做的事情。

`Matrix3x2F`类中有静态助手函数,可以用来创建公共转换。功能有`Translation``Rotation``Scale``Skew``Identity`。要应用变换矩阵的集合,用适当的辅助函数创建矩阵,将它们相乘,并将结果矩阵作为参数提供给`SetTransform`方法。`SetTransform`是属于`ID2D1DeviceContext`的方法,用于设置上下文当前的变换矩阵。例如,为了平移和平移,或者缩放和旋转,为每个变换创建三个矩阵。调用`SetTranform`方法时,将三个矩阵相乘。乘法运算产生了一个矩阵,它可以完成平移、缩放和旋转这三个任务。

W
wizardforcel 已提交
1580
```cpp
W
wizardforcel 已提交
1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592
    // Define each of the transformations
    Matrix3x2F pan = Matrix3x2F::Translation(10.0f, 15.0f); // Pan 10 × 15 pixels
    Matrix3x2F scale = Matrix3x2F::Scale(10.0f, 10.0f); // Scale 10 times the size
    Matrix3x2F rotate = Matrix3x2F::Rotation(25.0f);// Rotate 25 degrees

    // Apply them all by multiplying together
    m_d2dContext->SetTransform(pan * scale * rotate);

```

对于下面的讨论,我将使用一个新的 Direct2D (XAML)模板项目进行说明。之后,我们将把转换应用到我们的制图应用程序中。被修改的代码在`SimpleTextRenderer::Render`方法中。我们感兴趣的行已经在以下代码中突出显示:

W
wizardforcel 已提交
1593
```cpp
W
wizardforcel 已提交
1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
    void SimpleTextRenderer::Render()
    {
         m_d2dContext->BeginDraw();

         m_d2dContext->Clear(ColorF(BackgroundColors[m_backgroundColorIndex]));

           // Position the rendered text.
           Matrix3x2F translation = Matrix3x2F::Translation(
                m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
                m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
                );

           // Note that the m_orientationTransform2D matrix is post-multiplied here
           // in order to correctly orient the text to match the display orientation.
           // This post-multiplication step is required for any draw calls that are
           // made to the swap chain's target bitmap. For draw calls to other targets,
           // this transform should not be applied.
           m_d2dContext->SetTransform(translation * m_orientationTransform2D);

```

m _ d2dcontext->

点 2F(0.0f,0.0f),

m_textLayout.Get(),

m_blackBrush。Get(),

D2D1_DRAW_TEXT_OPTIONS_NO_SNAP

);

//忽略 D2DERR_RECREATE_TARGET。此错误表明设备

//丢失。它将在下一次呼叫 Present 时处理。

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR_RECREATE_TARGET)

{

DX::ThrowIfFailed(HR);

}

m _ renderNeeded = false

}

翻译转换

平移变换应用于矩阵,矩阵用于变换一组点。这是一个移动变换,可以用来平移我们的图表。打开一个新的 Direct2D (XAML)项目,并打开 SimpleTextRenderer.cpp 文件。向下滚动到`Render`方法,在第 103 行,您将看到一个翻译矩阵正在创建。这个特殊的矩阵计算文本的 x 和 y 值,这样它就开始居中,并且可以用指针拖动。DirectX XAML 页面捕获指针移动,记录`m_textPosition`成员变量中的 x 和 y 位置。

W
wizardforcel 已提交
1649
```cpp
W
wizardforcel 已提交
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         m_windowBounds.Width / 2.0f -
              m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
         m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
         );

```

矩阵由两部分组成,x 轴或水平轴的平移量和 y 轴或垂直轴的平移量。

W
wizardforcel 已提交
1661
```cpp
W
wizardforcel 已提交
1662 1663 1664 1665 1666 1667 1668 1669 1670
    Matrix3x2F translation = Matrix3x2F::Translation (
         amountToMoveInHorizontal, // X-axis 0 is far left
         amountToMoveInVertical         // Y-axis 0 is top of screen
         );

```

要将文本放在屏幕左上角,您可以将这两个值都设置为`0.0f`。这意味着根本不要平移轴。

W
wizardforcel 已提交
1671
```cpp
W
wizardforcel 已提交
1672 1673 1674 1675 1676 1677 1678 1679 1680
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         0.0f, 0.0f
         );

```

这将产生类似于图 19 的结果,文本将不再随着指针移动。

W
wizardforcel 已提交
1681
![](img/image028.jpg)
W
wizardforcel 已提交
1682 1683 1684 1685 1686 1687 1688 1689 1690

图 19:无翻译

在这个特殊的程序中,我们还看到先前定义的矩阵乘以一个名为`m_orientationTransform2D`的矩阵(在上下文中称为`SetTransform)`)。当程序在 WinRT 设备上运行时,方向矩阵会更新,这样,如果用户转动屏幕,文本可以自动更正,并始终垂直显示。方向矩阵的值是在 DirectXBase 类中设置的。请记住,两个或多个矩阵可以相乘产生一个包含原始矩阵所有变换的矩阵。

旋转变换

Direct2D 中的旋转变换允许对象围绕某个定义的点顺时针或逆时针旋转。如果您在平移矩阵之后定义旋转变换,然后用这个新矩阵乘以平移和方向矩阵,您将看到文本顺时针旋转了 45 度。

W
wizardforcel 已提交
1691
```cpp
W
wizardforcel 已提交
1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710
         // Position the rendered text.
         Matrix3x2F translation = Matrix3x2F::Translation(
              m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
              m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
              );
    // Rotate text about the middle
    Matrix3x2F rotation = Matrix3x2F::Rotation (
         45.0f,    // Angle to rotate in degrees, clockwise is positive
         D2D1::Point2F (
         m_textMetrics.widthIncludingTrailingWhitespace / 2.0f, // X position of origin
              m_textMetrics.height / 2.0f    // Y position of origin
              )
         );

    // Apply the rotation, then the translation, and then the orientation matrix
    m_d2dContext->SetTransform(rotation * translation * m_orientationTransform2D);

```

W
wizardforcel 已提交
1711
![](img/image029.png)
W
wizardforcel 已提交
1712 1713 1714 1715 1716

图 20:旋转

图 20 中的文本围绕其中心点旋转,因为这是旋转矩阵中指定的原点。矩阵中指定的原点是旋转将要发生的点;这一点将保持不变。相反,如果我们指定原点作为旋转点(0,0),文本将围绕其左上角旋转(输出如图 21 所示)。

W
wizardforcel 已提交
1717
```cpp
W
wizardforcel 已提交
1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
         m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
         );

         // Rotate text about the middle
         Matrix3x2F rotation = Matrix3x2F::Rotation (
              45.0f,    // Angle to rotate in degrees
              D2D1::Point2F (
                    0.0f, // X position of origin
                    0.0f // Y position of origin
                    )
              );
         // Apply the rotation then the translation then the orientation matrix
         m_d2dContext->SetTransform(rotation * translation *
          m_orientationTransform2D);

```

W
wizardforcel 已提交
1738
![](img/image030.jpg)
W
wizardforcel 已提交
1739 1740 1741 1742 1743

图 21:旋转

另一个需要重申的极其重要的一点是,矩阵乘法是不可交换的。如果我们在调用`SetTransform`时,通过将旋转作为矩阵乘法字符串中的第二个操作数来应用平移后的旋转,我们会看到效果非常不同。

W
wizardforcel 已提交
1744
```cpp
W
wizardforcel 已提交
1745 1746 1747 1748
         m_d2dContext->SetTransform(translation * rotation * m_orientationTransform2D);

```

W
wizardforcel 已提交
1749
![](img/image031.jpg)
W
wizardforcel 已提交
1750 1751 1752

图 22:旋转

W
wizardforcel 已提交
1753
![](img/image032.png)
W
wizardforcel 已提交
1754 1755 1756 1757 1758 1759 1760 1761 1762

图 23:转换的顺序

在图 22 中,文本几乎完全旋转出屏幕。旋转的原点是(0,0),屏幕的左上角(创建矩阵的调用中的第二个参数)。图 23 说明了导致文本从屏幕上旋转一半的转换顺序:

比例变换

缩放将乘数应用于一个或两个轴,以便缩小或放大渲染的形状。缩放变换需要三个参数,一个 x 乘数、一个 y 乘数和一个要缩放的原点。这段代码的输出应该如图 24 所示。

W
wizardforcel 已提交
1763
```cpp
W
wizardforcel 已提交
1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
         m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
         );
    // This will make the text 3 times wider and half as high!
    Matrix3x2F scale = Matrix3x2F::Scale (
         3.0f, // Multiply x by 3
         0.5f, // Halve the y values

           D2D1::Point2F(  // Make the origin the middle of the text
                m_textMetrics.widthIncludingTrailingWhitespace / 2.0f,
                m_textMetrics.height / 2.0f)

```

);

//应用比例、平移和方向

m _ d2d context-> SetTransform(scale * translation * m _ orientationontransform2d);

W
wizardforcel 已提交
1786
![](img/image033.jpg)
W
wizardforcel 已提交
1787 1788 1789 1790 1791

图 24:缩放

标尺的原点是这样计算的,即文本从其中心点(字母 d)开始增大和缩小。我用蓝色标记了计算原点的代码。首先移动文本,然后应用比例会产生非常不同的结果,类似于旋转和平移:

W
wizardforcel 已提交
1792
```cpp
W
wizardforcel 已提交
1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
         m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
         );

         // This will make the text 3 times wider and half as high!
    Matrix3x2F scale = Matrix3x2F::Scale (
         3.0f, // Multiply x by 3
         0.5f, // Halve the y values
         D2D1::Point2F(  // Make the origin the middle of the text
              m_textMetrics.widthIncludingTrailingWhitespace / 2.0f,
              m_textMetrics.height / 2.0f)
         );

         m_d2dContext->SetTransform(translation * scale * m_orientationTransform2D);

```

首先使用`translation`矩阵将文本翻译到屏幕中心。应用`scale`矩阵时,文本的当前位置在 x 轴乘以 3.0f,在 y 轴减半(乘以 0.5f)。这导致文本完全离开屏幕。我已经在图 25 中将屏幕右侧之外的区域描绘成深蓝色,因此我们可以看到我们的文本去了哪里。

W
wizardforcel 已提交
1814
![](img/image034.png)
W
wizardforcel 已提交
1815 1816 1817 1818 1819

图 25:转换顺序 2

像旋转矩阵一样,比例矩阵需要指定原点。原点是在整个生长和收缩过程中保持不变的点。在前面的例子中,它是文本的中心,但是它可以是任何点,甚至是文本边界之外的点。例如,如果我们将原点更改为文本的右上角,我们可以看到它不再从中心开始增长和收缩,而是向左和向下。

W
wizardforcel 已提交
1820
```cpp
W
wizardforcel 已提交
1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839
    // Position the rendered text.
    Matrix3x2F translation = Matrix3x2F::Translation(
         m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
         m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
         );
         // This will make the text 3 times wider and half as high!
         Matrix3x2F scale = Matrix3x2F::Scale (
              3.0f, // Multiply X by 3
              0.5f, // Halve the y values
              D2D1::Point2F(  // Make the origin the top right corner
                    m_textMetrics.widthIncludingTrailingWhitespace,
                    0.0f)
              );

         // Apply the scale, then the transform, and then the orientation
         m_d2dContext->SetTransform(scale * translation * m_orientationTransform2D);

```

W
wizardforcel 已提交
1840
![](img/image035.jpg)
W
wizardforcel 已提交
1841 1842 1843 1844 1845

图 26:缩放

原点在图 26 中保持不变。文本沿 y 轴挤压,沿 x 轴拉伸。如果您在平移后应用缩放,那么平移也会被缩放。

W
wizardforcel 已提交
1846
| ![](img/note.png) | 注意:Direct2D 中还有其他变换可以用来产生各种效果。Matrix3x2F::Identity()是具有原始值或默认值的矩阵。它可以用来重置转换。偏斜矩阵也很有趣;它可以用来在二维数据上产生一些简单的透视变换。 |
W
wizardforcel 已提交
1847 1848 1849 1850 1851 1852 1853

### 翻译散点图

既然我们已经了解了如何变换我们正在绘制的对象,我们就可以修复散点图了。默认情况下,计算机将假设左上角是原点,y 轴向下向右增加。我们希望我们的原点在屏幕的左下角,我们希望 y 轴值在屏幕上向上增加。

为了修正散点图,我们可以应用两个矩阵,一个比例矩阵和一个平移。比例矩阵将所有 y 值乘以-1.0f,从而反转 y 轴。在翻转 y 轴时,我们已经将屏幕上方的所有数据向上翻转。平移矩阵可以用来增加屏幕的高度,所以我们再次查看数据。

W
wizardforcel 已提交
1854
```cpp
W
wizardforcel 已提交
1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895
    void GraphRenderer::Render()
    {
    m_d2dContext->BeginDraw();

    // Reset the transform matrix so our background does not pan
    m_d2dContext->SetTransform(m_orientationTransform2D);
    // Render the background
    m_gradientBackground->Render(m_d2dContext);

      // The scale matrix inverts the y-axis
      Matrix3x2F scale = Matrix3x2F::Scale(1.0f, -1.0f, D2D1::Point2F(0.0f, 0.0f));

      // The pan matrix still pans but it also adds the height of the screen
      Matrix3x2F panMatrix = Matrix3x2F::Translation(m_pan.X, m_pan.Y +
           m_d2dContext->GetSize().height);

      // Apply the scale first
      m_d2dContext->SetTransform(scale*panMatrix*m_orientationTransform2D);

```

//

//在此绘制对象

//

//渲染图形变量

m _ graph variable-> Render(m _ d2d context);

//忽略 D2DERR_RECREATE_TARGET 错误

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR _ RECREATE _ TARGET)DX::ThrowIfFailed(HR);

}

在运行应用程序之前,我们还需要调整`m_pan`成员变量的初始起始位置。这是在`GraphRenderer:CreateWindowSizeDependentResources`方法中指定的,x 值可以保持不变,但是 y 值必须改变,因为我们翻转了轴。

W
wizardforcel 已提交
1896
```cpp
W
wizardforcel 已提交
1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913
    void GraphRenderer::CreateWindowSizeDependentResources() {
    DirectXBase::CreateWindowSizeDependentResources();

    // Create window size resources for gradient background
    m_gradientBackground->CreateWindowSizeDependentResources(m_d2dContext);

    // Set the initial pan value so the lowest node is visible in the corner
    m_pan.X = -m_graphVariable->GetMinX();

      m_pan.Y = m_graphVariable->GetMinY();

```

}

运行应用程序后,您现在应该看到原点在左下角,y 轴向上增加,与普通图形完全相同。

W
wizardforcel 已提交
1914
![](img/image036.jpg)
W
wizardforcel 已提交
1915 1916 1917 1918 1919 1920 1921 1922 1923

图 27:翻转 Y 轴的散点图

## 第六章:无限的线和轴

在本节中,我们将介绍一种渲染无限线的方法。我将使用渲染在原点相交的图表轴的例子。散点图和折线图通常有原点的概念,即图表坐标中的点(0,0)。有时这个点非常重要,我们会在图表上显示为两条相交的线:一条代表 x 轴的 0 点,另一条代表 y 轴的 0 点。

向项目中添加两个文件,Axis . h 和 Axes.cpp。

W
wizardforcel 已提交
1924
```cpp
W
wizardforcel 已提交
1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949
    // Axes.h
    #pragmaonce
    #include "DirectXBase.h"
    // This class represents a graph's axes as two perpendicular lines
    class Axes {
         ID2D1SolidColorBrush* m_solidBrush; // Brush to draw with
         float m_lineThickness;    // Thickness in pixels
         float m_opacity;          // Opacity, 0.0f is invisible 1.0f is solid
         D2D1::ColorF m_color;     // The color of the lines

    public:
         // Public constructor
         Axes(D2D1::ColorF col, float thickness, float opacity);

         // Create the solid brush to draw with
         void CreateDeviceDependentResources
              (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);

         // The render method needs to know the panning and scaling
         void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context,
              float panX, float panY, float scaleX, float scaleY);
    };

```

W
wizardforcel 已提交
1950
```cpp
W
wizardforcel 已提交
1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
    // Axes.cpp
    #include "pch.h"
    #include "Axes.h"
    using namespace D2D1;
    using namespace DirectX;
    Axes::Axes(D2D1::ColorF col, float thickness = 3.0f, float opacity = 1.0f): m_color(0)
    {
    // Save these settings to member variables so
    // they can create the brush later:
    this->m_color = col;
    this->m_lineThickness = thickness;
    this->m_opacity = opacity;
    }

    void Axes::CreateDeviceDependentResources(
    Microsoft::WRL::ComPtr<ID2D1DeviceContext> context){
    // Create the solid color brush
    DX::ThrowIfFailed(context->CreateSolidColorBrush(
         m_color, D2D1::BrushProperties(m_opacity), &m_solidBrush));
    }

    void Axes::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context, float panX, float panY, float scaleX, float scaleY) {
    // Draw infinite vertical line with 0.0f as the x-coordinate
    context->DrawLine(
         D2D1::Point2F(
              0.0f,           // Horizontal axis
              (-context->GetSize().height - panY) / scaleY // Top of the screen
              ),
         D2D1::Point2F(
              0.0f,           // Horizontal axis
              (-panY) / scaleY     // Bottom of the screen
              ),
         m_solidBrush,
         m_lineThickness/scaleX);

    // Draw infinite horizontal line with 0.0f as the y-coordinate
    context->DrawLine(
         D2D1::Point2F(
              -(panX)/scaleX, // Left side of screen
              0.0f                      // Vertical axis
              ),
         D2D1::Point2F(
              (context->GetSize().width - panX)/scaleX, // Right side of screen
              0.0f                      // Vertical axis
              ),
         m_solidBrush,
         m_lineThickness/scaleY);
    }

```

构造函数将一些设置保存到成员变量中。`CreateDeviceDependentResources`方法创建一个画笔来绘制线条。

轴线理论上是无限长的。用户在图形平面上向左、向右、向上或向下平移多远并不重要,这些线的端点永远不应该可见。为了达到这个效果,我们画了两条线,一条用于 x 轴,另一条用于 y 轴。水平线(标记 y 轴的 0 点)与屏幕宽度相同,垂直线(标记 x 轴的 0 点)与屏幕高度相同。这样,无论图表平移多远,如果轴线可见,它将始终绘制在整个屏幕上。当实际上这些线很短时,这将显示为无限长。

线条的实际绘制是通过使用上下文的`DrawLine`方法来实现的。这个方法需要两点,一个笔刷和一个线条粗细。这条线是用来连接这两点的。

线条的粗细是静态的。我假设即使用户缩小了几千个单位,原点线仍然应该是可见的。同样,如果用户放大原点周围的微小点,它不应该缩放到缩放比例并占据整个屏幕。我通过用当前比例或缩放比例除以厚度,使我们的原点成为一个标准的像素厚度,而不管缩放系数如何。在代码中,我已经手动取消了平移和缩放来实现这一点。

W
wizardforcel 已提交
2010
| ![](img/note.png) | 注意:当绘制厚度不是 1.0f 的线时,线的中心(纵向)将位于指定的坐标。这与左上角绘制在指定坐标的位图不同。这意味着,如果绘制一条从(0,0)到(100,0)的厚度为 30 的线,该线的上边缘将绘制在(0,(-30/2))处,下边缘将绘制在(0,(30/2))处。 |
W
wizardforcel 已提交
2011 2012 2013

我还加了一个保证金。这是距离屏幕边缘的距离,以像素为单位。它可以用来产生十字准线原点,而不是无限长的轴。要实例化我们新的`Axes`类的一个对象,将头部添加到 GraphRenderer.h 文件中。我在下面的代码中包含了一个渐变背景和散点图,并且我用蓝色突出显示了处理原点的代码。

W
wizardforcel 已提交
2014
```cpp
W
wizardforcel 已提交
2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026
    //
    // Additional headers for graph objects here
    //
    #include "GradientBackground.h"
    #include "ScatterPlot.h"

      #include "Axes.h"

```

另外,向该文件添加一个 Axes 成员变量。

W
wizardforcel 已提交
2027
```cpp
W
wizardforcel 已提交
2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;
          // Background
          GradientBackground *m_gradientBackground;

            // Axes
            Axes* m_axes;

```

//数据

scatterplot * m _ scatterplot

在 GraphRenderer 的构造函数中创建图表对象。这些可以以任何顺序创建。我最后创造了斧头。

W
wizardforcel 已提交
2045
```cpp
W
wizardforcel 已提交
2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081
    GraphRenderer::GraphRenderer() {
    // Create the gradient background:
    D2D1_COLOR_F colors[] = {
          D2D1::ColorF(ColorF::PaleGoldenrod), D2D1::ColorF(ColorF::PaleTurquoise),
          D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f) };

    float stops[] = { 0.0f, 0.5f, 1.0f };
    m_gradientBackground = new GradientBackground(colors, stops, 3);

    // Create the scatter plot:
    const int count = 25;

    float* x = new float[count];
    float* y = new float[count];

    // Create random points to plot, these
    // would usually be read from some data source:
    for(int i = 0; i < count; i++) {
          x[i] = (float)((rand() % 2000) - 1000);
          y[i] = (float)((rand() % 1000) - 500);
          }

    m_scatterPlot = new ScatterPlot(x, y, 10.0f, D2D1::ColorF::Chocolate, NodeShape::Circle, count);

    delete[] x;
    delete[] y;

      // Create the Axes
      m_axes = new Axes(D2D1::ColorF::Black, 5.0f, 0.75f);

```

}

调用 Axes 的`CreateDeviceResources`方法,这样它就可以初始化它的纯色画笔。

W
wizardforcel 已提交
2082
```cpp
W
wizardforcel 已提交
2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

    // Create the brush for the scatter plot:
    m_scatterPlot->CreateDeviceDependentResources(m_d2dContext);

      // Create the brush for the Axes
      m_axes->CreateDeviceDependentResources(m_d2dContext);

```

}

最后,在 GraphRenderer 的`Render`方法中调用原点的渲染方法。

W
wizardforcel 已提交
2098
```cpp
W
wizardforcel 已提交
2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137
    void GraphRenderer::Render() {
    m_d2dContext->BeginDraw();
    // Clear to some color other than blank
    m_d2dContext->Clear(D2D1::ColorF(ColorF::CornflowerBlue));
    // Reset the transform matrix so our background does not pan
    m_d2dContext->SetTransform(m_orientationTransform2D);
    // Draw the background
    m_gradientBackground->Render(m_d2dContext);
    // The scale matrix inverts the y-axis
    Matrix3x2F scale = Matrix3x2F::Scale(1.0f, -1.0f, D2D1::Point2F(0.0f, 0.0f));
    // The pans added to the screen height so origin is at lower left
    Matrix3x2F panMatrix = Matrix3x2F::Translation
          (m_pan.X, m_pan.Y + m_d2dContext->GetSize().height);
    // Apply the scale and the pan
    m_d2dContext->SetTransform(scale*panMatrix*m_orientationTransform2D);

      // Draw the axes
      m_axes->Render(m_d2dContext, m_pan.X, m_pan.Y, 1.0f, -1.0f);

```

//

//在此绘制对象

//

m _ scatter plot-> render(m _ D2 dconte);

//忽略 D2DERR_RECREATE_TARGET 错误

hresult HR = m _ D2 dcocontext-> end raw();

if (hr!= D2DERR _ RECREATE _ TARGET)DX::ThrowIfFailed(HR);

}

运行应用程序并向右上方平移一点,您将看到原点和散点图。这是我们的图表世界坐标中的(0,0)点,如图 28 所示。

W
wizardforcel 已提交
2138
![](img/image037.jpg)
W
wizardforcel 已提交
2139 2140 2141 2142 2143 2144 2145 2146 2147

图 28:轴线

## 第 7 章:显示 FPS(每秒帧数)

每秒帧数(FPS)是 DirectX 刷新或渲染场景的速率。在本节中,我们将研究如何在图表的左上角显示 FPS。这将提供一个计算 FPS 以及渲染文本的例子。文本在图表应用程序中非常重要,可以用来呈现购物车的标题、节点位置、轴标签和许多其他东西。文本渲染非常慢,所以尽量减少文本量,使其少于 200 个左右的字符串。坐标轴的标签、图表标题、节点值和许多其他东西都可以用文本来呈现,但是如果您呈现成千上万个字符串,FPS 将很快下降。

我们将建立在上一章的图表上,该图表显示了轴线。当运行`GraphRenderer::Update`方法时,向`GraphRenderer`类添加两个成员变量浮点数,用于记录`BasicTimer`中的时间。此外,添加一个`IDWriteTextFormat`对象,它将保存我们的 FPS 输出格式,以及一个黑色画笔,它将用于绘制文本。

W
wizardforcel 已提交
2148
```cpp
W
wizardforcel 已提交
2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162
    private:
          // Global pan value for moving the chart with the mouse
          Windows::Foundation::Point m_pan;

            // Member variables for displaying FPS
            float m_timeDelta;      // Time since last update call
            float m_timeTotal;      // Total time of application
            Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat;
            Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_blackBrush;

```

`GraphRenderer::CreateDeviceIndependentResources`资源方法中创建文本格式实例。文本格式用于指定字体、大小和其他几种文本格式选项。

W
wizardforcel 已提交
2163
```cpp
W
wizardforcel 已提交
2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185
    void GraphRenderer::CreateDeviceIndependentResources() {
    DirectXBase::CreateDeviceIndependentResources();

      DX::ThrowIfFailed(
            m_dwriteFactory->CreateTextFormat(
                  L"Segoe UI",
                  nullptr,
                  DWRITE_FONT_WEIGHT_NORMAL,
                  DWRITE_FONT_STYLE_NORMAL,
                  DWRITE_FONT_STRETCH_NORMAL,
                  42.0f,
                  L"en-US",
                  &m_textFormat
                  )
            );

```

}

`CreateDeviceDependentResources`方法创建用于绘制文本的画笔。

W
wizardforcel 已提交
2186
```cpp
W
wizardforcel 已提交
2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

    // Call the create device resources for our graph variable
    m_graphVariable->CreateDeviceDependentResources(m_d2dContext);

    // Create the brush for the origin
    m_axes->CreateDeviceDependentResources(m_d2dContext);

      // Create the solid brush for the text
      DX::ThrowIfFailed(
           m_d2dContext->CreateSolidColorBrush(ColorF(ColorF::Black),&m_blackBrush));

```

}

向 GraphRenderer 的代码文件中添加一个`#include <string>`。这为我们提供了将浮点数附加到字符串中以显示每秒帧数的功能。

W
wizardforcel 已提交
2206
```cpp
W
wizardforcel 已提交
2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217
    // GraphRenderer.cpp
    #include "pch.h"

    #include <string>

```

#包括“GraphRenderer.h”

在 GraphRenderer 的代码文件中记录作为参数传递给`Update`方法的时间(`m_timeTotal``m_timeDelta`)。`m_timeTotal`是程序启动后经过的总毫秒数,`timeDelta`是最后一次调用`Update`后经过的时间。

W
wizardforcel 已提交
2218
```cpp
W
wizardforcel 已提交
2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230
    void GraphRenderer::Update(float timeTotal, float timeDelta) {

            // Record the times for displaying:
            m_timeDelta = timeDelta;
            m_timeTotal = timeTotal;

```

}

`GraphRenderer::Render`方法中,通过将时间附加到标签(总时间和 FPS)来创建字符串。m_timeDelta ( `1.0f/m_timeDelta`)的倒数是上次更新的速度。我已经将这个值四舍五入为整数。在绘制完所有图形对象后渲染我们的新 FPS 字符串是一个好主意,这样它就不会被遮挡。

W
wizardforcel 已提交
2231
```cpp
W
wizardforcel 已提交
2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254
    //
    // Draw objects here
    //
    // Render the graph variable
    m_graphVariable->Render(m_d2dContext);

      // Reset the transform matrix so the time and FPS does not pan or zoom
      m_d2dContext->SetTransform(m_orientationTransform2D);
      // Set up the string to print:
      std::wstring s = std::wstring(
           L"Total Time: ") + std::to_wstring(m_timeTotal) +
           std::wstring(L" FPS: ") + std::to_wstring(
                (int)(0.5f+1.0f/m_timeDelta)); // FPS rounded to nearest int
      // Render the string in the top left corner
      m_d2dContext->DrawText(s.c_str(), s.length(), m_textFormat.Get(),
           D2D1::RectF(0, 0, 600, 32), m_blackBrush.Get());

```

//忽略 D2DERR_RECREATE_TARGET 错误

运行应用程序时,您会注意到计时器仅在平移图形时更新。这对于在 WinRT 设备上省电是一件非常好的事情,但是为了测试我们的图表渲染的性能,我们希望将应用程序切换到实时。要切换到实时(重复更新),打开 DirectXPage.cpp 文件,注释掉导致程序基于`m_renderNeeded`成员变量更新的“if”条件。当`CompositionTarget::Rendering`事件触发时,调用`DirectXPage``OnRendering`方法。`CompositionTarget`是 XAML 控制被渲染到的表面。

W
wizardforcel 已提交
2255
```cpp
W
wizardforcel 已提交
2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279
    void DirectXPage::OnRendering(Object^ sender, Object^ args) {

            // if (m_renderNeeded)

```

{

m _ timer-> Update();

m _ 渲染器->更新(m_timer->总计,m_timer->增量);

m _ renderer-> Render();

m _ renderer-> Present();

m _ renderNeeded = false

}

}

现在,当您运行应用程序时,它应该会持续更新。FPS 每秒更新多次,有点难读。我们可以通过在 GraphRenderer 的更新中添加一个静态计数器和“if”条件来降低速度并只每 16 帧更新一次计数器。

W
wizardforcel 已提交
2280
```cpp
W
wizardforcel 已提交
2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298
    void GraphRenderer::Update(float timeTotal, float timeDelta) {

      static int fpsCounter = -1;  // Start at -1 so frame 0 updates timers
      fpsCounter++;
      if((fpsCounter & 15) == 0) { // Update every 16 frames

```

//记录在渲染方法中显示的时间:

m _ 时间差=时间差;

m _ timeTotal = timeTotal

}

}

W
wizardforcel 已提交
2299
| ![](img/tip.png) | 提示:尽可能使用布尔运算和移位来代替整数除法。CPU 需要非常快速地运行更新方法。最好在更新和渲染方法中最小化除法、模数、平方根、三角学和所有其他复杂函数。其中一些复杂函数的执行速度比简单的布尔指令慢几百倍。优化编译器很可能足够聪明,能够意识到“x%16”和“x&15”是一样的,但是每个编译器都是不同的,当有这样一个简单的优化时,最好不要信任编译器。 |
W
wizardforcel 已提交
2300 2301 2302 2303 2304

## 第八章:折线图

我们在原点稍微看了一下渲染线,但是现在我们将检查渲染许多线,我们将使用折线图作为示例。折线图将数据显示为一系列相连的线条。这些线连接连续的节点或点,并以散点图没有的方式描绘数据的连续性或时间顺序。通常,x 轴被视为时间,从左边的早期点到右边的后期点,如图 29 所示。

W
wizardforcel 已提交
2305
![](img/image038.jpg)
W
wizardforcel 已提交
2306 2307 2308 2309 2310

图 29:折线图

图 29 是一个简单的折线图,使用年份作为 x 轴。它从 1978 年发展到 2013 年,每年都会绘制一个得分变量,并与前一点连成一条直线。重要的区别在于,在渲染线之前,数据必须按 x 轴排序;否则,可能会发生类似图 30 的情况。

W
wizardforcel 已提交
2311
![](img/image039.png)
W
wizardforcel 已提交
2312 2313 2314 2315 2316

图 30:带有无序 X 轴的折线图

图 30 当然是一个折线图,但是这条线是相对于 x 轴值前后绘制的,因为节点是以随机顺序创建的。要正确呈现 x 轴无序数据的折线图,必须对数据进行排序。我在下面的代码中使用了 STL(标准模板库)稳定排序。

W
wizardforcel 已提交
2317
| ![](img/tip.png) | 提示:始终在更新和渲染方法之外对数据进行排序。如果在这些关键方法之外对数据进行排序,排序算法的速度在很大程度上可以忽略不计。现代硬件可以在几秒钟内轻松地对 1,000,000 个节点进行排序,但是我们的 Update 或 Render 方法无法承受几秒钟的时间。 |
W
wizardforcel 已提交
2318 2319 2320

向项目中添加两个文件,LineChart.h 和 LineChart.cpp。

W
wizardforcel 已提交
2321
```cpp
W
wizardforcel 已提交
2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349
    // LineChart.h
    #pragmaonce
    #include "DirectXBase.h"
    #include "GraphVariable.h"

    // This class represents a variable rendered as a line
    class LineChart: public GraphVariable {
         ID2D1SolidColorBrush* m_solidBrush; // Brush to draw with
         float m_lineThickness;    // Thickness in pixels
         D2D1::ColorF m_color;     // The color of the line

         // Method to stable-sort the data by the x-axis
         void SortData();

    public:
         // Public constructor
         LineChart(float* x, float *y, int count, D2D1::ColorF col, float thickness);

         // Create the solid brush to draw with
         virtual void CreateDeviceDependentResources
              (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;

         // The main render method of the line class
         virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;
    };

```

W
wizardforcel 已提交
2350
```cpp
W
wizardforcel 已提交
2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402
    // LineChart.cpp
    #include "pch.h"
    #include "LineChart.h"
    #include <vector>
    #include <algorithm>
    using namespace D2D1;
    using namespace DirectX;
    // Comparison method used by the stable-sort below
    bool ComparePoints(D2D1_POINT_2F a, D2D1_POINT_2F b) {
    return a.x < b.x; // Sort on x values
    }

    LineChart::LineChart(float* x, float *y, int count,
         D2D1::ColorF col, float thickness = 3.0f): m_color(0), GraphVariable(x, y, count) {
    // Save these settings to member variables to
    // create the brush later:
    this->m_color = col;
    this->m_lineThickness = thickness;

    // Sort the data by the x-axis
    SortData();
    }

    void LineChart::SortData()
    {
    // Sort the data by the x-axis
    std::vector<D2D1_POINT_2F> sortedNodes(m_points, m_points + m_nodeCount);

    // Note the use of the stable sort, using an unstable sort
    // like Quicksort will produce unexpected results!
    std::stable_sort(sortedNodes.begin(), sortedNodes.end(), ComparePoints);

    // Copy the sorted points back to the m_pointsArray
    int counter = 0;
    for(std::vector<D2D1_POINT_2F>::iterator nodeIterator = sortedNodes.begin();
         nodeIterator != sortedNodes.end(); nodeIterator++, counter++) {
         m_points[counter].x = (*nodeIterator).x;
         m_points[counter].y = (*nodeIterator).y;
         }
    }

    void LineChart::CreateDeviceDependentResources(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // Create the solid color brush
    DX::ThrowIfFailed(context->CreateSolidColorBrush(
         m_color, D2D1::BrushProperties(1.0f), &m_solidBrush));
    }

```

为了创建和渲染折线图,我们可以替换用来创建散点图的代码。在折线图顶部渲染散点图节点也很常见,所以我也将包含散点图对象。散点图节点将用于强调折线图。将标题添加到 GraphRenderer.h 文件中。

W
wizardforcel 已提交
2403
```cpp
W
wizardforcel 已提交
2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417
    //
    // Additional headers for graph objects here
    //
    #include "GradientBackground.h"
    #include "ScatterPlot.h"

      #include "LineChart.h"

```

#包括“轴. h”

向 GraphRenderer 类添加一个名为 m_lineChart 的成员变量。

W
wizardforcel 已提交
2418
```cpp
W
wizardforcel 已提交
2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440
         // Member variables for displaying FPS
         float m_timeDelta;   // Time since last update call
         float m_timeTotal;   // Total time of application
         Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat;
         Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_blackBrush;

         // Solid background
         GradientBackground* m_gradientBackground;

         // Plottable data
         GraphVariable* m_graphVariable;

           GraphVariable* m_lineChart;

```

//轴

* m _ axes

在 GraphRenderer 构造函数中调用 LineChart 对象的构造函数。我正在用相同的数据和颜色绘制线图和散点图。

W
wizardforcel 已提交
2441
```cpp
W
wizardforcel 已提交
2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485
    GraphRenderer::GraphRenderer()
    {
    // Create the gradient background:
    D2D1_COLOR_F colors[] = {
         D2D1::ColorF(ColorF::PaleGoldenrod),
         D2D1::ColorF(ColorF::PaleTurquoise),
         D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f)
         };
    float stops[] = { 0.0f, 0.5f, 1.0f };
    m_gradientBackground = new GradientBackground(colors, stops, 3);

    // Create the scatter plot:
    const int count = 25;     // Create 25 nodes
    float* x = new float[count];
    float* y = new float[count];

    // Create random points to plot, these
    // would usually be read from some data source:
    for(int i = 0; i < count; i++) {
         x[i] = (float)(rand() % 2000) - 1000;
         y[i] = (float)(rand() % 1000) - 500;
         }

    m_graphVariable = new ScatterPlot(x, y, 10.0f,
         D2D1::ColorF::Chocolate,
         NodeShape::Circle, count);

      // Create the line chart
      m_lineChart = new LineChart(x, y, count, D2D1::ColorF::Chocolate, 5.0f);

```

删除[]x;

删除[]y;

//创建轴线

m_axes =新 Axes(D2D1::ColorF::Black,5.0f,0.75 f);

}

调用折线图的`CreateDeviceDependentResources`方法创建画线时使用的画笔。

W
wizardforcel 已提交
2486
```cpp
W
wizardforcel 已提交
2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

    // Call the create device resources for our graph variable
    m_graphVariable->CreateDeviceDependentResources(m_d2dContext);

      // Create device resources for the line chart
      m_lineChart->CreateDeviceDependentResources(m_d2dContext);

```

//为轴创建画笔

m _ axes-> create device dependents resources(m _ D2 dcontet):

//为文本创建实心画笔

DX::throwifffailed(

m _ D2 dconte-> createsolidcolorbrush(color f::black),& m _ black rush);

}

最后,在绘制散点图节点之前,调用`GraphRenderer::Render`方法中的`m_lineChart::Render`方法。

W
wizardforcel 已提交
2512
```cpp
W
wizardforcel 已提交
2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525
    //
    // Draw objects here
    //
    // Render the graph variable(s)

      m_lineChart->Render(m_d2dContext);

```

m _ graph variable-> Render(m _ d2d context);

运行应用程序时,您应该会看到类似图 31 的内容。

W
wizardforcel 已提交
2526
![](img/image040.png)
W
wizardforcel 已提交
2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537

图 31:折线图

x 轴上的数据可能已经排序了(因为它可能是按时间顺序收集的),在这种情况下,它根本不需要排序。`LineChart`类应该是相当不言自明的。我们已经根据传递给构造函数的数据和 render 方法创建了一个点列表,并将这些点与一组线连接起来。

## 第 9 章:在多个 XAML 页面之间导航

到目前为止,我们的图表只有一个 XAML 页面,由 Direct2D 绘制。通常需要包含多个 XAML 页面。我将使用一个设置页面的例子,用户可以在其中为折线图选择一些选项。这将是一个完全独立于主 XAML 页面的页面,每个图表对象都可以有自己的设置 XAML 页面,可以用同样的方式制作。

在一个简单的 XAML 项目中,程序员可以使用简单的`Frame->Navigate(destination)`语法。这在带有 Direct2D 的应用程序中是不可能的,因为 Frame 成员变量将为空(框架根本不会设置它)。程序员可以使用以下语法手动导航,而不是使用框架。

W
wizardforcel 已提交
2538
```cpp
W
wizardforcel 已提交
2539 2540 2541 2542 2543 2544 2545 2546 2547
    Window::Current->Content = ref new SomeOtherXAMLPage();// Reference another page
    Window::Current->Activate();

```

第一行将当前窗口的内容设置为另一个 XAML 页面,第二行激活它。激活一个 XAML 页面意味着把它带到前面,给它焦点,让它接收输入事件。

这个新的 XAML 子页面很可能需要返回到原始页面。例如,如果它正在更改图表中的某些设置,我们将需要在用户更改设置后再次显示图表。我们可以使用前面的代码创建原始页面的另一个副本(当用户关闭子页面时执行),但是这将是浪费的,并且可能导致崩溃(因为我们将重复地重新创建主 DirectX 页面,并且永远不会关闭它们)。我们希望恢复原始页面并赋予其焦点,而不是创建多个副本。有很多方法可以做到这一点。其中一个最简单的方法是给新页面的构造函数一个父 XAML 页面的句柄。

W
wizardforcel 已提交
2548
```cpp
W
wizardforcel 已提交
2549 2550 2551 2552 2553 2554
    Window::Current->Content=ref new SomeOtherXAMLPage(this);//Reference parent page

```

第二页将传递到其构造函数中的“this”指针保存为`m_myParent`,当第二页关闭时,它不会重新创建父级,而是从这个父级句柄恢复它。

W
wizardforcel 已提交
2555
```cpp
W
wizardforcel 已提交
2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566
    Window::Current->Content = m_myParent; // Give the parent the focus
    Window::Current->Activate();

```

传递给子页的“this”指针可以具有尽可能通用的类型,以允许几乎任何窗口打开任何子窗口。`Windows::UI::Xaml::UIElement^`是个不错的选择。

我们现在将通过创建一个允许用户编辑折线图设置的 XAML 页面来检查添加第二个页面,然后将控制返回到主`DirectXPage`类来绘制更新的图表。要向您的解决方案添加新的空白 XAML 页面,请右键单击该解决方案并单击**添加**,然后单击上下文菜单上的**新项目**

点击左侧面板的 **Windows Store** ,然后点击中间面板的**空白页**,输入新页面的名称。在这个例子中,我使用了编辑线,如图 32 所示。

W
wizardforcel 已提交
2567
![](img/image041.jpg)
W
wizardforcel 已提交
2568 2569 2570 2571 2572

图 32:添加空白页

一旦 Visual Studio 创建了新的 XAML 页面,打开标题(本例中为`EditLine.xaml.h`)并向构造函数添加一个参数,指定它需要一个`Windows::UI::Xaml::UIElement^`。另外,添加一个名为`m_myParent`的相同类型的成员变量。

W
wizardforcel 已提交
2573
```cpp
W
wizardforcel 已提交
2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608
    //
    // EditLine.xaml.h
    // Declaration of the EditLine class
    //
    #pragma once
    #include "EditLine.g.h"
    namespace GraphPlotting
    {
         /// <summary>
         /// Empty page that can be used on its own or navigated to within a Frame.
         /// </summary>
         [Windows::Foundation::Metadata::WebHostHidden]
         public ref class EditLine sealed{
         public:

                EditLine(Windows::UI::Xaml::UIElement^ myParent);

```

受保护:

虚拟空间

(windows::ui::xaml::navigation::navigationeventargs^ e)超越;

私人:

windows::ui::xaml::uielement ^ m _ my parent;

};

}

在解决方案资源管理器中双击新的 xaml 页面(本例中为 EditLine.xaml 文件),Visual Studio 将打开 XAML 设计器的空白页。在左上角添加一个按钮。这将是我们的后退按钮,将允许用户导航回主 DirectX 页面,见图 33。

W
wizardforcel 已提交
2609
![](img/image042.jpg)
W
wizardforcel 已提交
2610 2611 2612 2613 2614

图 33:添加按钮

在 XAML 代码部分,您可以设置新按钮的名称、内容和字体大小。

W
wizardforcel 已提交
2615
```cpp
W
wizardforcel 已提交
2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636
    <Page
        x:Class="GraphPlotting.EditLine"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:GraphPlotting"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">

        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <Button Name="btnBack" Content="Back" FontSize="32"
              HorizontalAlignment="Left" Height="82" Margin="10,10,0,0"
              VerticalAlignment="Top" Width="157"/>

        </Grid>
    </Page>

```

双击设计器中的新建按钮,Visual Studio 将编写`OnClicked`事件并带我们进入代码。我们希望当用户单击后退按钮时,父级被激活。

W
wizardforcel 已提交
2637
```cpp
W
wizardforcel 已提交
2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649
    void GraphPlotting::EditLine::btnBack_Click(Platform::Object^ sender,
              Windows::UI::Xaml::RoutedEventArgs^ e){

           Window::Current->Content = m_myParent; // Give the parent the focus
           Window::Current->Activate();

```

}

更改页面的构造函数,使其采用新的父 UIElement 作为参数,这是我们在标题中指定的。将句柄保存为类的`m_myParent`成员变量。

W
wizardforcel 已提交
2650
```cpp
W
wizardforcel 已提交
2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661
    EditLine::EditLine(Windows::UI::Xaml::UIElement^ myParent){
         InitializeComponent();

           this->m_myParent = myParent;

```

}

现在我们已经设置了打开和关闭子页面,我们可以设计用户请求编辑折线图的方法(本例中是应用程序栏中的一个按钮)。在解决方案资源管理器中双击该文件,将其打开。这将把你带到 XAML 设计师的主 DirectXPage。将带有按钮的应用程序栏添加到 DirectXPage XAML 文件中。

W
wizardforcel 已提交
2662
```cpp
W
wizardforcel 已提交
2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683
    <Page
        x:Class="GraphPlotting.DirectXPage"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:GraphPlotting"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <SwapChainBackgroundPanel x:Name="SwapChainPanel" PointerMoved="OnPointerMoved"
              PointerReleased="OnPointerReleased">
        </SwapChainBackgroundPanel>

          <Page.BottomAppBar>
              <AppBar Padding="10, 0, 10, 0">
                  <Button Name="btnEditLine" Content="Edit Line" FontSize="24"
                      HorizontalAlignment="Center" Width="240"/>
              </AppBar>
          </Page.BottomAppBar>

```

W
wizardforcel 已提交
2684
| ![](img/tip.png) | 提示:在 XAML 代码窗口中使用 AppBar 和其他几种对象类型比在主设计器窗口中容易得多,因为这些控件在设计器中通常是不可见的。要让设计器窗口显示一个不可见的控件,可以在 XAML 代码中选择该控件的开始标记。 |
W
wizardforcel 已提交
2685 2686 2687

双击设计器(btnEditLine)中的新建按钮,Visual Studio 将添加`OnClicked`事件并带我们进入代码。首先要做的是在这个文件的顶部#包含对 EditLine.xaml.h 头的引用。

W
wizardforcel 已提交
2688
```cpp
W
wizardforcel 已提交
2689 2690 2691 2692 2693 2694 2695 2696 2697 2698
    // DirectXPage.xaml.cpp
    #include "pch.h"
    #include "DirectXPage.xaml.h"

      #include "EditLine.xaml.h"

```

接下来,向下滚动到按钮点击事件,这样我们就可以添加代码来创建和激活编辑行窗口。

W
wizardforcel 已提交
2699
```cpp
W
wizardforcel 已提交
2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711
    void GraphPlotting::DirectXPage::btnEditLine_Click(Platform::Object^ sender,
              Windows::UI::Xaml::RoutedEventArgs^ e){
         Window::Current->Content = ref new EditLine(this);
         Window::Current->Activate();
         }

```

运行应用程序后,您应该能够通过右键单击(或在触摸屏设备上用指针滑动)调出应用程序栏。单击按钮后,您将看到“编辑线条”页面,您可以从该页面单击“上一步”按钮返回图形渲染器。

作为使用新的 XAML 页面更改图形对象值的示例,我们将允许用户设置折线图的厚度。为 LineChart.h 文件中的线条粗细向`LineChart`类添加一个公共的 getter 和 setter。

W
wizardforcel 已提交
2712
```cpp
W
wizardforcel 已提交
2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735
    // Public constructor
         LineChart(float* x, float *y, int count, D2D1::ColorF col, float thickness);

         // Create the solid brush to draw with
         virtual void CreateDeviceDependentResources
              (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;

         // The main render method of the line class
         virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;

           // Getters and setters for line thickness
           void SetLineThickness(float newThickness) {
                m_lineThickness = newThickness;
                }

           float GetLineThickness() {
                return m_lineThickness;
                }

```

向 GraphRenderer.h 文件添加一个公共内部 getter,该文件返回类的折线图成员变量。

W
wizardforcel 已提交
2736
```cpp
W
wizardforcel 已提交
2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761
    public:
         // Public constructor
         GraphRenderer();

         // DirectXBase methods.
         virtual void CreateDeviceIndependentResources() override;
         virtual void CreateDeviceResources() override;
         virtual void CreateWindowSizeDependentResources() override;
         virtual void Render() override;

         // Capture the pointer movements so the user can pan the chart
         void PointerMoved(Windows::Foundation::Point point);

         // Method for updating time-dependent objects.
         void Update(float timeTotal, float timeDelta);

      internal:
           LineChart* GetLine() {
                return (LineChart*) m_lineChart;
                }

```

在 EditLine.xaml.h 文件的折线图标题中添加#include。向构造函数添加一个指向折线图的新参数,将构造函数标记为内部,然后添加一个新的折线图指针成员变量来存储该参数。

W
wizardforcel 已提交
2762
```cpp
W
wizardforcel 已提交
2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816
    //
    // EditLine.xaml.h
    // Declaration of the EditLine class
    //

    #pragmaonce
    #include "EditLine.g.h"

    #include "LineChart.h"

```

命名空间图表绘制[

///

 <summary>///可以单独使用或在框架内导航到的空白页面。

///

[Windows::Foundation::Metadata::WebHostHidden]

公共引用类编辑行已密封

{

公共:

内部:

editline(windows::ui::xaml::uielement ^ my parent,line chart * my line);

受保护:

虚拟空间

(windows::ui::xaml::navigation::navigationeventargs^ e)超越;

私人:

windows::ui::xaml::uielement ^ m _ my parent;

折线图* m_myLine:

作废 btnback_click(platform::object^寄件人,

windows::ui::xaml::route deventrgs ^ e;

};

}

添加对 m_renderer::GetLine 方法的调用,并将其作为一个参数传递给 DirectXPage.xaml.cpp 文件的:btnEditLine _ Click 事件。

W
wizardforcel 已提交
2817
```cpp
W
wizardforcel 已提交
2818 2819 2820 2821 2822 2823 2824 2825 2826
    void GraphPlotting::DirectXPage::btnEditLine_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) {
         Window::Current->Content = ref new EditLine(this, m_renderer->GetLine());
         Window::Current->Activate();
         }

```

向 EditLine.xaml 文件中添加滑块控件。这将用于设置线条的粗细。我称之为`sldLineThickness`。我还在下面的代码中向网格添加了一个文本块,用于为用户标记滑块。EditLine.xaml 文件的网格的完整代码如下:

W
wizardforcel 已提交
2827
```cpp
W
wizardforcel 已提交
2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <Button Name="btnBack" Content="Back" FontSize="32" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Click="btnBack_Click"/>

          <Slider Name="sldLineThickness" HorizontalAlignment="Left" Margin="420,302,0,0"
                VerticalAlignment="Top" Width="573" Height="47" Minimum="1" Maximum="12"/>

          <TextBlock FontSize="32" HorizontalAlignment="Left" Margin="185,302,0,0"
                      TextWrapping="Wrap" Text="Line Thickness"
                      VerticalAlignment="Top"/>

```

修改 EditLine.xaml.cpp 文件中的构造函数代码,取一个`LineChart`参数,保存到`m_` `myLine`成员变量中。我还将滑块的初始值设置为与`myLine`参数的当前厚度相同。

W
wizardforcel 已提交
2842
```cpp
W
wizardforcel 已提交
2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856
    EditLine::EditLine(Windows::UI::Xaml::UIElement^ myParent, LineChart* myLine)
    {
         InitializeComponent();
         this->m_myParent = myParent;

           this->m_myLine = myLine;
           this->sldLineThickness->Value = (int)myLine->GetLineThickness();

```

}

最后,在 EditLine.xaml.cpp 文件中的`btnBack_Click`事件方法中,就在将控制权交还给父窗口之前,我们可以调用折线图的设置线条粗细方法,将线条粗细设置为`sldLineThickness->Value`

W
wizardforcel 已提交
2857
```cpp
W
wizardforcel 已提交
2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876
    void GraphPlotting::EditLine::btnBack_Click(Platform::Object^ sender,
              Windows::UI::Xaml::RoutedEventArgs^ e)    {

           m_myLine->SetLineThickness((float)sldLineThickness->Value);

```

window::Current-> Content = m _ myParent;//给家长焦点

窗口::当前->激活();

}

运行应用程序后,您应该能够通过移动滑块来设置折线图的厚度。本示例仅允许更改线条的粗细,但可以向页面添加其他控件,以允许编辑线条的其他方面。可以添加新的 XAML 页面来改变图表的任何方面。

## 第十章:打印 Direct2D

在本章中,我们将介绍如何将打印添加到标准的 Direct2D (XAML)应用程序中,用户可以通过选择设备图标从魅力栏激活该应用程序。我已经描述了如何将打印添加到一个新的 Direct2D (XAML)项目中,而不是将功能添加到我们现有的应用程序中,因为打印 Direct2D 适用于图形和图表数据之外。

W
wizardforcel 已提交
2877
| ![](img/note.png) | 注意:我在这里展示的大部分代码要么是基于微软的 Direct2Dapp 打印示例,要么是直接取自这个示例。微软示例被设计为独立的应用程序。在这个演练中,我们将研究如何向 Direct2D (XAML)应用程序添加打印,而不是编写一个全新的应用程序。如果您正从头开始一个新项目,那么在名为“Direct2Dapp 打印示例”的 Microsoft 示例之后,对应用程序的结构进行更紧密的建模可能是有益的请参阅附录 A,获取微软使用其示例代码的许可。 |
W
wizardforcel 已提交
2878 2879 2880 2881 2882 2883 2884

创建新的 Direct2D XAML 项目

创建新的 Direct2D (XAML)应用程序。即使您正在向现有项目添加打印功能,建议您使用新的 Direct2D (XAML)应用程序逐步完成此过程,以便熟悉 Direct2D 打印应用程序的结构。

打开文件,删除写着“你好,xaml”的文字本示例不会打印出 XAML 控件的内容。

W
wizardforcel 已提交
2885
| ![](img/note.png) | 注意:在本例中,XAML 控件与我们将要打印的 Direct2D 图形无关。如果您希望打印 XAML 控件,微软有一个单独的示例应用程序,名为“快速启动:从您的应用程序打印(使用 C#/VB/C++和 XAML 的 Windows Store 应用程序)。” |
W
wizardforcel 已提交
2886 2887 2888 2889 2890

**让你的应用程序多线程化**

Direct2D 打印发生在具有另一个设备上下文的单独线程上。还有一个单独的线程用于呈现打印预览。屏幕、预览和打印可以同时进行,而不会相互干扰。主 Direct2D 工厂必须是多线程创建的。创建多线程应用程序的选项是在 DirectXBase.cpp 文件的`CreateDeviceIndependentResources`方法中的 DirectXBase 类中设置的。

W
wizardforcel 已提交
2891
```cpp
W
wizardforcel 已提交
2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916
    // These are the resources required independent of the device.
    void DirectXBase::CreateDeviceIndependentResources(){
         D2D1_FACTORY_OPTIONS options;
         ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
    #if defined(_DEBUG)
          // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
         options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
    #endif
         DX::ThrowIfFailed(
              D2D1CreateFactory(

                      D2D1_FACTORY_TYPE_MULTI_THREADED,

```

__uuidof(ID2D1Factory1),&options,&m_d2dFactory

)

);

**添加打印管理器**

在 DirectXPage.cpp 文件中的 DirectXPage 类顶部添加一个“使用命名空间”指令,以使用 Printing 命名空间。必须对此类进行其他几项更改。有关 DirectXPage 类的完整列表,请参见附录 B。

W
wizardforcel 已提交
2917
```cpp
W
wizardforcel 已提交
2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;

      using namespace Windows::Graphics::Printing;

```

DirectXPage::DirectXPage():

将打印管理器成员变量添加到 DirectXPage.h 文件中的 DirectXPage 类中。

W
wizardforcel 已提交
2929
```cpp
W
wizardforcel 已提交
2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941
    BasicTimer^ m_timer;     

           Windows::Graphics::Printing::PrintManager^ m_printManager;

```

};

**初始化打印管理器**

在 DirectXPage.cpp 文件中的 DirectXPage 类的构造函数中初始化打印管理器。这可能发生在另一个代码之后。我把它直接放在了 m_timer 的初始化下面。要初始化它,我们需要做两件事:获取当前程序的打印管理器,并注册一个事件处理程序,这样我们就知道用户何时请求打印什么。

W
wizardforcel 已提交
2942
```cpp
W
wizardforcel 已提交
2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959
    m_timer = ref new BasicTimer();

      // Grab the print manager for the current view
      m_printManager = Windows::Graphics::Printing::PrintManager::GetForCurrentView();
      // Add an event handler to capture when the user requests a print task
      m_printManager->PrintTaskRequested +=
           ref new TypedEventHandler<PrintManager^, PrintTaskRequestedEventArgs^>(this,
           &DirectXPage::SetPrintTask);

```

在这段代码中,我们指定了一个名为 SetPrintTask 的处理程序方法,打印管理器将使用它来执行新的打印任务。

**创建设置打印任务方法**

这个方法非常重要,因为它决定了用户何时请求从魅力栏打印东西。当用户从魅力栏中选择设备时,它将被调用,根据事件的内容,它将使应用程序能够打印,或者魅力栏将显示“该应用程序目前无法发送到其他设备。”将 SetPrintTask 原型添加到 DirectXPage.h 标头中。

W
wizardforcel 已提交
2960
```cpp
W
wizardforcel 已提交
2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972
         BasicTimer^ m_timer;     
         Windows::Graphics::Printing::PrintManager^ m_printManager;

      internal:
           // Print task requested event handler method
           void SetPrintTask(_In_ Windows::Graphics::Printing::PrintManager^ sender,
                _In_ Windows::Graphics::Printing::PrintTaskRequestedEventArgs^ args);

```

};

W
wizardforcel 已提交
2973
| ![](img/note.png) | 注意:SetPrintTask(以及本示例中的许多其他方法)的声明必须标记为内部,因为它在其原型中包含本机数据类型,并且该类标记为 ref,这意味着它是一个. NET Framework Reference 类。引用类不能包含基于本机类型的公共方法或数据。 |
W
wizardforcel 已提交
2974 2975 2976

下面的列表是 SetPrintTask 方法的方法体。我把它放在 LoadInternalState 方法之后,作为 DirectXPage.cpp 文件中的最后一个方法。

W
wizardforcel 已提交
2977
```cpp
W
wizardforcel 已提交
2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005
    void DirectXPage::LoadInternalState(IPropertySet^ state) {
          m_renderer->LoadInternalState(state);
    }

      void DirectXPage::SetPrintTask(_In_ PrintManager^ sender,
           _In_ PrintTaskRequestedEventArgs^ args){
      // Create a new source requested handler
      PrintTaskSourceRequestedHandler^ sourceRequestedHandler = ref new PrintTaskSourceRequestedHandler(
           [this](PrintTaskSourceRequestedArgs^ args)-> void {
                Microsoft::WRL::ComPtr<CDocumentSource> documentSource;
                DX::ThrowIfFailed (
                      Microsoft::WRL::MakeAndInitialize<CDocumentSource>(
                           &documentSource,reinterpret_cast<IUnknown*>(m_renderer)
                           )
                      );

                      // Cast the document to an object
                      IPrintDocumentSource^ objSource(               reinterpret_cast<IPrintDocumentSource^>(documentSource.Get())
                      );
                args->SetSource(objSource);
                });
      // Create the print task
      PrintTask^ printTask = args->Request->CreatePrintTask(L"Direct 2D Printing Example",
                sourceRequestedHandler);
      }

```

W
wizardforcel 已提交
3006
| ![](img/note.png) | 注意:这个方法对 lambda 表达式使用新的(C++11)语法,因为新的事件处理程序需要访问外部 DirectXPage 类的数据成员。这些表达式在功能上类似于 Java 中的匿名内部类型。代码来自微软。本代码的许可和复用请参考[附录 A](4.html#sigil_toc_id_46) 。 |
W
wizardforcel 已提交
3007 3008 3009 3010 3011 3012 3013

DirectX (XAML)模板有几个对`SimpleTextRenderer`中方法的引用,当我们重写类时,这些引用将被删除。它还引用了微软的`CDocumentClass`,我已经把它作为[附录 C](6.html#sigil_toc_id_50) 包含在内了。它创建并注册一个`PrintTaskSourceRequestedHandler`事件处理程序。当打印任务请求打印文档时,会触发此事件。必须调用它才能完成`PrintTask`对象的初始化。如前所述,关于没有这些方法的最终修改的 DirectXPage 类的完整列表,请参考[附录 B](5.html#sigil_toc_id_49)

**添加文档源类**

接下来,我们将创建前面代码中引用的文档源类。在 DirectXPage.cpp 文件的顶部添加#include,以包含名为 DocSource.h 的新类的标头。

W
wizardforcel 已提交
3014
```cpp
W
wizardforcel 已提交
3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027
    #include "pch.h"
    #include "DirectXPage.xaml.h"

      #include "DocSource.h"

```

为名为 DocSource.h 和 DocSource.cpp 的新文档源类添加一个标题和一个代码文件。文档源类的完整代码列表(来自微软的打印示例)包含在[附录 C](6.html#sigil_toc_id_50) 中。

**拆分简单文本渲染器**

下一步是允许多次创建 SimpleTextRenderer 类来创建多个上下文。多个设备上下文将并行运行。渲染方法本身也必须更新,以考虑输出的格式。电脑显示器的尺寸通常不同于标准的 A4 纸。该类的渲染方法现在必须考虑这些维度的差异,并根据需要更改图像的布局。没有标准的方式来格式化显示器的输出,使其完全适合一张物理纸。一些建议是,打印时不要对背景进行着色,因为打印纯色背景非常慢,而且会浪费大量墨水。

W
wizardforcel 已提交
3028
| ![](img/tip.png) | 提示:打印页面的标准方向是纵向。如果将打印页面的方向更改为横向,它可能会更好地匹配显示器上的图像。 |
W
wizardforcel 已提交
3029 3030 3031 3032 3033 3034 3035

SimpleTextRenderer 类包含许多特定于此 Visual Studio 模板的代码。改变颜色的背景,内部状态的加载和保存,以及显示“你好 DirectX”的成员变量因为它们与打印无关。这两个文件的最终列表包含在[附录 D](7.html#sigil_toc_id_51) 中。以下大部分代码来自微软;[附录 A](4.html#sigil_toc_id_46) 是使用微软代码的许可协议。

实际图纸与我们之前检查的完全相同;它由完全正常的 Direct2D 上下文方法调用组成。在示例代码中,该方法称为“绘制”。我用绿色突出显示了它。还必须更新应用程序类,并且可以删除处理加载和保存内部状态的 Visual Studio 模板方法。

App.xaml.cpp 的最终列表如下。

W
wizardforcel 已提交
3036
```cpp
W
wizardforcel 已提交
3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084
    //
    // App.xaml.cpp
    // Implementation of the App class.
    //
    #include "pch.h"
    #include "DirectXPage.xaml.h"
    using namespace DXPrinting;
    using namespace Platform;
    using namespace Windows::ApplicationModel;
    using namespace Windows::ApplicationModel::Activation;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Storage;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    App::App(){
         InitializeComponent();
         Suspending += ref new SuspendingEventHandler(this, &App::OnSuspending);
    }

    void App::OnLaunched(LaunchActivatedEventArgs^ args){
         m_directXPage = ref new DirectXPage();
         // Place the page in the current window and ensure that it is active.
         Window::Current->Content = m_directXPage;
         Window::Current->Activate();
    }

    void App::OnSuspending(Object^ sender, SuspendingEventArgs^ args){
         (void) sender; // Unused parameter.
         (void) args; // Unused parameter.
    }

```

实现本章和附录中的代码后,您应该能够运行应用程序并打印一个圆形图案(在 Draw 方法中呈现)。相同图案的彩色圆圈可以绘制到屏幕上,打印预览,或使用魅力栏打印到页面上。

这一章的代码很长,主要取自微软 Direct2D 打印示例。与打印机交互是一项复杂的任务,此类活动的所有样板代码都应该取自微软示例。打印 DirectX 真的是操作系统的工作,和 DirectX 关系很小。与微软示例中提供的标准代码的微小偏差将不起作用。

## 第十一章:利润

我们现在返回到我们的图表应用程序,我们将在边缘添加一个边距。我将添加到代码中,就像在“[在多个 XAML 页面之间导航](#sigil_toc_id_22)”一章之后一样。向图表添加边距很重要,因为它可以用来将标题、轴标签和网格图形与正在绘制的数据分开。下面的边距类由四个矩形和一个纯色画笔组成。矩形在可见的图表区域周围形成边框。向项目中添加一个 Margin.h 和 Margin.cpp 文件。

W
wizardforcel 已提交
3085
```cpp
W
wizardforcel 已提交
3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119
    // Margin.h
    #pragma once
    #include "DirectXBase.h"
    enum MarginStyle { Absolute, WindowSizeDependent };

    class Margin {
         ID2D1SolidColorBrush* m_solidBrush; // Brush to draw margin
         D2D1::ColorF m_color;     // The color of the margin    
         D2D1_RECT_F m_leftRect;   // Rectangles which make the margin
         D2D1_RECT_F m_rightRect;
         D2D1_RECT_F m_topRect;
         D2D1_RECT_F m_bottomRect;
         MarginStyle m_style; // Style is Absolute or Window size dependent
         // The size of the margin
         float m_left, m_right, m_top, m_bottom;
    public:
         // Public constructor
         Margin(float left, float right, float top, float bottom,
         D2D1::ColorF color, MarginStyle style);

         // Create the rectangles to draw the margin
         void CreateWindowSizeDependentResources
              (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);

         // Create the solid brush to draw with
         void CreateDeviceDependentResources
              (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);

         // The render method needs to know the panning and scaling
         void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context);
         };

```

W
wizardforcel 已提交
3120
```cpp
W
wizardforcel 已提交
3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183
    // Margin.cpp
    #include "pch.h"
    #include "Margin.h"
    Margin::Margin(float left, float right, float top, float bottom,
         D2D1::ColorF color, MarginStyle style)    : m_color(0) {
    // Save the parameters for the create resources methods
    this->m_color = color; m_style = style; this->m_left = left; this->m_right = right;
    this->m_top = top; this->m_bottom = bottom;
    }

    void Margin::CreateWindowSizeDependentResources(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // If the sizes of the margin passed were percentages of the screen
    // we have to multiply the values before creating the rectangles:
    if(m_style == MarginStyle::WindowSizeDependent) {
         m_left = context->GetSize().width * m_left;
         m_right = context->GetSize().width * m_right;
         m_top = context->GetSize().height * m_top;
         m_bottom = context->GetSize().height * m_bottom;
         }

    // Left rectangle
    m_leftRect.left = 0; m_leftRect.right = m_left;
    m_leftRect.top = 0; m_leftRect.bottom = context->GetSize().height;

    // Right rectangle
    m_rightRect.left = context->GetSize().width - m_right;
    m_rightRect.right = context->GetSize().width;
    m_rightRect.top = 0; m_rightRect.bottom = context->GetSize().height;

    // Top margin
    m_topRect.left = m_left;
    m_topRect.right = context->GetSize().width - m_right;
    m_topRect.top = 0; m_topRect.bottom = m_top;

    // Bottom margin
    m_bottomRect.left = m_left;
    m_bottomRect.right = context->GetSize().width - m_right;
    m_bottomRect.top = context->GetSize().height - m_bottom;
    m_bottomRect.bottom = context->GetSize().height;
    }

    void Margin::CreateDeviceDependentResources
         (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // Create the brush
    DX::ThrowIfFailed(context->CreateSolidColorBrush(m_color, &m_solidBrush));
    }

    void Margin::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    // Draw the left margin
    context->FillRectangle(m_leftRect, m_solidBrush);
    // Draw the right margin
    context->FillRectangle(m_rightRect, m_solidBrush);
    // Draw the top margin
    context->FillRectangle(m_topRect, m_solidBrush);
    // Draw the bottom margin
    context->FillRectangle(m_bottomRect, m_solidBrush);
    }

```

要添加 margin 对象,我们需要向图形渲染器添加一个成员变量。在适当的时候加载它的资源并渲染它。

W
wizardforcel 已提交
3184
| ![](img/note.png) | 注意:margin 类将 MarginStyle 枚举作为构造函数的参数之一。我添加了这个功能,以便根据运行应用程序的设备的大小和/或分辨率来创建边距。使用绝对作为该参数会导致边距的厚度参数被解释为像素。使用 WindowSizeDependent 意味着传入的参数被解释为上下文宽度和高度的百分比。 |
W
wizardforcel 已提交
3185 3186 3187

将 Margin.h 文件添加到图形渲染器. h 标题部分:

W
wizardforcel 已提交
3188
```cpp
W
wizardforcel 已提交
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202
    //
    // Additional headers for graph objects here
    //
    #include "GradientBackground.h"
    #include "ScatterPlot.h"
    #include "LineChart.h"
    #include "Axes.h"

      #include "Margin.h"

```

将成员变量添加到 GraphRenderer.h 文件中。

W
wizardforcel 已提交
3203
```cpp
W
wizardforcel 已提交
3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217
    // Plottable data
         GraphVariable* m_graphVariable;
         GraphVariable* m_lineChart;

         // Axes
         Axes* m_axes;

           // Margin
           Margin* m_margin;

```

调用`GraphRenderer`构造函数中保证金的构造函数;这可以在任何时候完成。我在构造`m_axes`对象后放置了它。

W
wizardforcel 已提交
3218
```cpp
W
wizardforcel 已提交
3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237
    // Create the line chart
    m_lineChart = new LineChart(x, y, count, D2D1::ColorF::Chocolate, 5.0f);

    delete[] x;
    delete[] y;

    // Create the Axes
    m_axes = new Axes(D2D1::ColorF::Black, 5.0f, 0.75f);

      // Create the margin
      m_margin = new Margin(0.1f, 0.1f, 0.1f, 0.1f,  // 10% of window size
           D2D1::ColorF(0.38f, 0.66f, 0.74f, 1.0f), MarginStyle::WindowSizeDependent);

```

}

`GraphRenderer::CreateDeviceResources`方法中为新的 m_margin 对象调用`CreateDeviceDependentResources`方法(这将创建 margin 的画笔)。

W
wizardforcel 已提交
3238
```cpp
W
wizardforcel 已提交
3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263
    void GraphRenderer::CreateDeviceResources() {
    DirectXBase::CreateDeviceResources();

    // Call the create device resources for our graph variable
    m_graphVariable->CreateDeviceDependentResources(m_d2dContext);

    // Create device resources for the line chart
    m_lineChart->CreateDeviceDependentResources(m_d2dContext);

    // Create the brush for the Axes
    m_axes->CreateDeviceDependentResources(m_d2dContext);

    // Create the solid brush for the text
    DX::ThrowIfFailed(
         m_d2dContext->CreateSolidColorBrush(ColorF(ColorF::Black),&m_blackBrush));

      // Create the margin's device dependent resources
      m_margin->CreateDeviceDependentResources(m_d2dContext);

```

}

调用`GraphRenderer::CreateWindowSizeDependentResources`方法中的`CreateWindowSizeDependentResources`方法来填充边距(这将创建填充边距的矩形)。

W
wizardforcel 已提交
3264
```cpp
W
wizardforcel 已提交
3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283
    void GraphRenderer::CreateWindowSizeDependentResources() {
    DirectXBase::CreateWindowSizeDependentResources();

    // Create window size resources for gradient background
    m_gradientBackground->CreateWindowSizeDependentResources(m_d2dContext);

    // Set the initial pan value so the lowest node is visible in the corner
    m_pan.X = -m_graphVariable->GetMinX();
    m_pan.Y = m_graphVariable->GetMinY();

      // Create window size dependent resources for the margin
      m_margin->CreateWindowSizeDependentResources(m_d2dContext);

```

}

最后,在`GraphRenderer::Render`方法中调用边缘渲染。我已经在转换矩阵重置后立即渲染了边距,就在渲染 FPS 计数器之前。我们最初添加此重置是为了确保 FPS 计数器不受平移和缩放的影响,但我们也可以使用它来确保边距不会被转换。

W
wizardforcel 已提交
3284
```cpp
W
wizardforcel 已提交
3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311
    //
    // Draw objects here
    //
    // Render the graph variable(s)
    m_lineChart->Render(m_d2dContext);
    m_graphVariable->Render(m_d2dContext);

    // Reset the transform matrix so the time and FPS do not pan or zoom
    m_d2dContext->SetTransform(m_orientationTransform2D);

      // Render the margin
      m_margin->Render(m_d2dContext);

```

//设置要打印的字符串:

std::wstring s = std::wstring(

l "总时间:")+ std::to_wstring(m_timeTotal) +

STD::wstring(L " FPS:")+STD::to _ wstring(

(int)(0.5f+1.0f/m _ time delta));// FPS 四舍五入到最接近的整数

在调试应用程序时,您应该会看到窗口周围有一个暴风雨般的青色边距,如图 34 所示。

W
wizardforcel 已提交
3312
![](img/image043.png)
W
wizardforcel 已提交
3313 3314 3315 3316 3317 3318 3319 3320 3321

图 34:边距

## 第十二章:缩放

如变换部分所述,可以使用简单的比例矩阵放大或缩小整个视图。我们将在 DirectXRenderer 页面上附加一个事件,捕获鼠标滚轮(如果用户使用的是 Windows 8 桌面),并允许滚轮改变图表的缩放比例。

我们已经有一个应用于数据的比例矩阵,但是它的任务是翻转 y 轴,所以我们没有颠倒绘制图表。为了创建缩放功能,我们可以在这个矩阵的参数值上再增加一个乘法。我使用了两个成员浮点来保存缩放系数,并且我使用了#define 来指定一些限制,避免放大太远,并避免在缩小到 32 位浮点不足时可能出现的除以零的错误。我已经对 GraphRenderer.h 文件进行了更改。

W
wizardforcel 已提交
3322
```cpp
W
wizardforcel 已提交
3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340
    // Member variables for displaying FPS
         float m_timeDelta;   // Time since last update call
         float m_timeTotal;   // Total time of application  

           // Member variables and constants for zooming
           #define MIN_ZOOM (0.01f)  // Smallest zoom value is 1%
           #define MAX_ZOOM (100.0f) // Largest zoom value is 10,000%
           float m_zoomX;  // The amount the x-axis is scaled by
           float m_zoomY;  // The amount the y-axis is scaled by

```

微软::wrl::comptr<id2d1solidcolorbrush>m _ black rush;</id2d1solidcolorbrush>

Microsoft::wrl::comptr<idwritetextformat>m _ text format;</idwritetextformat>

初始化我们刚刚在 GraphRenderer.cpp 文件中`GraphRenderer`的构造函数中定义的浮点数。

W
wizardforcel 已提交
3341
```cpp
W
wizardforcel 已提交
3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352
    GraphRenderer::GraphRenderer():

           m_zoomX(1.0f),
           m_zoomY(1.0f)

```

{

向 GraphRenderer 类添加一个公共的内部 Zoom 方法原型,这样我们就可以从 DirectXPage 类中调用它。我已经将这个原型直接放在了 GetLine 方法原型之后。

W
wizardforcel 已提交
3353
```cpp
W
wizardforcel 已提交
3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365
    internal:
         LineChart* GetLine() {
              return (LineChart*) m_lineChart;
              }

           // Zooming method
           void Zoom(float amount);

```

GraphRenderer.cpp 文件中函数的主体非常基本。我们将轴缩放乘以参数路径,并确保它们在最大缩放和最小缩放的限制范围内。将以下代码放入 GraphRenderer.cpp 文件中。它可以放在渲染方法之后的末尾。

W
wizardforcel 已提交
3366
```cpp
W
wizardforcel 已提交
3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383
    void GraphRenderer::Zoom(float amount) {
    // Multiply the zooms
    m_zoomX *= amount;
    m_zoomY *= amount;

    // Make sure the new zooms are still within the limits:
    if(m_zoomX < MIN_ZOOM) m_zoomX = MIN_ZOOM;
    else if(m_zoomX > MAX_ZOOM) m_zoomX = MAX_ZOOM;

    if(m_zoomY < MIN_ZOOM) m_zoomY = MIN_ZOOM;
    else if(m_zoomY > MAX_ZOOM) m_zoomY = MAX_ZOOM;
    }

```

接下来,我们需要添加一个事件处理程序来捕获鼠标滚轮何时改变。打开 DirectXPage.xaml 文件,找到描述 SwapChainBackground 面板的那一行,并在 xaml 代码中添加一个`PointerWheelChanged`事件。

W
wizardforcel 已提交
3384
```cpp
W
wizardforcel 已提交
3385 3386 3387 3388 3389 3390 3391 3392
    <SwapChainBackgroundPanel x:Name="SwapChainPanel"
         PointerWheelChanged="OnPointerWheelChanged" PointerMoved="OnPointerMoved"
         PointerReleased="OnPointerReleased">

```

右键单击 XAML 代码中的事件名称(我使用过`OnPointerWheelChanged`),然后在出现的上下文菜单中单击**导航到事件处理程序**

W
wizardforcel 已提交
3393
![](img/image044.jpg)
W
wizardforcel 已提交
3394 3395 3396

Visual Studio 将为我们编写事件处理程序代码,并将我们直接带到那里,这样我们就可以指定当鼠标滚轮发生变化时会发生什么。我们所需要做的就是检查车轮旋转的方向,并用合适的值调用`GraphRenderer::Zoom`方法。我用`1.2f`放大,`0.8f`缩小。这些值意味着缩放将在 20%左右相当平滑。这是`DirectXPage::OnPointerWheelChangedEvent`的代码。

W
wizardforcel 已提交
3397
```cpp
W
wizardforcel 已提交
3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410
    void DirectXPage::OnPointerWheelChanged(Platform::Object^ sender,
              Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) {
         Windows::UI::Input::PointerPoint ^p = e->GetCurrentPoint(this);
         if(p->Properties->MouseWheelDelta > 0)
              m_renderer->Zoom(1.2f);
         else
              m_renderer->Zoom(0.8f);
         }

```

`GraphRenderer::Render`方法中,将我们新的轴缩放值的额外乘法添加到`scale`矩阵中。

W
wizardforcel 已提交
3411
```cpp
W
wizardforcel 已提交
3412 3413 3414 3415 3416 3417 3418 3419 3420 3421
    // The scale matrix inverts the y-axis
    Matrix3x2F scale = Matrix3x2F::Scale(
         1.0f * m_zoomX, // Multiply by x-axis zoom
         -1.0f * m_zoomY,     // Flip and multiply by y-axis zoom
         D2D1::Point2F(0.0f, 0.0f));

```

运行应用程序后,您会发现现在可以使用鼠标滚轮放大和缩小图表。轴类不再正确渲染。首先,轴线的厚度被比例矩阵改变。当用户放大时,轴线变粗。当用户缩小时,它们会变薄并完全消失。图 35 是放大到坐标轴的交点,即原点的屏幕截图。

W
wizardforcel 已提交
3422
![](img/image045.png)
W
wizardforcel 已提交
3423 3424 3425 3426 3427

图 35:放大原点

另一个问题是,当图表缩小时,坐标轴不再横跨整个窗口。这破坏了它们是无限的错觉,因为末端清晰可见。参见图 36。

W
wizardforcel 已提交
3428
![](img/image046.png)
W
wizardforcel 已提交
3429 3430 3431 3432 3433

图 36:缩小

我们可以通过在`m_axes::Render`方法调用中乘以 m_zoomX 和 m_zoomY 来修复这两个问题。

W
wizardforcel 已提交
3434
```cpp
W
wizardforcel 已提交
3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452
    // The pan matrix still pans but it also adds the height of the screen
    Matrix3x2F panMatrix = Matrix3x2F::Translation(m_pan.X, m_pan.Y +
         m_d2dContext->GetSize().height);

    // Apply the scale first
    m_d2dContext->SetTransform(scale*panMatrix*m_orientationTransform2D);

    // Draw the axes
    m_axes->Render(m_d2dContext, m_pan.X, m_pan.Y, m_zoomX*1.0f, m_zoomY*-1.0f);

    //
    // Draw objects here
    //

```

进行此更改后,您应该能够运行应用程序并放大和缩小,而不会改变轴线的粗细,也不会使其端点可见。参见图 37。

W
wizardforcel 已提交
3453
![](img/image047.png)
W
wizardforcel 已提交
3454 3455 3456 3457 3458 3459 3460

图 37:使用缩放轴进行缩放

## 第 13 章:命中测试或选择

在图表应用程序中,确定用户是否在图表中的节点或其他对象上单击了鼠标通常很有用。这被称为命中测试或挑选。

W
wizardforcel 已提交
3461
| ![](img/tip.png) | 提示:Direct2D 几何图形有一个名为 FillContainsPoint 的方法,该方法返回一个 BOOL,指示填充的几何图形是否包含指定的点。这种方法对于简单原语集合的命中测试并不有用,因为几何图形与设备无关,并且在这种类型的任务中非常慢。几何图形可以很快确定一个点是否位于复杂的形状中,但是它们不擅长分辨一大堆节点中哪一个离给定点最近。 |
W
wizardforcel 已提交
3462 3463 3464 3465 3466

我们将在接下来的几何部分使用 Direct2D 检查实际命中测试(确定一个点是否位于一个形状内)。本节将集中讨论用指针选择节点的更有效的方法。我们将创建的特殊机制在绘图软件中很常见;它允许图表用户在选择节点时有一点自由。指针不需要正好在一个小节点的上面,只要足够近就可以了。这种机制使得用户从众多节点中选择一个小节点变得更加容易。

我们要做的就是找出哪个节点离指针最近,然后看指针是否离节点足够近。例如,如果指针远离所有节点,则不应拾取任何节点。如果我们想知道指针离哪个节点最近,我们可以非常快速地检查所有节点并计算到每个节点的距离,确定哪个是最小的。我们将编辑`ScatterPlot`类,并让它通过放大所选节点向用户显示正在选择哪个节点。构成散点图的椭圆和点都是独立于设备的资源,因此由中央处理器控制。打开散点图. h 文件,添加一个名为 PickPoint 的公共方法原型。该方法将光标的 x 和 y 位置作为参数。我在课程结束时添加了这个原型。

W
wizardforcel 已提交
3467
```cpp
W
wizardforcel 已提交
3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479
    virtual void CreateDeviceDependentResources(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;
         virtual void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) override;

           // Method to select a node
           void PickPoint(float x, float y);

```

};

向同一类添加私有成员变量。这将用于跟踪所选节点的索引。

W
wizardforcel 已提交
3480
```cpp
W
wizardforcel 已提交
3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491
    NodeShape m_NodeShape;    // The shape of the nodes

           // Selected node index
           int m_selectedNode;

```

公共:

`ScatterPlot`构造函数中将 m_selectedNode 初始化为-1。

W
wizardforcel 已提交
3492
```cpp
W
wizardforcel 已提交
3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508
    ScatterPlot::ScatterPlot(float* x, float* y, float nodeSize,
         D2D1::ColorF nodeColor, NodeShape nodeShape, int count):
            m_NodeColor(0), GraphVariable(x, y, count), m_selectedNode(-1)
    {
    // Save half the node size. The nodes are drawn with
    // the point they're representing at the middle of the shape.
    this->m_HalfNodeSize = nodeSize / 2;

    this->m_NodeShape = nodeShape;
    this->m_NodeColor = nodeColor;
    }

```

`PickPoint`方法的主体添加到散点图. cpp 文件的末尾。

W
wizardforcel 已提交
3509
```cpp
W
wizardforcel 已提交
3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546
    void ScatterPlot::PickPoint(float x, float y) {
    float smallestDistance = (x - m_points[0].x) * (x - m_points[0].x) +
         (y - m_points[0].y) * (y - m_points[0].y);

    int indexOfSmallest = 0;  // Assume closest node is the first one

    // Run through all nodes and see if any are closer
    for(int i = 1; i < m_nodeCount; i++) {   
         // Approximate the distance, don't take the sqrt()!
         float thisDistance = ((x - m_points[i].x) * (x - m_points[i].x) +
              (y - m_points[i].y) * (y - m_points[i].y));

         // If this one's closer, update the index and dist
         if(thisDistance < smallestDistance) {
              smallestDistance = thisDistance;
              indexOfSmallest = i;
              }
         }

    // Calculate the sqrt to get the real Euclidean distance
    smallestDistance = sqrt(smallestDistance);

    // If the distance is greater than a threshhold (50.0f), assume
    // no points are selected at all:
    if(smallestDistance > 50.0f)
         m_selectedNode = -1; // Nothing is selected
    // Otherwise, select the node that's closest to the pointer
    else
         m_selectedNode = indexOfSmallest;
    }

```

为了计算出哪个节点离我们的指针最近,我们必须计算每个节点到指针的距离(我已经使用了欧几里德距离),并决定哪个节点最近。最初,我假设指针最接近第一个节点。然后,我遍历所有其他节点,检查到指针的距离。如果指针比我们当前的最短距离更接近一个节点,更新最短距离和节点的索引。这样,当我们到达最后一个节点时,我们将拥有最接近指针的节点的索引。

最后,更改`ScatterPlot`类的渲染方法(在散点图. cpp 文件中),使其以不同的颜色和大小渲染选定的节点。我只包含了圆形节点的示例代码,但是这个想法可以扩展到正方形。

W
wizardforcel 已提交
3547
```cpp
W
wizardforcel 已提交
3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597
    void ScatterPlot::Render(
         Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) {
    switch(m_NodeShape) {    

           // Draw as circle nodes
           case NodeShape::Circle:
                for(int i = 0; i < m_nodeCount; i++) {
                           context->FillEllipse(D2D1::Ellipse(m_points[i],
                                m_HalfNodeSize, m_HalfNodeSize), m_brush);
                      }
                if(m_selectedNode != -1) // If a node is selected, render it larger
                           context->FillEllipse(
                                D2D1::Ellipse(m_points[m_selectedNode],
                                m_HalfNodeSize*2.0f, m_HalfNodeSize*2.0f), m_brush);
                break;

```

//绘制为方形节点

案例节点形状::正方形:

for(int I = 0;i < m _ nodeCounti++){ 0

context-> fill rectangle(d2d 1::RectF(m _ points[I])。x -

m_HalfNodeSize,

m_points[i]。y - m_HalfNodeSize,m_points[i]。x + m_HalfNodeSize,

m_points[i]。y + m_HalfNodeSize)、m _ brush);

}

打破;

//可能会出现其他形状

默认值:

打破;

}

}

我们希望当指针在 GraphRenderer 类拥有一个 PointerMoved 方法时移动时,PickPoint 方法执行。但是,只有当指针被按下时,或者当鼠标按钮被按下或者用户滑动他或她的手指时,才调用它。

向名为 UpdatePointerPosition 的 GraphRenderer 类添加一个新的公共成员方法。我已经将原型放在 GraphRenderer.h 文件中的 PointerMoved 方法之后。

W
wizardforcel 已提交
3598
```cpp
W
wizardforcel 已提交
3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612
         // Capture the pointer movements so the user can pan the chart
         void PointerMoved(Windows::Foundation::Point point);

           // Record pointers x and y value
           void UpdatePointerPosition(Windows::Foundation::Point point);

```

//更新时间相关对象的方法。

void Update(float timeTotal,float time delta);

`GraphRenderer`类添加两个私有成员变量,用于记录指针的位置。

W
wizardforcel 已提交
3613
```cpp
W
wizardforcel 已提交
3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627
         float m_zoomX;  // The amount the x-axis is scaled by
         float m_zoomY;  // The amount the y-axis is scaled by

           float m_pointerX; // X position of pointer in pixels
           float m_pointerY; // Y position of pointer in pixels

```

Microsoft::wrl::comptr<idwritetextformat>m _ text format;</idwritetextformat>

微软::wrl::comptr<id2d1solidcolorbrush>m _ black rush;</id2d1solidcolorbrush>

`UpdatePointerPosition`方法的主体添加到 GraphRenderer.cpp 文件中。我在最后加了。

W
wizardforcel 已提交
3628
```cpp
W
wizardforcel 已提交
3629 3630 3631 3632 3633 3634 3635 3636 3637 3638
    void GraphRenderer::UpdatePointerPosition(Windows::Foundation::Point point)
    {
    m_pointerX = point.X;
    m_pointerY = point.Y;
    }

```

最后,我们需要调用`m_graphVariable->PickPoint`方法。我们可以在每次指针移动时调用`UpdatePosition`方法,但是`PickPoint`是一个缓慢的方法,所以我们应该不那么频繁地调用它。我已经在新的条件下调用了`GraphRenderer::Update`方法,这将导致它每 32 帧执行一次。

W
wizardforcel 已提交
3639
```cpp
W
wizardforcel 已提交
3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661
    void GraphRenderer::Update(float timeTotal, float timeDelta) {
    static int fpsCounter = -1;    // Start at -1 so frame 0 updates timers
    fpsCounter++;
    if((fpsCounter & 15) == 0) { // Update every 16 frames
         // Record the times for display in the render method:
         m_timeDelta = timeDelta;
         m_timeTotal = timeTotal;
         }

      if((fpsCounter & 31) == 0) {
           ((ScatterPlot*)m_graphVariable)->PickPoint(
                (m_pointerX-m_pan.X)/m_zoomX,
                (-m_pointerY + m_d2dContext->GetSize().height + m_pan.Y)/m_zoomY
                );
           }

```

}

成员变量`m_pointerX``m_pointerY`是指针的屏幕坐标,所以当我们使用它们作为`PickPoint`方法的参数时,我们必须将它们转换成图形的坐标。

W
wizardforcel 已提交
3662
| ![](img/tip.png) | 提示:避免在紧密循环中使用 sqrt。在前面的代码示例中,我已经从循环体中删除了 sqrt 函数(用于正确计算指针和节点之间的欧几里德距离)。我们不需要知道这个环中间的实际距离。我们只需要知道距离最短的节点的索引。我只在循环后使用平方根计算过一次实际距离。从循环中去掉平方根可以让它的执行速度快 100 倍左右。 |
W
wizardforcel 已提交
3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677

我在之前的代码(50.0f)中包含了一个阈值。如果光标距离所有节点都超过 50.0 个单位,我们得出结论,它太远,没有选择任何节点,将所选节点设置为-1。如果光标在一个或多个节点的 50.0f 范围内,该函数将设置最近节点的索引。如果阈值等于圆形节点集合的半径(我们的散点图类中的`m_halfNodeSize`成员变量),那么这个方法就变成了命中测试,光标必须正好在一个节点上才能选中它,而不是在附近。

下一节包含另一种有趣而灵活的方法来执行命中测试,这次是使用几何图形。这些几何图形能够在像素级别上进行真正的命中测试,而不是我之前介绍的简单的最近邻搜索。几何图形的使用可能比我们刚刚检查的手动命中测试慢得多。原因是几何形状本身是复杂的、与器件无关的结构;它们非常灵活,但也很重。

我们现在已经到了开发绘图应用程序的最后阶段。本书的剩余部分是对一些额外主题和工具的讨论,这些主题和工具不会应用于当前的应用程序。

## 第 14 章:直接二维几何

几何图形允许我们为剪辑区域、点击测试和动画路径指定形状。它们是独立于设备的资源,应该在任何紧密循环之外创建。最基本的几何图形是那些基本形状,椭圆,矩形和圆角矩形。本章中的信息并不是为了扩展我们目前正在开发的绘图应用程序。它是一些更高级主题的语法和概念的介绍。下面的代码示例可以很容易地添加到我们的绘图应用程序中,但是我假设该项目是一个新的 DirectX (XAML)项目,并且渲染类被称为`SimpleTextRenderer`

### 简单几何图形

要在项目中渲染几何图形,请将这三个成员变量添加到渲染类中。

W
wizardforcel 已提交
3678
```cpp
W
wizardforcel 已提交
3679 3680 3681 3682 3683 3684 3685 3686
         ID2D1RectangleGeometry *m_rectangle;
         ID2D1EllipseGeometry *m_ellipse;
         ID2D1RoundedRectangleGeometry *m_roundedRectangle;

```

调用设备无关资源创建方法中的`m_d2dFactory`创建几何方法(`CreateDeviceIndependentResources`()在 SimpleTextRenderer 类中)。

W
wizardforcel 已提交
3687
```cpp
W
wizardforcel 已提交
3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704
    m_d2dFactory->CreateRectangleGeometry(
         D2D1::RectF(0, 0, 100, 100),
         &m_rectangle); // 100 × 100 rectangle

    m_d2dFactory->CreateEllipseGeometry(
         D2D1::Ellipse(D2D1::Point2F(100, 10), 150, 100),
         &m_ellipse); // 150 × 100 ellipse

    m_d2dFactory->CreateRoundedRectangleGeometry(
         D2D1::RoundedRect(
              D2D1::RectF(-100, -100, 100, 100),
              25, 25), &m_roundedRectangle); // 200 × 200 rectangle w/ rounded corners

```

制作几何类型的创建方法与创建相应形状的方法几乎相同。例如,`CreateEllipseGeometry`语法与`m_d2dContext::DrawEllipse`方法非常相似。要渲染先前创建的几何图形,请在渲染方法中调用上下文的渲染几何图形方法。

W
wizardforcel 已提交
3705
```cpp
W
wizardforcel 已提交
3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723
         // I've assumed the m_blackBrush brush exists!
         m_d2dContext->DrawGeometry(
              m_rectangle, m_blackBrush.Get());
         m_d2dContext->DrawGeometry(
              m_ellipse, m_blackBrush.Get());
         m_d2dContext->DrawGeometry(
              m_roundedRectangle, m_blackBrush.Get());

```

如前所述,几何图形渲染非常慢(与图元相比)。但是,几何图形非常灵活,不仅可以用于渲染形状。例如,它们有许多有趣的方法,如`ComputeLength``ComputeArea,`,它们返回形状的长度和面积,并将变换矩阵视为计算的一部分。也就是说,他们考虑缩放和倾斜。它们也可以用来建立几何下沉和使用`FillContainsPoint`方法进行命中测试。所有这些功能和其他功能使几何成为一个非常灵活的类,但是除非你需要这些特殊的能力,否则它对于计算机来说操作和渲染是比较慢的。

创建几何图形后,可以使用上下文的`FillGeometry`方法,使用纯色画笔渲染填充的形状。上下文的`DrawGeometry`方法只能用于渲染几何图形的轮廓。

变换几何

变换几何允许我们将矩阵变换附加到几何上。这对于创建缩放和平移的形状很有用,但笔画粗细(笔的粗细)保持不变。变换后的几何基本上只是一个规则的几何,但它有自己的变换矩阵。使用绘图渲染的几何图形几何图形使用上下文中的变换矩阵进行变换。

W
wizardforcel 已提交
3724
```cpp
W
wizardforcel 已提交
3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735
         m_d2dFactory->CreateTransformedGeometry(
              m_rectangle,    // Pointer to original geometry
              Matrix3x2F::Scale(1.0f, 12.0f), // Tranformation Matrix
              &m_transformed);     // Pointer to our transformed geometry

```

该代码示例使用工厂创建了一个从章节开始的变换几何图形`m_rectangle`。提供的变换矩阵沿 x 轴的比例为 1.0f,沿 y 轴的比例为 12.0f,因此形状将被拉伸 12 个单位。需要注意的重要一点是,渲染形状时不会拉伸笔画,只会拉伸形状本身。

要渲染变换后的几何图形,可以使用上下文的常规`DrawGeometry`方法。

W
wizardforcel 已提交
3736
```cpp
W
wizardforcel 已提交
3737 3738 3739 3740 3741
          m_d2dContext->DrawGeometry(
              m_transformed, m_blackBrush.Get());

```

W
wizardforcel 已提交
3742
| ![](img/tip.png) | 提示:在我们最初的折线图中,当 x 轴和 y 轴按不同的值缩放时,线条的缩放方式很奇怪。根据线条的角度,笔画的粗细也被拉长了。这会产生类似书法笔的效果,对于折线图来说是不理想的。对我们的折线图使用 TransformedGeometry 可能更好,因为线条可以用静态笔画宽度进行缩放。 |
W
wizardforcel 已提交
3743 3744 3745 3746 3747 3748 3749

### 复杂的几何图形

几何图形的强大功能之一是简单的形状可以组合在一起形成更复杂的几何图形。生成的几何图形具有原始几何图形的所有能力,计算面积、长度和命中测试。要将几个几何图形组合在一起,请使用几何图形的`CombineWithGeometry`方法。

要从线的集合中创建自定义几何图形,可以使用`PathGeometry`类。这允许我们指定一组点,用线连接它们,并渲染最终的形状。id2d 1 几何链接是一个对象,用于描述由直线、圆弧和其他几何直线图元构建的路径。路径几何使用 id2d 1 几何链接来构建形状,一旦构建完成,就可以使用上下文的`DrawGeometry``FillGeometry`方法来渲染形状。在下面的例子中,我设置了一个随机点的集合,并指定这些点创建一个封闭的形状。我已经指出几何形状是交替填充的。首先,向渲染类添加一个路径几何成员变量。

W
wizardforcel 已提交
3750
```cpp
W
wizardforcel 已提交
3751 3752 3753 3754 3755 3756
    Microsoft::WRL::ComPtr<ID2D1PathGeometry> m_pathGeometry;

```

与其他几何图形一样,路径几何图形是独立于设备的资源,因此您可以在渲染器的`CreateDeviceIndependentResources`方法中指定形状。

W
wizardforcel 已提交
3757
```cpp
W
wizardforcel 已提交
3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790
         // Create or load some data into a points array.
         int count = 25;
         D2D1_POINT_2F * points = new D2D1_POINT_2F[count*2];
         for(int i = 0; i < count; i++) {
              points[i].x = 50;
              points[i].y = i * 10;
              points[i+count].x = (count * 10)-(i * 10);
              points[i+count].y = 50;
              }   
         // Create the path geometry:
         DX::ThrowIfFailed(m_d2dFactory->CreatePathGeometry(&m_pathGeometry));   
         // Use the path geometry to create a geometry sink:
         ID2D1GeometrySink *geometrySink;
         DX::ThrowIfFailed(m_pathGeometry->Open(&geometrySink));
         // Set the fill mode:
         geometrySink->SetFillMode(D2D1_FILL_MODE::D2D1_FILL_MODE_ALTERNATE);
         // Set the start point and specify the figure is to be filled
         geometrySink->BeginFigure(points[0], D2D1_FIGURE_BEGIN_FILLED);    
         // Add the other points
         for(int i = 1; i < count; i++) {
              // Add a line to the sink connecting the current point to the last:
              geometrySink->AddLine(points[i+count]);       
              geometrySink->AddLine(points[i]);
              }   
         // End the figure, connect the final point to first
         geometrySink->EndFigure(D2D1_FIGURE_END_CLOSED);   
         // Release the geometry sink from RAM
         geometrySink->Close();
         geometrySink->Release(); 
         delete[] points;

```

W
wizardforcel 已提交
3791
| ![](img/note.png) | 注意:几何图形接收器是一种临时的几何图形构建工具。一旦构建了几何图形,就不需要它了,所以我使用了一个局部指针,并在创建完几何图形后释放了它。 |
W
wizardforcel 已提交
3792 3793 3794

最后,通过调用`FillGeometry``DrawGeometry`,可以在渲染方法中渲染几何图形。

W
wizardforcel 已提交
3795
```cpp
W
wizardforcel 已提交
3796 3797 3798 3799
         m_d2dContext->DrawGeometry(m_pathGeometry.Get(), m_blackBrush.Get());

```

W
wizardforcel 已提交
3800
![](img/image048.jpg)
W
wizardforcel 已提交
3801 3802 3803 3804 3805

图 38:未填充的几何体

图 38 是绘制的(未填充的)几何图形的输出。如果您使用上下文的`FillGeometry`方法渲染几何图形,您将得到如下内容。

W
wizardforcel 已提交
3806
![](img/image049.jpg)
W
wizardforcel 已提交
3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817

图 39:填充几何图形

图 39 是与前一个相同的几何图形,只有由几何图形中的线条创建的每一秒形状都被填充。这是由于几何下沉中指定的替代`FILL_MODE`。您也可以将填充模式设置为`D2D1_FILL_MODE_WINDING`

填充模式的实现如下。对于交替填充模式的情况:对于形状中的每个像素,在任意方向上绘制一条假想线(无论方向是什么,只要线是无限的,结果都是一样的)。如果这条线与几何图形中奇数条线相交,则像素被填充。否则,它是透明的。

在缠绕填充模式的情况下,对于每个像素,在任意方向绘制一条无限长的线。如果几何图形中从左到右穿过这条假想线的线与从右到左穿过的线一样多(从假想线的角度来看),则形状不会被填充。否则就是。

如前所述,几何图形能够做的不仅仅是渲染形状。例如,您可以计算长度和面积,或者用下面的代码轻松地执行命中测试。这个代码最好在`Render()`方法之外调用,因为它非常慢。本代码中使用的`scale``translation`矩阵可以是`SimpleTextRenderer`类的成员变量。

W
wizardforcel 已提交
3818
```cpp
W
wizardforcel 已提交
3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834
    // Compute the area of the geometry
    float area;
    m_pathGeometry->ComputeArea(scale * translation *
         m_orientationTransform2D, &area);
    // Compute the length of the geometry
    float length;
    m_pathGeometry->ComputeLength(scale * translation *
         m_orientationTransform2D, &length);
    // Specify a point for hit test:
    D2D1_POINT_2F point = D2D1::Point2F(120.0f, 150.0f);
    BOOL result = false;
    m_pathGeometry->FillContainsPoint(point, scale *
         translation * m_orientationTransform2D, &result);

```

W
wizardforcel 已提交
3835
| ![](img/tip.png) | 提示:在这些例子中,我假设有变换几何的平移、缩放和方向矩阵,并且这些在计算中被考虑。例如,如果 x 轴和 y 轴上的当前比例矩阵为 3.0f,则计算长度方法报告的长度将是形状原始长度的三倍。如果希望在不进行缩放或其他变换的情况下获得形状的面积或长度,可以使用 matrix x2f::Identity()作为变换矩阵。 |
W
wizardforcel 已提交
3836

W
wizardforcel 已提交
3837
| ![](img/note.png) | 注意:Direct2D 使用点和几何接收器创建线集合的方式与 Direct3D 使用顶点和索引缓冲区的方式非常相似。点在数组中指定,然后可以使用它们在数组中的索引在任意两点之间创建线。在检查顶点和索引缓冲区之前,确保您已经很好地掌握了这个概念;它在二维中比在三维中更简单,但它是完全相同的概念。 |
W
wizardforcel 已提交
3838 3839 3840 3841 3842

在前面的例子中,我们看了渲染直线。虽然我们使用了一个几何下沉,我们可以使用一个简化的几何下沉,因为在我们渲染的路径中没有曲线。如果需要向路径中添加贝塞尔曲线或圆弧段,可以使用几何下沉的`AddBezier``AddQuadraticBezier``AddArc`方法。

在以下示例中,使用`AddBezier`方法从加载到几何图形中的点集合中渲染一组行进形状。本示例假设代码来自上一个示例。例如,有一个名为`m_pathGeometry`的成员变量和一个来自 SimpleTextRenderer 示例的黑色画笔,等等。

W
wizardforcel 已提交
3843
```cpp
W
wizardforcel 已提交
3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855
         // Create or load some data into a points array.
         int count = 50;
         D2D1_POINT_2F * points = new D2D1_POINT_2F[count];
         for(int i = 0; i < count; i++) {
              points[i].x = (i*i);
              points[i].y = (i%2)*(i*10+i*10);
              }

```

使用点将贝塞尔曲线添加到几何图形中。

W
wizardforcel 已提交
3856
```cpp
W
wizardforcel 已提交
3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871
    // Set the fill mode:
    geometrySink->SetFillMode(D2D1_FILL_MODE::D2D1_FILL_MODE_ALTERNATE);
    // Set the start point and specify the figure is to be filled
    geometrySink->BeginFigure(points[0], D2D1_FIGURE_BEGIN_FILLED);
    // Add the other points
    for(int i = 1; i < count-1; i++) {
          // Create Beziers from the points:
          geometrySink->AddBezier(D2D1::BezierSegment(
                points[i-1],points[i],    points[i+1]));
         }

```

并在渲染方法中渲染几何图形。

W
wizardforcel 已提交
3872
```cpp
W
wizardforcel 已提交
3873 3874 3875 3876 3877 3878
         m_d2dContext->FillGeometry(m_pathGeometry.Get(), m_blackBrush.Get());

```

运行应用程序时,您应该会看到类似图 40 的内容。

W
wizardforcel 已提交
3879
![](img/image050.jpg)
W
wizardforcel 已提交
3880 3881 3882

图 40:曲线几何

W
wizardforcel 已提交
3883
| ![](img/tip.png) | 提示:曲线可以是折线图中近似中间值的简单方法。折线图中的节点可以渲染为曲线,而不是用直线连接。节点之间的任何点都可以用曲线来近似,此外,曲线看起来比通常用于渲染图表的锯齿状直线更有吸引力。实际的中间值可能并不比直线更接近曲线,但是(特别是对于高度相关或表现良好的数据集)曲线可能更接近近似值。 |</summary>