I draw most of my UI using ID2D1HwndRenderTarget, but I want some classic window controls: button, edit. How to
ID2D1HwndRenderTarget * canvas = nullptr; // it's global object
HWND button = nullptr; // it's global object
HWND edit = nullptr; // it's global object
HWND custom = nullptr; // it's global object
// mainWindow WinPproc
case WM_CREATE:
button = CreateWindowExW(0, L"button", L"Send", WS_CHILD | WS_VISIBLE, 10, 10, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // win32 control
edit = CreateWindowExW(0, L"edit", L"Edit", WS_CHILD | WS_VISIBLE, 10, 50, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // win32 control
custom = CreateWindowExW(0, L"custom", L"Custom", WS_CHILD | WS_VISIBLE, 10, 90, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // it's my custom class
break;
case WM_PAINT:
BeginPaint(hWnd, nullptr);
render_target->BeginPaint();
... GUI rendering stuff ....
HRESULT result = render_target->EndDraw();
if(result != S_OK)
{
// Error handling
...
}
EndPaint(hWnd, nullptr);
break;
// custom WinProc
case WM_PAINT:
BeginPaint(hWnd, nullptr);
render_target->BeginPaint();
... rendering stuff ....
HRESULT result = render_target->EndDraw();
if(result != S_OK)
{
// Error handling
...
}
EndPaint(hWnd, nullptr);
break;
Only things painted with render_target are visible. I understand why: becausebutton and edit are default win32 controls, internally drawn using PAINTSTRUCT->HDC context. I read Direct2D and GDI Interoperability Overview and get the concept, but still don't know where this HDC intrecpet should take place? I don't want touch default control WM_PAINT. I have to supclass all default win32 controls?
How to force those Win32 controls to draw onto my render_target?
If you want to share a device context (HDC) between GDI and Direct2D, you can use a ID2D1DCRenderTarget and Bind to this HDC when you want to render on it.
This is demonstrated in this official sample : GDI/Direct2D Interoperability Sample
Note, as is, it doesn't compile/work with today's Visual Studio. So, here is a similar code here with a simple button and textbox:
#include <windows.h>
#include <stdlib.h>
#include <math.h>
#include <d2d1.h>
template<class Interface>
inline void
SafeRelease(Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
class DemoApp
{
public:
DemoApp();
~DemoApp();
HRESULT Initialize();
private:
HRESULT CreateDeviceIndependentResources();
HRESULT CreateDeviceResources();
void DiscardDeviceResources();
HRESULT OnRender(const PAINTSTRUCT& ps);
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
HWND m_hwnd;
ID2D1Factory* m_pD2DFactory;
ID2D1DCRenderTarget* m_pDCRT;
ID2D1SolidColorBrush* m_pBlackBrush;
};
int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
{
if (SUCCEEDED(CoInitialize(NULL)))
{
DemoApp app;
if (SUCCEEDED(app.Initialize()))
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CoUninitialize();
}
return 0;
}
DemoApp::DemoApp() :
m_hwnd(NULL),
m_pD2DFactory(NULL),
m_pDCRT(NULL),
m_pBlackBrush(NULL)
{
}
DemoApp::~DemoApp()
{
SafeRelease(&m_pD2DFactory);
SafeRelease(&m_pDCRT);
SafeRelease(&m_pBlackBrush);
}
HRESULT DemoApp::Initialize()
{
HRESULT hr;
hr = CreateDeviceIndependentResources();
if (SUCCEEDED(hr))
{
// Register the window class.
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DemoApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = (HINSTANCE)&__ImageBase;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = L"D2DDemoApp";
RegisterClassEx(&wcex);
// Create the application window.
// Because the CreateWindow function takes its size in pixels, we obtain the system DPI and use it to scale the window size.
FLOAT dpiX, dpiY;
m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);
m_hwnd = CreateWindow(
L"D2DDemoApp",
L"Direct2D Demo App",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
NULL,
NULL,
(HINSTANCE)&__ImageBase,
this
);
hr = m_hwnd ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
ShowWindow(m_hwnd, SW_SHOWNORMAL);
UpdateWindow(m_hwnd);
}
}
return hr;
}
HRESULT DemoApp::CreateDeviceIndependentResources()
{
// Create D2D factory
return D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
}
HRESULT DemoApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
if (!m_pDCRT)
{
// Create a DC render target.
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_IGNORE),
0,
0,
D2D1_RENDER_TARGET_USAGE_NONE,
D2D1_FEATURE_LEVEL_DEFAULT
);
hr = m_pD2DFactory->CreateDCRenderTarget(&props, &m_pDCRT);
if (SUCCEEDED(hr))
{
// Create a black brush.
hr = m_pDCRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_pBlackBrush);
}
}
return hr;
}
void DemoApp::DiscardDeviceResources()
{
SafeRelease(&m_pDCRT);
SafeRelease(&m_pBlackBrush);
}
HRESULT DemoApp::OnRender(const PAINTSTRUCT& ps)
{
HRESULT hr;
RECT rc;
// Get the dimensions of the client drawing area.
GetClientRect(m_hwnd, &rc);
// Draw the pie chart with Direct2D.
// Create the DC render target.
hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
// Bind the DC to the DC render target.
hr = m_pDCRT->BindDC(ps.hdc, &rc);
m_pDCRT->BeginDraw();
m_pDCRT->SetTransform(D2D1::Matrix3x2F::Identity());
m_pDCRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
m_pDCRT->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(150.0f, 150.0f), 100.0f, 100.0f), m_pBlackBrush, 3.0);
m_pDCRT->DrawLine(
D2D1::Point2F(150.0f, 150.0f),
D2D1::Point2F((150.0f + 100.0f * 0.15425f), (150.0f - 100.0f * 0.988f)), m_pBlackBrush, 3.0
);
m_pDCRT->DrawLine(
D2D1::Point2F(150.0f, 150.0f),
D2D1::Point2F((150.0f + 100.0f * 0.525f), (150.0f + 100.0f * 0.8509f)), m_pBlackBrush, 3.0
);
m_pDCRT->DrawLine(
D2D1::Point2F(150.0f, 150.0f),
D2D1::Point2F((150.0f - 100.0f * 0.988f), (150.0f - 100.0f * 0.15425f)), m_pBlackBrush, 3.0
);
hr = m_pDCRT->EndDraw();
if (SUCCEEDED(hr))
{
// Draw the pie chart with GDI.
// Save the original object.
HGDIOBJ original = NULL;
original = SelectObject(ps.hdc, GetStockObject(DC_PEN));
HPEN blackPen = CreatePen(PS_SOLID, 3, 0);
SelectObject(ps.hdc, blackPen);
Ellipse(ps.hdc, 300, 50, 500, 250);
POINT pntArray1[2];
pntArray1[0].x = 400;
pntArray1[0].y = 150;
pntArray1[1].x = static_cast<LONG>(400 + 100 * 0.15425);
pntArray1[1].y = static_cast<LONG>(150 - 100 * 0.9885);
POINT pntArray2[2];
pntArray2[0].x = 400;
pntArray2[0].y = 150;
pntArray2[1].x = static_cast<LONG>(400 + 100 * 0.525);
pntArray2[1].y = static_cast<LONG>(150 + 100 * 0.8509);
POINT pntArray3[2];
pntArray3[0].x = 400;
pntArray3[0].y = 150;
pntArray3[1].x = static_cast<LONG>(400 - 100 * 0.988);
pntArray3[1].y = static_cast<LONG>(150 - 100 * 0.15425);
Polyline(ps.hdc, pntArray1, 2);
Polyline(ps.hdc, pntArray2, 2);
Polyline(ps.hdc, pntArray3, 2);
DeleteObject(blackPen);
// Restore the original object.
SelectObject(ps.hdc, original);
}
}
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
DiscardDeviceResources();
}
return hr;
}
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pcs->lpCreateParams);
auto button = CreateWindowExW(0, L"button", L"Send", WS_CHILD | WS_VISIBLE, 10, 10, 120, 30, hwnd, (HMENU)1, (HINSTANCE)&__ImageBase, 0); // win32 control
auto edit = CreateWindowExW(0, L"edit", L"Edit", WS_CHILD | WS_VISIBLE, 10, 50, 120, 30, hwnd, (HMENU)2, (HINSTANCE)&__ImageBase, 0); // win32 control
return 1;
}
LRESULT result = 0;
DemoApp* pDemoApp = (DemoApp*)(GetWindowLongPtr(hwnd, GWLP_USERDATA));
bool wasHandled = false;
if (pDemoApp)
{
switch (message)
{
case WM_PAINT:
case WM_DISPLAYCHANGE:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
pDemoApp->OnRender(ps);
EndPaint(hwnd, &ps);
}
result = 0;
wasHandled = true;
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
result = 1;
wasHandled = true;
break;
}
}
if (!wasHandled)
{
result = DefWindowProc(hwnd, message, wParam, lParam);
}
return result;
}
And here is how it renders (left circle is Direct2D, right is aliased GDI):
How to force Win32 controls to draw onto my ID2D1Bitmap1? Is that even possible?
Yes. You can write GDI content to a Direct2D GDI-compatible render target. This approach is useful for applications that primarily render with Direct2D but have an extensibility model or other legacy content that requires the ability to render with GDI.
Step:
To render GDI content to a Direct2D GDI-compatible render target, use
an ID2D1GdiInteropRenderTarget, which provides access to a device
context that can accept GDI draw calls. Unlike other interfaces, an
ID2D1GdiInteropRenderTarget object is not created directly. Instead,
use the QueryInterface method of an existing render target instance.
Refer: Draw GDI Content to a Direct2D GDI-Compatible Render Target
Related
I'm using a SwapChain created with CreateSwapChainForHwnd with DXGI_SCALING_STRETCH scaling. When the sizes of the swapchain and the window client area differ, the SwapChain's associated bitmap is correctly scaled, but its origin is moved as if the window borders were accounted in the stretch process although there sizes are constant.
Is there a way to fix or circumvent this issue ?
Thank you for the help !
(More precisely : for a client area larger that swapchain, the origin of the bitmap in shifted down, right proportionally respectively to title-bar height and left border width, the part of the bitmap thus moved outside the client area being invisible; a reverse shift is produced with client area smaller than swapchain).
Below a minimal code to reproduce this issue based on visual studio desktop app template. I put everything in InitInstance() (I know it's bad), appart from classical DX #includes and declarations. It corresponds to https://learn.microsoft.com/en-us/windows/win32/direct2d/devices-and-device-contexts except I create the swapchain with CreateSwapChainForHwnd() instead of CreateSwapChainForCoreWindow() :
The pb appears fror MDI childs, as well as for SDI windows with a menu. The way the buffer bitmap is drawn, CopyFromMemory, DrawBitmap or even D2D drawing doesn't matter, as shown in images with the two diagonals made with DrawLine().226707-stretchedout.png
Actually the white bands are flickering during resizing.
Example, starting from Visual Studios's destop app template, replace InitInstance() below and add D2D includes and declarations...
...
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
ATOM aMainClass;
ATOM aChildClass;
const TCHAR lpszMainClassName[] = TEXT("FCLAYERMAIN");
const TCHAR lpszChildClassName[] = TEXT("FCLAYERCHILD");
hInst = hInstance;
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
//prepare MDI windows (main + MDI client + one child)
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = lpszMainClassName;
wcex.hIconSm = NULL;
aMainClass = RegisterClassEx(&wcex);
wcex.lpfnWndProc = ChildWndProc;
wcex.lpszClassName = lpszChildClassName;
wcex.hCursor = LoadCursor(NULL, IDC_CROSS);
wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
aChildClass = RegisterClassEx(&wcex);
hWndMDI = CreateWindow((LPCTSTR)aMainClass,
szTitle,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
NULL,
NULL,
hInst,
NULL);
CLIENTCREATESTRUCT ccs;
ccs.idFirstChild = 5000;
hwndClient = CreateWindow(TEXT("mdiclient"),
NULL,
WS_CLIPCHILDREN | WS_CHILD | WS_HSCROLL | WS_VSCROLL | MDIS_ALLCHILDSTYLES,
0,
0,
0,
0,
hWndMDI,
0,
hInst,
(LPSTR)&ccs);
ShowWindow(hWndMDI, SW_SHOW);
ShowWindow(hwndClient, SW_SHOW);
//so as the initial child's client are fits the D2D swapchain (256*256px)
RECT Rect;
Rect.left = 0;
Rect.top = 0;
Rect.right = 256;
Rect.bottom = 256;
AdjustWindowRect(&Rect,
WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU| WS_SIZEBOX, FALSE);
HWND hWnd = CreateMDIWindow(
(LPCTSTR)aChildClass,
szTitle,
WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU| WS_SIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
Rect.right - Rect.left,
Rect.bottom - Rect.top,
hwndClient,
hInst,
NULL);
ShowWindow(hWnd, SW_SHOW);
//create D2D + D3D + DXGI machinery
D2D1_FACTORY_OPTIONS debugOptions;
debugOptions.debugLevel = D2D1_DEBUG_LEVEL_NONE;
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory1),
&debugOptions,
(void*)&m_D2DFactory
);
if (SUCCEEDED(hr))
{
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&m_pD3D11device,
NULL,
NULL
);
}
if (SUCCEEDED(hr))
{
IDXGIDevice1 dxgiDevice;
hr = m_pD3D11device->QueryInterface(__uuidof(IDXGIDevice), (void*)&dxgiDevice);
if (SUCCEEDED(hr))
{
hr = m_D2DFactory->CreateDevice(
dxgiDevice,
&m_d2dDevice
);
if (SUCCEEDED(hr))
{
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
);
}
}
IDXGIAdapter dxgiAdapter;
if (SUCCEEDED(hr))
{
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
if (SUCCEEDED(hr))
{
hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&m_dxgiFactory));
SafeRelease(&dxgiAdapter);
dxgiDevice->SetMaximumFrameLatency(1);
SafeRelease(&dxgiDevice);
}
}
}
if (SUCCEEDED(hr))
{
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
swapChainDesc.Width = 256;
swapChainDesc.Height = 256;
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH; //DXGI_SCALING_ASPECT_RATIO_STRETCH seems not supported
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.Flags = 0;
hr = m_dxgiFactory->CreateSwapChainForHwnd(
m_pD3D11device,
hWnd,
&swapChainDesc,
nullptr,
nullptr,
&(m_swapChain)
);
}
if (SUCCEEDED(hr))
{
hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_dxgiBackBuffer));
if (SUCCEEDED(hr))
{
D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
0,
0,
NULL
);
/
//just in case dpi=0 doesn't work:
bitmapProperties.dpiX = GetDpiForWindow(hWnd);
bitmapProperties.dpiY = GetDpiForWindow(hWnd);
/
hr = m_d2dContext->CreateBitmapFromDxgiSurface(
m_dxgiBackBuffer,
&bitmapProperties,
&(m_pBitmap)
);
}
}
if (SUCCEEDED(hr))
{
D2D1_SIZE_U bitmapSize = m_pBitmap->GetPixelSize();
m_d2dContext->BeginDraw();
m_d2dContext->SetTarget(m_pBitmap);
ID2D1SolidColorBrush* m_pBlackBrush;
hr = m_d2dContext->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Red, 1.0f),
&m_pBlackBrush
);
m_d2dContext->DrawLine(
D2D1::Point2F(0.0f, 0.0f),
D2D1::Point2F(bitmapSize.height, bitmapSize.width),
m_pBlackBrush,
2.0f,
NULL);
m_d2dContext->DrawLine(
D2D1::Point2F(0.0f, bitmapSize.width),
D2D1::Point2F(bitmapSize.height, 0.0f),
m_pBlackBrush,
2.0f,
NULL);
}
hr = m_d2dContext->EndDraw();
hr = m_swapChain->Present(1, 0);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Analyse les sΓ©lections de menu :
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefFrameProc(hWnd, hwndClient, message, wParam, lParam);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefFrameProc(hWnd, hwndClient, message, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefMDIChildProc(hWnd, message, wParam, lParam);
}
...
Replace your AdjustWindowRect with AdjustWindowRectEx and specify WS_EX_MDICHILD extended window style.
Another thing, youβre rendering and even presenting some graphics from within InitInstance method. Thatβs not the right time for that, too early. Instead, you should move rendering into WM_PAINT handler of the window for which you have created the swap chain.
P.S. This sample might help.
The streching error only occurs for windows with borders and/or menu bar. A possible workaround is thus to add a borderless child window (hWndPic) to the MDI child (hWnd) itself:
//initialization :
hWnd = CreateMDIWindow(
(LPCTSTR)aChildClass,
szTitle,
WS_SIZEBOX|WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU ,
CW_USEDEFAULT,
CW_USEDEFAULT,
Rect.right - Rect.left,
Rect.bottom - Rect.top,
hwndClient,
hInst,
NULL);
hWndPic = CreateWindow(
(LPCTSTR)aPicClass,
NULL,
WS_CHILD,
0,
0,
256,
256,
hWnd,
0,
hInst,
NULL);
Then resize this picture window in response to WM_SIZE sent to the MDI child hWnd; that's all! then there is no need to repaint the picture when the window is resized and the stretch process is correctly handled by D2D, benefitting from hardware acceleration.
// windows procedures :
LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
{
RECT Rect;
GetClientRect(hWnd,&Rect);
SetWindowPos(
hWndPic,
HWND_TOP,
0,
0,
Rect.right - Rect.left,
Rect.bottom - Rect.top,
0
);
}
}
return DefMDIChildProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK PicWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}
https://learn.microsoft.com/ko-kr/windows/win32/direct2d/direct2d-quickstart-with-device-context
By following above, I typed the Direct2D base code which draw a rectangle. But It didn't draw anything. What is the reason?
It is the result after making a empty project and setting sub system to WINDOW in project properties.
#pragma comment(lib, "D2D1.lib")
#pragma comment(lib, "D3D11.lib")
#pragma comment(lib, "DXGI.lib")
#pragma comment(lib, "dxguid.lib")
#include <windows.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <d2d1_1.h>
#include <d3d11.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <wrl.h>
#include <wincodec.h>
using namespace D2D1;
using namespace Microsoft::WRL;
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass = TEXT("DeviceContext Test");
int m_dpi = 96;
ComPtr<ID2D1Factory1> m_d2dFactory;
D3D_FEATURE_LEVEL m_featureLevel;
ComPtr<ID2D1Device> m_d2dDevice;
ComPtr<ID2D1DeviceContext> m_d2dContext;
ComPtr<IDXGISwapChain1> m_swapChain;
ComPtr<ID2D1Bitmap1> m_d2dTargetBitmap;
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
ComPtr<IDXGIDevice1> dxgiDevice;
ComPtr<IDXGIAdapter> dxgiAdapter;
ComPtr<IDXGIFactory2> dxgiFactory;
ComPtr<ID3D11Texture2D> backBuffer;
ComPtr<IDXGISurface> dxgiBackBuffer;
void onInit();
void onPaint();
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
, LPSTR lpszCmdParam, int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst = hInstance;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc = WndProc;
WndClass.lpszClassName = lpszClass;
WndClass.lpszMenuName = NULL;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
//CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
1400, 500, 500, 500,
NULL, (HMENU)NULL, hInstance, NULL);
hWndMain = hWnd;
ShowWindow(hWnd, nCmdShow);
while (GetMessage(&Message, NULL, 0, 0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch (iMessage) {
case WM_CREATE:
hWndMain = hWnd;
onInit();
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
onPaint();
InvalidateRect(hWnd, 0, 0);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
void onPaint()
{
HRESULT res;
m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
auto size = m_d2dTargetBitmap->GetSize();
ComPtr<ID2D1SolidColorBrush> pBlackBrush;
res = m_d2dContext->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
);
m_d2dContext->BeginDraw();
D2D1_RECT_F rc;
rc.left = rc.top = 100;
rc.right = rc.bottom = 200;
m_d2dContext->DrawRectangle(rc, pBlackBrush.Get());
res = m_d2dContext->EndDraw();
RECT rect = { 0, 0, 400, 400 };
DXGI_PRESENT_PARAMETERS parameters;
parameters.DirtyRectsCount = 1;
parameters.pDirtyRects = ▭
parameters.pScrollOffset = 0;
parameters.pScrollRect = 0;
res = m_swapChain->Present1(1, 0, ¶meters);
}
void onInit()
{
D2D1_FACTORY_OPTIONS options;
options.debugLevel = D2D1_DEBUG_LEVEL_ERROR;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), &options, &m_d2dFactory);
// This flag adds support for surfaces with a different color channel ordering than the API default.
// You need it for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
// This array defines the set of DirectX hardware feature levels this app supports.
// The ordering is important and you should preserve it.
// Don't forget to declare your app's minimum required feature level in its
// description. All apps are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
// Create the DX11 API device object, and get a corresponding context.
D3D11CreateDevice(
nullptr, // specify null to use the default adapter
D3D_DRIVER_TYPE_HARDWARE,
0,
creationFlags, // optionally set debug and Direct2D compatibility flags
featureLevels, // list of feature levels this app can support
ARRAYSIZE(featureLevels), // number of possible feature levels
D3D11_SDK_VERSION,
&device, // returns the Direct3D device created
&m_featureLevel, // returns feature level of device created
&context // returns the device immediate context
);
// Obtain the underlying DXGI device of the Direct3D11 device.
device.As(&dxgiDevice);
// Obtain the Direct2D device for 2-D rendering.
m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice);
// Get Direct2D device's corresponding device context object.
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
);
// Allocate a descriptor.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
swapChainDesc.Width = 0; // use automatic sizing
swapChainDesc.Height = 0;
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // use double buffering to enable flip
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
swapChainDesc.Flags = 0;
// Identify the physical adapter (GPU or card) this device is runs on.
dxgiDevice->GetAdapter(&dxgiAdapter);
// Get the factory object that created the DXGI device.
dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
// Get the final swap chain for this window from the DXGI factory.
dxgiFactory->CreateSwapChainForHwnd(
device.Get(),
hWndMain,
&swapChainDesc,
nullptr, // allow on all displays
nullptr,
&m_swapChain
);
// Ensure that DXGI doesn't queue more than one frame at a time.
dxgiDevice->SetMaximumFrameLatency(1);
// Get the backbuffer for this window which is be the final 3D render target.
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
// Now we set up the Direct2D render target bitmap linked to the swapchain.
// Whenever we render to this bitmap, it is directly rendered to the
// swap chain associated with the window.
D2D1_BITMAP_PROPERTIES1 bitmapProperties =
BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
m_dpi,
m_dpi
);
// Direct2D needs the dxgi version of the backbuffer surface pointer.
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));
// Get a D2D surface from the DXGI back buffer to use as the D2D render target.
m_d2dContext->CreateBitmapFromDxgiSurface(
dxgiBackBuffer.Get(),
&bitmapProperties,
&m_d2dTargetBitmap
);
}
I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.
I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.
I also tried a simple code, like this:
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;
pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
const std::wstring text = L"Test π π¬ π π π π π
π";
TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);
hFont = ::CreateFont(-40,
0,
0,
0,
FW_DONTCARE,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
VARIABLE_PITCH,
L"Segoe UI Emoji");
::SelectObject(hDC, hFont);
::DrawTextW(hDC,
text.c_str(),
text.length(),
&textRect,
DT_LEFT | DT_TOP | DT_SINGLELINE);
::DeleteObject(hFont);
The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below:
I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?
EDITED on 30.10.2017
As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.
const std::wstring text = L"Test π π¬ π π π π π
π";
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;
pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
::D2D1_RECT_F textRect;
textRect.left = 10;
textRect.top = 10;
textRect.right = ClientWidth - 10;
textRect.bottom = ClientHeight - 10;
std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));
// configure Direct2D font
pCanvas->Font->Size = 40;
pCanvas->Font->Name = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style = TFontStyles();
// get DirectWrite text format object
_di_IDWriteTextFormat pFormat = pCanvas->Font->Handle;
if (!pFormat)
return;
pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;
::ID2D1SolidColorBrush* pBrush = NULL;
// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &pBrush);
if (!pBrush)
return;
// set horiz alignment
pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
// set vert alignment
pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
// set reading direction
pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
// set word wrapping mode
pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
IDWriteInlineObject* pInlineObject = NULL;
::DWRITE_TRIMMING trimming;
trimming.delimiter = 0;
trimming.delimiterCount = 0;
trimming.granularity = DWRITE_TRIMMING_GRANULARITY_NONE;
// set text trimming
pFormat->SetTrimming(&trimming, pInlineObject);
pCanvas->BeginDraw();
pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
pCanvas->EndDraw();
Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).
Bonus Reading
MSDN: Color Fonts with DirectWrite, Direct2D, and Win2D
GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.
You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC.
Basically, you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result.
In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to set up your text format.
The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you.
Just remember to update the render target/parameters when the window size/position changes.
You may reference this MSDN documentation:
Render to a GDI Surface
https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx
As mentioned by #SoronelHaetir's answer above, the win32 graphics device interface (GDI) that is used when working with static window components (via WC_STATIC) doesn't support colorized fonts. In order to display colored emojis and/or "fancier" text (i.e. colored text, etc.), you'll need to use the Direct2D API.
The example above provided by original poster (OP) #Jean-Milost Reymond isn't something that is immediately able to be compiled and tried out by the reader. Also, it uses the TCanvas class, which isn't strictly needed when working directly with the Win32 API.
For people looking for a complete example that works on the bare-metal Win32 API and can be immediately copied and pasted and compiled, then here is the code that will compile in Visual Studio:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#include <string>
#include <cassert>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "Dwrite.lib")
HWND WindowHandle = nullptr;
IDWriteFactory * DWriteFactory = nullptr;
ID2D1Factory * Direct2dFactory = nullptr;
ID2D1HwndRenderTarget * RenderTarget = nullptr;
ID2D1SolidColorBrush * TextBlackBrush = nullptr;
const std::wstring DISPLAY_TEXT = L"Test π π¬ π π π π π
π";
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease);
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
HRESULT CreateDeviceIndependentResources ();
HRESULT InitInstance (HINSTANCE hInstance, int nCmdShow);
void DiscardDeviceResources ();
HRESULT OnRender ();
HRESULT CreateDeviceResources ();
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release ();
(*ppInterfaceToRelease) = NULL;
}
}
HRESULT OnRender ()
{
HRESULT Result = S_OK;
D2D1_SIZE_F RenderCanvasArea = { 0 };
IDWriteTextFormat * TextFormat = nullptr;
D2D1_RECT_F TextCanvasArea = { 0 };
Result = CreateDeviceResources ();
if (SUCCEEDED (Result))
{
RenderTarget->BeginDraw ();
RenderCanvasArea = RenderTarget->GetSize ();
RenderTarget->Clear (D2D1::ColorF (D2D1::ColorF::White));
if (SUCCEEDED (Result))
{
Result = DWriteFactory->CreateTextFormat (L"Segoe UI",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
25.0f,
L"en-us",
&TextFormat);
TextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING);
TextFormat->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
TextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
TextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP);
if (SUCCEEDED (Result) &&
TextFormat != nullptr)
{
TextCanvasArea = D2D1::RectF (0,
0,
RenderCanvasArea.width,
RenderCanvasArea.height);
RenderTarget->DrawTextW (DISPLAY_TEXT.c_str (),
static_cast <UINT32> (DISPLAY_TEXT.size ()),
TextFormat,
TextCanvasArea,
TextBlackBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
}
Result = RenderTarget->EndDraw ();
}
if (Result == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources ();
Result = S_OK;
}
return Result;
}
HRESULT CreateDeviceResources ()
{
HRESULT Result = S_OK;
RECT rc = { 0 };
if (!RenderTarget)
{
GetClientRect (WindowHandle,
&rc);
D2D1_SIZE_U size = D2D1::SizeU (rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
Result = Direct2dFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties (),
D2D1::HwndRenderTargetProperties (WindowHandle, size),
&RenderTarget);
if (SUCCEEDED (Result))
{
// Create a blue brush.
Result = RenderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
&TextBlackBrush);
}
}
return Result;
}
void DiscardDeviceResources ()
{
SafeRelease (&RenderTarget);
SafeRelease (&TextBlackBrush);
}
HRESULT InitInstance (HINSTANCE hInstance,
int nCmdShow)
{
HRESULT Result = S_OK;
// Create the window.
WindowHandle = CreateWindow (L"D2DTextDemo",
L"Direct2D Text Demo Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
200,
nullptr,
nullptr,
hInstance,
nullptr);
if (WindowHandle == nullptr)
{
Result = E_POINTER;
}
else
{
ShowWindow (WindowHandle,
nCmdShow);
UpdateWindow (WindowHandle);
}
return Result;
}
HRESULT CreateDeviceIndependentResources ()
{
HRESULT Result = S_OK;
Result = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
&Direct2dFactory);
if (SUCCEEDED (Result))
{
Result = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof (IDWriteFactory),
reinterpret_cast <IUnknown **> (&DWriteFactory));
}
return Result;
}
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
LRESULT Result = 0;
switch (message)
{
case WM_SIZE:
{
UINT width = LOWORD (lParam);
UINT height = HIWORD (lParam);
if (RenderTarget != nullptr)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
RenderTarget->Resize (D2D1::SizeU (width,
height));
}
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect (hwnd, nullptr, FALSE);
}
break;
case WM_PAINT:
{
OnRender ();
ValidateRect (hwnd,
nullptr);
}
break;
case WM_DESTROY:
{
PostQuitMessage (0);
Result = 1;
}
break;
default:
{
Result = DefWindowProc (hwnd,
message,
wParam,
lParam);
}
break;
}
return Result;
}
int APIENTRY wWinMain (_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER (hInstance);
UNREFERENCED_PARAMETER (hPrevInstance);
UNREFERENCED_PARAMETER (lpCmdLine);
UNREFERENCED_PARAMETER (nCmdShow);
HRESULT ExitCode = S_OK;
MSG NextMessage = { 0 };
WNDCLASSEX wcex = { 0 };
ATOM WindowClassId = 0;
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof (LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor (nullptr, IDI_APPLICATION);
wcex.lpszClassName = L"D2DTextDemo";
if (SUCCEEDED (CoInitialize (nullptr)))
{
WindowClassId = RegisterClassEx (&wcex);
if (WindowClassId == 0)
{
ExitCode = HRESULT_FROM_WIN32 (GetLastError ());
}
if (SUCCEEDED (ExitCode))
{
ExitCode = CreateDeviceIndependentResources ();
}
if (SUCCEEDED (ExitCode))
{
ExitCode = InitInstance (hInstance,
nCmdShow);
}
if (SUCCEEDED (ExitCode))
{
while (GetMessage (&NextMessage,
nullptr,
0,
0))
{
TranslateMessage (&NextMessage);
DispatchMessage (&NextMessage);
}
}
CoUninitialize ();
SafeRelease (&Direct2dFactory);
SafeRelease (&DWriteFactory);
SafeRelease (&RenderTarget);
}
return ExitCode;
}
(The above example doesn't have perfect error handling, so it's important to audit this example code if the following is used in any project the reader is working on.)
Before attempting to compile this in Visual Studio, make sure your project has the "SubSystem" linker option set to Windows /SUBSYSTEM:WINDOWS.
Once compiled successfully, the following application window will appear:
I tested this coded example in Visual Studio 2022 Community Edition on Windows 11 with success.
Reference(s):
Creating a Simple Direct2D Application
Tutorial: Getting Started with DirectWrite
I've been using the win32 api to make a game with sprites. For some reason when I have more than one sprite on screen they flash occasionally as if they are disappearing and returning. When there is only one sprite on screen it displays correctly.
I am using C++, win32 API and working with Visual Studio 08
The following is roughly what I have:
//creates rect based on window client area
GetClientRect(ghwnd, &screenRect);
// Initialises front buffer device context (window)
frontHDC = GetDC(ghwnd);
// sets up Back DC to be compatible with the front
backHDC = CreateCompatibleDC(frontHDC);
// Create another hdc to store the bitmap in before the backbuffer
bitmapHDC = CreateCompatibleDC(frontHDC);
//creates bitmap compatible with the front buffer
theOldFrontBitMap = CreateCompatibleBitmap(frontHDC, screenRect.right, screenRect.bottom);
//creates bitmap compatible with the back buffer
theOldBackBitMap = (HBITMAP)SelectObject(backHDC, theOldFrontBitMap);
HBITMAP originalBitMap = (HBITMAP)SelectObject(bitmapHDC,bitmap);
//Transparency function
TransparentBlt( backHDC,
m_Position.x,
m_Position.y,
m_Size.x,
m_Size.y,
bitmapHDC,
0,
0,
m_Size.x,
m_Size.y,
0x00FFFFFF);
SelectObject(bitmapHDC,originalBitMap);
BitBlt(frontHDC, screenRect.left, screenRect.top,
screenRect.right, screenRect.bottom, backHDC, 0, 0, SRCCOPY);
Am I doing this correctly? and if so where am I going wrong? If I have not given enough information please tell me and I will rectify that.
The problem with creating a Win32 game is that, even if you use double buffering, you have no way to wait for the vertical retrace of the monitor to display the buffer.
Displaying the buffer or sprite while the vertical retrace is in progress can cause tearing or even the disappearing sprite that you experience.
The only real way around this is to use an SDK like OpenGL or DirectX to manage and display the buffers.
Here's a sample program that may help you, use the arrow keys to move the white box on the double buffered background:
#include <Windows.h>
RECT rcSize;
HDC hdcBackBuffer, hdcSprite;
HBITMAP hbmBackBuffer, hbmSprite;
int spriteX = 175, spriteY = 175;
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static PAINTSTRUCT ps;
switch (msg)
{
case WM_CREATE:
{
HDC hdcWindow = GetDC(hWnd);
// make back buffer
GetClientRect(hWnd, &rcSize);
hdcBackBuffer = CreateCompatibleDC(hdcWindow);
hbmBackBuffer = CreateCompatibleBitmap(hdcBackBuffer, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top);
SelectObject(hdcBackBuffer, hbmBackBuffer); // SHOULD SAVE PREVIOUS...
// make sprite
hdcSprite = CreateCompatibleDC(hdcWindow);
hbmSprite = CreateCompatibleBitmap(hdcSprite, 50, 50);
SelectObject(hdcSprite, hbmSprite); // SHOULD SAVE PREVIOUS...
RECT rcSprite;
SetRect(&rcSprite, 0, 0, 50, 50);
FillRect(hdcSprite, &rcSprite, (HBRUSH)GetStockObject(WHITE_BRUSH));
ReleaseDC(hWnd, hdcWindow);
return 0;
}
case WM_KEYDOWN:
{
// SHOULD REALLY USE GetAsyncKeyState for game, but simplified here
switch (wParam)
{
case VK_LEFT:
spriteX--;
break;
case VK_RIGHT:
spriteX++;
break;
case VK_UP:
spriteY--;
break;
case VK_DOWN:
spriteY++;
break;
}
return 0;
}
case WM_ERASEBKGND:
{
return 1; // INDICATE THAT WE ERASED THE BACKGROUND OURSELVES
}
case WM_PAINT:
{
BeginPaint(hWnd, &ps);
// clear back buffer
FillRect(hdcBackBuffer, &rcSize, (HBRUSH)GetStockObject(BLACK_BRUSH));
// render sprite to back buffer
BitBlt(hdcBackBuffer, spriteX, spriteY, 50, 50, hdcSprite, 0, 0, SRCCOPY);
// render back buffer to screen
BitBlt(ps.hdc, 0, 0, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top, hdcBackBuffer, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
{
// TODO - DESTROY ALL BITMAPS AND DEVICE CONTEXTS
PostQuitMessage(0);
return 0;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
}
int WINAPI WinMain(HINSTANCE hPrevInstance, HINSTANCE hInstance, LPSTR lpCmdLine, int nShowCmd)
{
static TCHAR className[] = TEXT("GameClass");
static TCHAR windowName[] = TEXT("A Game");
WNDCLASSEX wcex;
wcex.cbClsExtra = 0;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.cbWndExtra = 0;
wcex.hbrBackground = NULL;
wcex.hCursor = LoadCursor(hInstance, IDC_ARROW);
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hIconSm = NULL;
wcex.hInstance = hInstance;
wcex.lpfnWndProc = WndProc;
wcex.lpszClassName = className;
wcex.lpszMenuName = NULL;
wcex.style = 0;
if (!RegisterClassEx(&wcex))
return 0;
HWND hWnd = CreateWindow(className, windowName, WS_CAPTION | WS_BORDER | WS_SYSMENU, 0, 0, 400, 400, NULL, NULL, hInstance, NULL);
if (!hWnd)
return 0;
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
MSG msg;
for (;;)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
InvalidateRect(hWnd, NULL, FALSE);
}
return msg.wParam;
}
I think your back buffer implementation is wrong although Im not sure exactly where. Try this implementation of a separate back buffer class. I hope it helps.
Here my back buffer class.
#ifndef BACKBUFFER_H
#define BACKBUFFER_H
#include <Windows.h>
class BackBuffer
{
public:
BackBuffer(HWND hWnd, int width, int height);
~BackBuffer();
HDC getDC();
int width();
int height();
void present();
private:
// Make copy constructor and assignment operator private
// so client cannot copy BackBuffers. We do this because
// this class is not designed to be copied because it
// is not efficient--copying bitmaps is slow (lots of memory).
// In addition, most applications will probably only need one
// BackBuffer anyway.
BackBuffer(const BackBuffer& rhs);
BackBuffer& operator=(const BackBuffer& rhs);
private:
HWND mhWnd;
HDC mhDC;
HBITMAP mhSurface;
HBITMAP mhOldObject;
int mWidth;
int mHeight;
};
#endif //BACKBUFFER_H
Heres the implementation:
BackBuffer::BackBuffer(HWND hWnd, int width, int height)
{
//Save a copy of the main window handle
mhWnd = hWnd;
//Get a handle to the device context associated with
// the window
HDC hWndDC = GetDC(hWnd);
//Save the backbuffer dimensions
mWidth = width;
mHeight = height;
//Create system memory device context that is compatible
//with the window one
mhDC = CreateCompatibleDC(hWndDC);
//Create the backbuffer surface bitmap that is compatible
//with the window device context bitmap format. That is
//the surface we will render onto.
mhSurface = CreateCompatibleBitmap(hWndDC, width, height);
//Done with DC
ReleaseDC(hWnd, hWndDC);
//At this point, the back buffer surface is uninitialized,
//so lets clear it to some non-zero value. Note that it
//needs to be a non-zero. If it is zero then it will mess
//up our sprite blending logic.
//Select the backbuffer bitmap into the DC
mhOldObject = (HBITMAP)SelectObject(mhDC, mhSurface);
//Select a white brush
HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH);
HBRUSH oldBrush = (HBRUSH)SelectObject(mhDC, white);
//Clear the backbuffer rectangle
Rectangle(mhDC, 0, 0, mWidth, mHeight);
//Restore the original brush
SelectObject(mhDC, oldBrush);
}
BackBuffer::~BackBuffer()
{
SelectObject(mhDC, mhOldObject);
DeleteObject(mhSurface);
DeleteDC(mhDC);
}
HDC BackBuffer::getDC()
{
return mhDC;
}
int BackBuffer::width()
{
return mWidth;
}
int BackBuffer::height()
{
return mHeight;
}
void BackBuffer::present()
{
//Get a handle to the device context associated with
//the window
HDC hWndDC = GetDC(mhWnd);
//Copy the backbuffer contents over to the
//window client area
BitBlt(hWndDC, 0, 0, mWidth, mHeight, mhDC, 0, 0, SRCCOPY);
//Free window DC when done
ReleaseDC(mhWnd, hWndDC);
}
Try to work your way through this implementation the comments should help you understand. Hope this helps.
I am trying to make a window that alternates between having an Aero/Glass and a custom rendered frame (by handling WM_NCPAINT) based on a user setting. (Windows Vista).
DwmComposition is enabled. My app comes up with the glass frame, but as soon as I toggle the setting to trigger the custom WM_NCPAINT codepath then toggle back to use DefWindowProc's WM_NCPAINT handling, the native frame is now perpetually stuck in the "Vista Basic" style - it's no longer translucent and the caption buttons look different to the normal Aero/Glass ones.
I've tried just about every way of poking the window from sending SWP_FRAMECHANGED to changing the window style then changing it back, hiding it, etc, but all to no avail. It seems like as soon as I handle WM_NCPAINT for a glass window rather than deferring to DefWindowProc my window is forever "broken".
I found a C#/WPF example on MSDN (code dot msdn dot microsoft dot com slash chrome ) that seemed to indicate that one simply needed to stop handling WM_NCPAINT and the glass would return, but that does not seem to work in my own app.
Is there a way to reset this state cleanly? My code is in C++ and lives here:
http://bengoodger.dreamhosters.com/software/chrome/dwm/
#include <windows.h>
#include <dwmapi.h>
static const wchar_t* kWindowClass = L"BrokenGlassWindow";
static const wchar_t* kWindowTitle =
L"BrokenGlass - Right click client area to toggle frame type.";
static const int kGlassBorderSize = 50;
static const int kNonGlassBorderSize = 40;
static bool g_glass = true;
bool IsGlass() {
BOOL composition_enabled = FALSE;
return DwmIsCompositionEnabled(&composition_enabled) == S_OK &&
composition_enabled && g_glass;
}
void SetIsGlass(bool is_glass) {
g_glass = is_glass;
}
void ToggleGlass(HWND hwnd) {
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM w_param,
LPARAM l_param) {
PAINTSTRUCT ps;
HDC hdc;
RECT wr;
HBRUSH br;
RECT* nccr = NULL;
RECT dirty;
RECT dirty_box;
MARGINS dwmm = {0};
WINDOWPOS* wp = NULL;
switch (message) {
case WM_CREATE:
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &wr);
br = GetSysColorBrush(IsGlass() ? COLOR_APPWORKSPACE : COLOR_WINDOW);
FillRect(hdc, &wr, br);
EndPaint(hwnd, &ps);
break;
case WM_NCPAINT:
if (IsGlass())
return DefWindowProc(hwnd, message, w_param, l_param);
GetWindowRect(hwnd, &wr);
if (!w_param|| w_param == 1) {
dirty = wr;
dirty.left = dirty.top = 0;
} else {
GetRgnBox(reinterpret_cast<HRGN>(w_param), &dirty_box);
if (!IntersectRect(&dirty, &dirty_box, &wr))
return 0;
OffsetRect(&dirty, -wr.left, -wr.top);
}
hdc = GetWindowDC(hwnd);
br = CreateSolidBrush(RGB(255,0,0));
FillRect(hdc, &dirty, br);
DeleteObject(br);
ReleaseDC(hwnd, hdc);
break;
case WM_NCACTIVATE:
// Force paint our non-client area otherwise Windows will paint its own.
RedrawWindow(hwnd, NULL, NULL, RDW_UPDATENOW);
break;
case WM_NCCALCSIZE:
nccr = w_param ? &reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0]
: reinterpret_cast<RECT*>(l_param);
nccr->bottom -= IsGlass() ? kGlassBorderSize : kNonGlassBorderSize;
nccr->right -= IsGlass() ? kGlassBorderSize : kNonGlassBorderSize;
nccr->left += IsGlass() ? kGlassBorderSize : kNonGlassBorderSize;
nccr->top += IsGlass() ? kGlassBorderSize : kNonGlassBorderSize;
return WVR_REDRAW;
case WM_RBUTTONDOWN:
SetIsGlass(!g_glass);
ToggleGlass(hwnd);
break;
case 0x31E: // WM_DWMCOMPOSITIONCHANGED:
ToggleGlass(hwnd);
break;
case 0xAE: // WM_NCUAHDRAWCAPTION:
case 0xAF: // WM_NCUAHDRAWFRAME:
return IsGlass() ? DefWindowProc(hwnd, message, w_param, l_param) : 0;
case WM_WINDOWPOSCHANGED:
dwmm.cxLeftWidth = kGlassBorderSize;
dwmm.cxRightWidth = kGlassBorderSize;
dwmm.cyTopHeight = kGlassBorderSize;
dwmm.cyBottomHeight = kGlassBorderSize;
DwmExtendFrameIntoClientArea(hwnd, &dwmm);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, w_param, l_param);
}
return 0;
}
ATOM RegisterClazz(HINSTANCE instance) {
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.lpszClassName = kWindowClass;
return RegisterClassEx(&wcex);
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int show_command) {
RegisterClazz(instance);
HWND hwnd = CreateWindow(kWindowClass, kWindowTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL,
instance, NULL);
ShowWindow(hwnd, show_command);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
When toggling between Aero/Glass and your custom rendered frame it you can use the following to explicitly control the non-client area rendering policy:
DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED; // DWMNCRP_DISABLED to toggle back
DwmSetWindowAttribute(hwnd,
DWMWA_NCRENDERING_POLICY,
(void*)&policy,
sizeof(DWMNCRENDERINGPOLICY));