Win32 API programming with C/C++ - Painting the window using Direct2D
Window client area
We want to show something inside a window. In Windows terminology, this is called painting the window.
The first time a window is shown, the entire client area of the window must be painted. Therefore, you will always receive at least one WM_PAINT message when you show a window.
You are only responsible for painting the client area. The surrounding frame, including the title bar, is automatically painted by the operating system.
What is Direct2D
Direct2D is a hardware-accelerated, immediate-mode 2-D graphics API that provides high performance and high-quality rendering for 2-D geometry, bitmaps, and text. The Direct2D API is designed to interoperate with existing code that uses GDI, GDI+, or Direct3D.
Direct2D is designed primarily for use by the following classes of developers:
Developers of large, enterprise-scale, native applications.
Developers who create control toolkits and libraries for consumption by downstream developers.
Developers who require server-side rendering of 2-D graphics.
Developers who use Direct3D graphics and need simple, high-performance 2-D and text rendering for menus, user interface (UI) elements, and Heads-up Displays (HUDs).
We will be using C++ for this example as I was not able to create a ID2D1Factory object using C.
Include Headers and Link Libraries
First, include necessary headers and link against required libraries. Add this at the top of your source file.
#include <windows.h>
#include <d2d1.h>
#pragma comment(lib, "d2d1")
Define Global Variables
At the root of the Direct2D
API are the ID2D1Factory and ID2D1Resource interfaces. An ID2D1Factory object creates ID2D1Resource objects and serves as the starting point for using Direct2D
. All other Direct2D objects inherit from the ID2D1Resource interface.
We define global variables for managing Direct2D
factory, render target, and the paint brush.
ID2D1Factory* pD2DFactory = NULL;
ID2D1HwndRenderTarget* pRenderTarget = NULL;
ID2D1SolidColorBrush* pBrush = NULL;
Initialize Direct2D
We need to initialize Direct2D
resources. This includes creating a factory, a render target and a brush.
The ID2D1Factory interface is the starting point for using Direct2D
. We use an ID2D1Factory to instantiate Direct2D
resources. To create an ID2D1Factory, we use the D2D1CreateFactory method.
A render target is a resource that inherits from the ID2D1RenderTarget interface. A render target creates resources for drawing and performs drawing operations. There are several kinds of render targets that can be used to render graphics. We will create a ID2D1HwndRenderTarget as this is used to render content to a window.
HRESULT InitDirect2D(HWND hwnd) {
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
if (SUCCEEDED(hr)) {
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
hr = pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&pRenderTarget);
if (SUCCEEDED(hr)) {
pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black), &pBrush);
}
}
return hr;
}
We will need to cleanup resource after we use them so let's add this CleanUp method as well:
void CleanUp() {
if (pRenderTarget) pRenderTarget->Release();
if (pD2DFactory) pD2DFactory->Release();
if (pBrush) pBrush->Release();
}
Handling the WM_PAINT message
Inside our window procedure we'll add some code to handle the WM_PAINT message. Here we will initialize our Direct2D
resources and Draw a rectangle inside the window:
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT:
if (!pRenderTarget) {
InitDirect2D(hwnd);
}
pRenderTarget->BeginDraw();
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_RECT_F rect = D2D1::RectF(250, 100, 450, 300);
D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(rect, 10.f, 10.f);
pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, 5.0f, NULL);
pRenderTarget->EndDraw();
break;
// ...
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
Here's the full code below:
#include <windows.h>
#include <d2d1.h>
#pragma comment(lib, "d2d1")
ID2D1Factory* pD2DFactory = NULL;
ID2D1HwndRenderTarget* pRenderTarget = NULL;
ID2D1SolidColorBrush* pBrush = NULL;
// Forward declarations of functions
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULT InitDirect2D(HWND hwnd);
void CleanUp();
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
WNDCLASSEX wc = {
sizeof(WNDCLASSEX),
CS_HREDRAW | CS_VREDRAW,
WndProc,
0L, 0L,
GetModuleHandle(NULL),
NULL,
NULL,
(HBRUSH)(COLOR_WINDOW+1),
NULL,
L"Direct2DApp",
NULL };
RegisterClassEx(&wc);
HWND hwnd = CreateWindowEx(0, L"Direct2DApp", L"Direct2D Demo",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, wc.hInstance, NULL);
if (hwnd) {
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT:
if (!pRenderTarget) {
InitDirect2D(hwnd);
}
pRenderTarget->BeginDraw();
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_RECT_F rect = D2D1::RectF(250, 100, 450, 300);
D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(rect, 10.f, 10.f);
pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, 5.0f, NULL);
pRenderTarget->EndDraw();
break;
case WM_DESTROY:
CleanUp();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
HRESULT InitDirect2D(HWND hwnd)
{
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
if (SUCCEEDED(hr)) {
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
hr = pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&pRenderTarget);
if (SUCCEEDED(hr)) {
pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Blue), &pBrush);
}
}
return hr;
}
void CleanUp()
{
if (pRenderTarget) pRenderTarget->Release();
if (pD2DFactory) pD2DFactory->Release();
if (pBrush) pBrush->Release();
}
Subscribe to my newsletter
Read articles from Ciprian Fusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ciprian Fusa
Ciprian Fusa
I am a .NET Developer and Consultant with over 15 years of experience, I specialize in crafting scalable, high-performance applications that drive business growth and innovation.