微软有一些 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