DirectWrite Hello World 示例简化版

微软有一些 DirectWrite Win32 示例程序,源码在 https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/DirectWrite

本文参照 TextDialogSample 代码,对 HelloWorld 示例进行了简化,简化内容有两方面:

  1. 使用对话框模板简化了原来使用 CreateWindow() 创建窗口的代码
  2. 通过类继承简化了 MultiformattedText 类和 CustomText 类的代码

开发环境:Windows 10 64位 + Visual Studio 2015


创建 Win32 程序

启动 Visual Studio 2015,新建 Win32 项目 DWHelloWorld
在这里插入图片描述
在这里插入图片描述
在向导界面,选择 “空项目”,然后点击 [完成] 按钮创建新项目。

添加对话框模板

在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->资源…]。
在这里插入图片描述
在这里插入图片描述
在 [添加资源] 对话框,在 [资源类型] 下面选择 [Dialog],然后点击 [新建] 按钮,创建对话框模板。

  • 删除对话框上自带的 [确定] 和 [取消] 两个按钮
  • 从工具箱中拖拽一个 Tab Control 控件到对话框上
  • 再拖拽一个 Picture Control 控件到对话框上
  • 修改 Picture ControlIDIDC_PICTURE,修改 TypeOwner Draw
  • 修改对话框的 BorderCaptionMaximize BoxMinimize BoxCenter 属性

注意:一定要修改 Picture ControlID 值,否则 GetDlgItem 函数获取不到该控件。

在这里插入图片描述

添加 SimpleText 类

直接把原来的文件复制过来,做适当修改即可。

复制 SimpleText.h 和 SimpleText.cpp 文件

把原项目里的 SimpleText.hSimpleText.cpp 文件复制到当前项目里,与 DWHelloWorld.vcxproj 文件放在一起。
然后在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->现有项…],把这两个文件添加到项目里。
在这里插入图片描述

修改 SimpleText.h 文件

1、在 #pragma once 下面添加如下代码:

#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")

// SafeRelease inline function.
template <class T> inline void SafeRelease(T **ppT)
{
	if (*ppT)
	{
		(*ppT)->Release();
		*ppT = NULL;
	}
}

2、把 class SimpleText 里的两个 private: 替换为 protected:,以便派生类可以访问。
3、把 DrawD2DContent()OnResize() 两个函数挪到 public: 下面,使可以由外部调用。
4、在 CreateDeviceIndependentResources()CreateDeviceResources()DiscardDeviceResources()DrawText() 四个函数的声明前面添加 virtual 关键字。
说明:加 virtual 关键字是把函数声明为虚函数。这四个函数会在派生类中重写,如果不声明为虚函数,派生类中的这四个函数将不能从基类调用。
5、删掉 WndProc() 函数。
6、把 Initialize(HWND hwndParent) 函数的参数名改为 hwndhwndParent 参数在原代码里表示父窗口的句柄,在新代码里将用来表示绘图窗口的句柄,Parent 字样会产生歧义,所以去掉。

修改 SimpleText.cpp 文件

1、删掉 #include "DWriteHelloWorld.h",添加 #include "SimpleText.h"
2、修改 Initialize() 函数为:

HRESULT SimpleText::Initialize(HWND hwnd)
{
	hwnd_ = hwnd;

	//get the dpi information
	HDC screen = GetDC(0);
	dpiScaleX_ = GetDeviceCaps(screen, LOGPIXELSX) / 96.0f;
	dpiScaleY_ = GetDeviceCaps(screen, LOGPIXELSY) / 96.0f;
	ReleaseDC(0, screen);

	HRESULT hr = CreateDeviceIndependentResources();

	return hr;
}

3、删掉 WndProc() 函数。

添加 MultiformattedText 类

在原来的例子里,MultiformattedText 类是从头写的,但 MultiformattedText 类与 SimpleText 类有很多代码是相同的,所以我们把 MultiformattedText 类改为从 SimpleText 类继承,这样可以少写很多重复的代码。

在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->类…]。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

修改 MultiformattedText.h 文件

重写 OnResize()CreateDeviceIndependentResources()DrawText() 三个函数,再添加一个成员变量 pTextLayout_
修改后的 MultiformattedText.h 文件内容如下:

#pragma once
#include "SimpleText.h"
class MultiformattedText :
	public SimpleText
{
public:
	MultiformattedText();
	~MultiformattedText();

	void OnResize(UINT width, UINT height);

protected:
	HRESULT CreateDeviceIndependentResources();
	HRESULT DrawText();

	IDWriteTextLayout* pTextLayout_;
};

修改 MultiformattedText.cpp 文件

  • 修改构造函数和析构函数,实现 pTextLayout_ 的初始化和资源释放。
  • 添加 OnResize()CreateDeviceIndependentResources()DrawText() 三个函数的定义。
void MultiformattedText::OnResize(UINT width, UINT height)
{
	SimpleText::OnResize(width, height);

	if (pTextLayout_)
	{
		pTextLayout_->SetMaxWidth(static_cast<FLOAT>(width / dpiScaleX_));
		pTextLayout_->SetMaxHeight(static_cast<FLOAT>(height / dpiScaleY_));
	}
}
HRESULT MultiformattedText::CreateDeviceIndependentResources()
{
	SimpleText::CreateDeviceIndependentResources();

	HRESULT hr;
	hr = pDWriteFactory_->CreateTextLayout(
		wszText_,      // The string to be laid out and formatted.
		cTextLength_,  // The length of the string.
		pTextFormat_,  // The text format to apply to the string (contains font information, etc).
		640.0f,         // The width of the layout box.
		480.0f,         // The height of the layout box.
		&pTextLayout_  // The IDWriteTextLayout interface pointer.
	);

	// Format the "DirectWrite" substring to be of font size 100.
	if (SUCCEEDED(hr))
	{
		DWRITE_TEXT_RANGE textRange = { 20,        // Start index where "DirectWrite" appears.
			6 };      // Length of the substring "Direct" in "DirectWrite".
		hr = pTextLayout_->SetFontSize(100.0f, textRange);
	}

	// Format the word "DWrite" to be underlined.
	if (SUCCEEDED(hr))
	{
		DWRITE_TEXT_RANGE textRange = { 20,      // Start index where "DirectWrite" appears.
			11 };    // Length of the substring "DirectWrite".
		hr = pTextLayout_->SetUnderline(TRUE, textRange);
	}

	if (SUCCEEDED(hr))
	{
		// Format the word "DWrite" to be bold.
		DWRITE_TEXT_RANGE textRange = { 20,
			11 };
		hr = pTextLayout_->SetFontWeight(DWRITE_FONT_WEIGHT_BOLD, textRange);
	}

	// Declare a typography pointer.
	IDWriteTypography* pTypography = NULL;

	// Create a typography interface object.
	if (SUCCEEDED(hr))
	{
		hr = pDWriteFactory_->CreateTypography(&pTypography);
	}

	// Set the stylistic set.
	DWRITE_FONT_FEATURE fontFeature = { DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7,
		1 };
	if (SUCCEEDED(hr))
	{
		hr = pTypography->AddFontFeature(fontFeature);
	}

	if (SUCCEEDED(hr))
	{
		// Set the typography for the entire string.
		DWRITE_TEXT_RANGE textRange = { 0,
			cTextLength_ };
		hr = pTextLayout_->SetTypography(pTypography, textRange);
	}

	SafeRelease(&pTypography);

	return hr;
}
HRESULT MultiformattedText::DrawText()
{
	RECT rc;

	GetClientRect(hwnd_, &rc);

	D2D1_POINT_2F origin = D2D1::Point2F(
		static_cast<FLOAT>(rc.top / dpiScaleY_),
		static_cast<FLOAT>(rc.left / dpiScaleX_)
	);

	pRT_->DrawTextLayout(origin, pTextLayout_, pBlackBrush_);

	return S_OK;
}

添加 CustomText 类

CustomText 类依赖自定义渲染器类 CustomTextRenderer,CustomTextRenderer 类可以直接从原项目复制过来。

复制 CustomTextRenderer.h 和 CustomTextRenderer.cpp 文件

把原项目里的 CustomTextRenderer.hCustomTextRenderer.cpp 文件复制到当前项目里,与 DWHelloWorld.vcxproj 文件放在一起。
然后在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->现有项…],把这两个文件添加到项目里。

修改 CustomTextRenderer.h 文件

#pragma once 下面添加如下代码:

#include <d2d1.h>
#include <dwrite.h>

修改 CustomTextRenderer.cpp 文件

删掉 #include "DWriteHelloWorld.h",并添加如下代码:

#include "CustomTextRenderer.h"

// SafeRelease inline function.
template <class T> inline void SafeRelease(T **ppT)
{
	if (*ppT)
	{
		(*ppT)->Release();
		*ppT = NULL;
	}
}

添加图片资源

CustomText 类使用一个图片资源渲染文字,下面把这个图片从原项目复制过来,并添加到资源里。

1、把原项目里的 img1.jpg 文件复制到当前项目里,与 DWHelloWorld.vcxproj 文件放在一起。
2、在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->资源…],打开 添加资源 对话框。
在这里插入图片描述
3、点击【导入】按钮,打开 导入 对话框,将 [文件类型] 改为 所有文件,选中刚才复制过来的 img1.jpg 文件。
在这里插入图片描述
4、点击【打开】按钮,打开 自定义资源类型 对话框,在 [资源类型] 输入框中输入 "IMAGE",然后点击 [确定] 按钮。
在这里插入图片描述
对话框关闭后,会以二进制的形式自动打开 img1.jpg 文件,此时不需做任何操作,直接关闭该二进制窗口。

5、按 Ctrl+Shift+E 打开 资源视图 窗口,依次展开 DWHelloWorld->DWHelloWorld.rc->“IMAGE” 节点,选中 IDR_IMAGE1,按 F4 键打开 属性 窗口,修改 IDIDR_IMAGE1"TULIP"

注: "IMAGE""TULIP" 都是原来项目里的名称,在这里直接使用原来的名称,可以免得修改代码。
问题: img1.jpg 文件原来是 JPG 格式的,经过以上操作后,就变成 BMP 格式了,文件大小也增大了好几倍,不清楚为什么。

添加 CustomText 类

像 MultiformattedText 类一样,我们新建 CustomText 类,并从 MultiformattedText 类继承,这样可以少写很多重复的代码。

在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->类…],打开 添加类 窗口,添加 C++ 类。
在这里插入图片描述

修改 CustomText.h 文件

重写 CreateDeviceIndependentResources()CreateDeviceResources()DiscardDeviceResources()DrawText() 四个函数,添加一个 LoadResourceBitmap() 函数,再添加三个成员变量 pBitmapBrush_pTextRenderer_pWICFactory_

修改后的 CustomText.h 文件内容如下:

#pragma once
#include "MultiformattedText.h"

#include <wincodec.h>
#pragma comment(lib, "WindowsCodecs.lib")

class CustomText :
	public MultiformattedText
{
public:
	CustomText();
	~CustomText();

private:
	HRESULT CreateDeviceIndependentResources();
	HRESULT CreateDeviceResources();
	void DiscardDeviceResources();
	HRESULT DrawText();

	HRESULT LoadResourceBitmap(
		ID2D1RenderTarget *pRT,
		IWICImagingFactory *pIWICFactory,
		PCWSTR resourceName,
		PCWSTR resourceType,
		__deref_out ID2D1Bitmap **ppBitmap
	);

	ID2D1BitmapBrush* pBitmapBrush_;
	IDWriteTextRenderer* pTextRenderer_;
	IWICImagingFactory* pWICFactory_;
};

修改 CustomText.cpp 文件

  • 添加 #include "CustomTextRenderer.h"
  • 修改构造函数和析构函数,实现 pBitmapBrush_pTextRenderer_pWICFactory_ 的初始化和资源释放。
  • 添加 CreateDeviceIndependentResources()CreateDeviceResources()DiscardDeviceResources()DrawText() 四个函数的定义。
  • LoadResourceBitmap() 函数的代码直接从原项目复制过来即可。
HRESULT CustomText::CreateDeviceIndependentResources()
{
	MultiformattedText::CreateDeviceIndependentResources();

	// Create WIC factory
	HRESULT hr = CoCreateInstance(
		CLSID_WICImagingFactory,
		NULL,
		CLSCTX_INPROC_SERVER,
		IID_IWICImagingFactory,
		reinterpret_cast<void **>(&pWICFactory_)
	);

	return hr;
}
HRESULT CustomText::CreateDeviceResources()
{
	SimpleText::CreateDeviceResources();

	HRESULT hr = S_OK;
	if (pRT_)
	{
		ID2D1Bitmap* pBitmap = NULL;

		// Create the bitmap to be used by the bitmap brush
		if (SUCCEEDED(hr))
		{
			hr = LoadResourceBitmap(
				pRT_,
				pWICFactory_,
				L"Tulip",
				L"Image",
				&pBitmap
			);
		}

		// Create the bitmap brush
		D2D1_BITMAP_BRUSH_PROPERTIES properties = { D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR };
		if (SUCCEEDED(hr))
		{
			hr = pRT_->CreateBitmapBrush(
				pBitmap,
				properties,
				&pBitmapBrush_);
		}

		if (SUCCEEDED(hr))
		{
			// Create the text renderer
			pTextRenderer_ = new (std::nothrow) CustomTextRenderer(
				pD2DFactory_,
				pRT_,
				pBlackBrush_,
				pBitmapBrush_
			);
		}

		SafeRelease(&pBitmap);
	}
	return hr;
}
void CustomText::DiscardDeviceResources()
{
	SimpleText::DiscardDeviceResources();

	SafeRelease(&pBitmapBrush_);
	SafeRelease(&pTextRenderer_);
}
HRESULT CustomText::DrawText()
{
	HRESULT hr = S_OK;

	RECT rc;

	GetClientRect(
		hwnd_,
		&rc);

	D2D1_POINT_2F origin = D2D1::Point2F(
		static_cast<FLOAT>(rc.top / dpiScaleY_),
		static_cast<FLOAT>(rc.left / dpiScaleX_)
	);

	// Draw the text layout using DirectWrite and the CustomTextRenderer class.
	hr = pTextLayout_->Draw(
		NULL,
		pTextRenderer_,  // Custom text renderer.
		origin.x,
		origin.y
	);

	return hr;
}

添加 WinMain.cpp 文件

在这个文件里,实现程序启动、创建对话框,及处理消息循环。

在解决方案资源管理器中,在项目名称 [DWHelloWorld] 上点击鼠标右键,在快捷菜单上选择 [添加->新建项…],打开 添加新项 窗口,添加 C++ 文件。
在这里插入图片描述
在这里插入图片描述
WinMain.cpp 文件代码如下:

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")

#include "resource.h"

#include "SimpleText.h"
#include "MultiformattedText.h"
#include "CustomText.h"

// 开启可视化效果
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
SimpleText simpleText_;
MultiformattedText multiformattedText_;
CustomText customText_;

int tabIndex = 0;

INT_PTR CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL OnInitDialog(HWND hWnd, HWND hwndFocus, LPARAM lParam);
UINT OnShowWindow(HWND hWnd, BOOL shown, UINT state);
void OnSize(HWND hWnd, UINT state, int cx, int cy);
void OnPaint(HWND hWnd);
void OnClose(HWND hWnd);
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR /*lpCmdLine*/, int /*nCmdShow*/)
{
	return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc);
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
		HANDLE_MSG(hWnd, WM_INITDIALOG, OnInitDialog);
		HANDLE_MSG(hWnd, WM_SHOWWINDOW, OnShowWindow);
		HANDLE_MSG(hWnd, WM_SIZE, OnSize);
		HANDLE_MSG(hWnd, WM_PAINT, OnPaint);
		HANDLE_MSG(hWnd, WM_CLOSE, OnClose);
	case WM_NOTIFY:
		switch (((LPNMHDR)lParam)->code)
		{
		case TCN_SELCHANGE:
		{
			HWND hTab = GetDlgItem(hWnd, IDC_TAB1);
			tabIndex = TabCtrl_GetCurSel(hTab);
			OnPaint(hWnd);
		}
		break;
		}
		break;
	}
	return FALSE;
}
int InitTabItem(HWND hTab, LPTSTR pszText, int iid)
{
	TCITEM ti = { 0 };
	ti.mask = TCIF_TEXT;
	ti.pszText = pszText;
	ti.cchTextMax = wcslen(pszText);

	return (int)TabCtrl_InsertItem(hTab, iid, &ti);  //宏 SendMessage
}

BOOL OnInitDialog(HWND hWnd, HWND hwndFocus, LPARAM lParam)
{
	CoInitialize(NULL);

	HWND hTab = GetDlgItem(hWnd, IDC_TAB1);
	InitTabItem(hTab, L"Simple Text", 0);
	InitTabItem(hTab, L"Multiformatted Text", 1);
	InitTabItem(hTab, L"Custom Text Renderer", 2);

	HWND hPic = GetDlgItem(hWnd, IDC_PICTURE);
	simpleText_.Initialize(hPic);
	multiformattedText_.Initialize(hPic);
	customText_.Initialize(hPic);
	return TRUE;
}
UINT OnShowWindow(HWND hWnd, BOOL shown, UINT state)
{
	if (shown)
	{
		RECT rc;
		GetClientRect(hWnd, &rc);
		OnSize(hWnd, NULL, rc.right - rc.left, rc.bottom - rc.top);
		return 0;
	}
	return 1;
}
void OnSize(HWND hWnd, UINT state, int width, int height)
{
	HWND hTab = GetDlgItem(hWnd, IDC_TAB1);

	// Resize tab control to fill client area of its parent window
	MoveWindow(hTab, 0, 0, width, height, TRUE);

	RECT rc;
	SetRect(&rc, 0, 0, width, height);
	TabCtrl_AdjustRect(hTab, FALSE, &rc);

	// Get the Tab control handle which was previously stored in the 
	// user data associated with the parent window.
	HWND hPic = GetDlgItem(hWnd, IDC_PICTURE);

	// Resize tab control to fill client area of its parent window
	MoveWindow(hPic, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);

	BringWindowToTop(hPic);  //使绘图窗口处于最前面

	simpleText_.OnResize(rc.right - rc.left, rc.bottom - rc.top);
	multiformattedText_.OnResize(rc.right - rc.left, rc.bottom - rc.top);
	customText_.OnResize(rc.right - rc.left, rc.bottom - rc.top);
}
void OnPaint(HWND hWnd)
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(hWnd, &ps);

	switch (tabIndex)
	{
	case 0:
		simpleText_.DrawD2DContent();
		break;
	case 1:
		multiformattedText_.DrawD2DContent();
		break;
	case 2:
		customText_.DrawD2DContent();
		break;
	}

	EndPaint(hWnd, &ps);
}
void OnClose(HWND hwnd)
{
	CoUninitialize();
	PostQuitMessage(0);
}

问题处理

如果编译程序时出现链接错误 LNK2001 无法解析的外部符号 _CLSID_WICImagingFactory2,只要将 $(WindowsSDK_LibraryPath_x86) 添加到 VC++ 库目录 即可解决。
在这里插入图片描述

运行程序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码下载

https://download.csdn.net/download/blackwoodcliff/11853345

参考

教程:DirectWrite 入门

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章