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

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