微軟有一些 DirectWrite Win32 示例程序,源碼在 https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/DirectWrite
本文參照 TextDialogSample 代碼,對 HelloWorld 示例進行了簡化,簡化內容有兩方面:
- 使用對話框模板簡化了原來使用 CreateWindow() 創建窗口的代碼
- 通過類繼承簡化了 MultiformattedText 類和 CustomText 類的代碼
開發環境:Windows 10 64位 + Visual Studio 2015
創建 Win32 程序
啓動 Visual Studio 2015,新建 Win32 項目 DWHelloWorld
。
在嚮導界面,選擇 “空項目”,然後點擊 [完成] 按鈕創建新項目。
添加對話框模板
在解決方案資源管理器中,在項目名稱 [DWHelloWorld] 上點擊鼠標右鍵,在快捷菜單上選擇 [添加->資源…]。
在 [添加資源] 對話框,在 [資源類型] 下面選擇 [Dialog],然後點擊 [新建] 按鈕,創建對話框模板。
- 刪除對話框上自帶的 [確定] 和 [取消] 兩個按鈕
- 從工具箱中拖拽一個 Tab Control 控件到對話框上
- 再拖拽一個 Picture Control 控件到對話框上
- 修改 Picture Control 的 ID 爲
IDC_PICTURE
,修改 Type 爲Owner Draw
- 修改對話框的 Border、Caption、Maximize Box、Minimize Box、Center 屬性
注意:一定要修改 Picture Control 的 ID 值,否則 GetDlgItem 函數獲取不到該控件。
添加 SimpleText 類
直接把原來的文件複製過來,做適當修改即可。
複製 SimpleText.h 和 SimpleText.cpp 文件
把原項目裏的 SimpleText.h 和 SimpleText.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)
函數的參數名改爲 hwnd
。hwndParent
參數在原代碼裏表示父窗口的句柄,在新代碼裏將用來表示繪圖窗口的句柄,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.h 和 CustomTextRenderer.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 鍵打開 屬性 窗口,修改 ID 值 IDR_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