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
);
}
Related
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
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 have a Win32 application with a static control that I want to render to using OpenGL. The problem is when I run the application, my computer comes to a screeching halt and more than once now I've had to hard-reboot it. It takes less than a second and it's really brutal. Most of my code here is ripped off of the internet or from my OpenGL bluebook. I have tried doing this several other ways, some with the same results and some with different but no more desirable results, including making the static control owner-draw, subclassing the static control, and just rendering to the control in it's parent's (the main window's) WM_PAINT message. I'm not even rendering any geometry yet, I just want to see the clear color. Most if not all of the resources I've found online either don't cover rendering to a control or using MFC instead of native Win32 API calls.
I have determined that it only happens because I am calling glClear(). If I don't call it, I don't experience the slow down, but I also don't see anything in my static control.
Some more information:
Visual Studio 2010 Ultimate
Windows SDK v7.0a
GLEW 1.7
nVidia Quadro 1600M with the latest drivers
Windows 7 x64 Ultimate
Here is my code:
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define GLEW_STATIC
#include <windows.h>
#include <gl\glew.h>
#include <gl\wglew.h>
#include <Uxtheme.h>
#include <commctrl.h>
#include <tchar.h>
#include "resource.h"
//#define WIN32_LEAN_AND_MEAN
#define IDC_THE_BUTTON 9001
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
struct GlobalSection
{
HINSTANCE app;
HWND wnd;
HWND button;
HWND panel;
HWND text;
HGLRC glrc;
HDC fdc;
} g;
bool initWindow(int cmdShow);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
void Init(HWND wnd);
void Render();
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nCmdShow)
{
g.app = hInst;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(iccex);
iccex.dwICC = ICC_BAR_CLASSES|ICC_COOL_CLASSES|ICC_LISTVIEW_CLASSES|ICC_PROGRESS_CLASS|ICC_STANDARD_CLASSES|ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);
initWindow(nCmdShow);
MSG m;
ZeroMemory(&m, sizeof(m));
while(m.message != WM_QUIT)
{
Render();
if(PeekMessage(&m, NULL, 0, 0, PM_REMOVE) && !IsDialogMessage(g.wnd, &m))
{
TranslateMessage(&m);
DispatchMessage(&m);
}
}
}
bool initWindow(int cmdShow)
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.lpszClassName = _T("Window Party");
wc.hInstance = g.app;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.hIcon = LoadIcon(g.app, MAKEINTRESOURCE(IDI_ICON2));
wc.hIconSm = wc.hIcon;
RegisterClassEx(&wc);
g.wnd = CreateWindowEx(WS_EX_CLIENTEDGE, _T("Window Party"), _T("Window Party"), WS_SYSMENU|WS_CLIPCHILDREN|WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 800, NULL, NULL, g.app, NULL);
ShowWindow( g.wnd, cmdShow ); // technically should be nCmdShow
return true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch(message)
{
case WM_CREATE:
Init(hwnd);
return DefWindowProc(hwnd, message, wparam, lparam);
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wparam);
int wmEvent = HIWORD(wparam);
switch(wmId)
{
case IDM_FILE_CLOSE:
PostQuitMessage(0);
break;
case IDOK:
case IDC_THE_BUTTON:
{
MessageBox(hwnd, _T("You have pressed the button!"), _T("Congraturation"), MB_OK);
}
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
}
break;
case WM_KEYDOWN:
{
TCHAR buff[64] = {0};
wsprintf(buff, _T("Key Pressed: 0x%X"), (unsigned)wparam);
MessageBox(hwnd, buff, _T("YOYOYO"), MB_OK);
break;
}
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
return 0;
}
void Init(HWND wnd)
{
//Creating controls
RECT cr;
GetClientRect(wnd, &cr);
g.panel = CreateWindow(_T("STATIC"), NULL, WS_CHILDWINDOW|WS_VISIBLE|CS_OWNDC|CS_VREDRAW|CS_HREDRAW, 0, 0, cr.right, cr.bottom-26, wnd, NULL, g.app, NULL);
g.button = CreateWindow(_T("BUTTON"), _T("The Button"), WS_CHILDWINDOW|BS_DEFPUSHBUTTON|WS_VISIBLE, cr.right-160, cr.bottom-26, 160, 26, wnd, (HMENU)IDC_THE_BUTTON, g.app, NULL);
g.text = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"), NULL, WS_CHILDWINDOW|ES_LEFT|WS_VISIBLE, 0, cr.bottom-26, cr.right-160, 26, wnd, NULL, g.app, NULL);
//Setting fonts
HFONT hf = NULL;
hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
SendMessage(g.button, WM_SETFONT, (WPARAM)hf, TRUE);
SendMessage(g.text, WM_SETFONT, (WPARAM)hf, TRUE);
//Creating wgl context
g.fdc = GetDC(g.panel);
PIXELFORMATDESCRIPTOR pfd = {0};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 16;
pfd.cStencilBits = 8;
SetPixelFormat(g.fdc, 1, &pfd);
g.glrc = wglCreateContext(g.fdc);
wglMakeCurrent(g.fdc, g.glrc);
RECT pcr;
GetClientRect(g.panel, &pcr);
glViewport(0, 0, pcr.right, pcr.bottom);
glewInit();
glClearColor(0.0, 0.0, 0.0, 1.0);
UpdateWindow(wnd);
}
void Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SwapBuffers(g.fdc);
}
You must not call Render() for every single window message! Only call Render() for WM_PAINT messages.
This sounds like a memory leak somewhere. The computer will come to a screeching stop if the PF usage gets too high.
Open up your task manager and look at the PF Usage before and then again during running the program. If it jumps up .5GB or more and keeps climbing then you've got a memory leak on your hands.
If it behaves in the manner described above the most likely culprit is that you are either making a new object (or texture) multiple times causing Windows to need to allocate more PF space for you, or you could have your VSYNC turned off.
I probably don't have to tell you what happens with a memory leak, but with the VSYNC turned off the program is going to try to run as fast as the CPU lets it. It will max out your CPU and it will do it fast.
Hope this helps.
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'm trying to get an openfile dialog to show up on windows CE 6.0 according to msdn it's the same process as in win32, but it doesn't work. I submit for review the interresting part of the code :
#include <windows.h>
#include <commctrl.h>
#include <winuser.h>
#include <stdio.h>
#include <Commdlg.h>
/* Prototypes */
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND create_window(int width, int height) ;
void resize_window(int width, int height) ;
HWND g_hWindow ;
/* Function to modify the host window size to match the size of the movie. */
void resize_window(int width, int height)
{
RECT r;
r.left = r.top = 0;
r.right = width ;
r.bottom = height ;
AdjustWindowRectEx(&r,WS_BORDER,false,WS_EX_CLIENTEDGE);
SetWindowPos(g_hWindow, NULL, 0,0,r.right, r.bottom,SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
}
HWND create_window(int width, int height)
{
WNDCLASS wce; // A Window class in Windows CE
wce.style = CS_VREDRAW | CS_HREDRAW;
wce.lpfnWndProc = (WNDPROC) WndProc;
wce.cbClsExtra = 0;
wce.cbWndExtra = 0;
wce.hInstance = GetModuleHandle(NULL);
wce.hIcon = LoadIcon((HINSTANCE) NULL, (LPCWSTR)MB_ICONQUESTION);
wce.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wce.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wce.lpszMenuName = 0;
wce.lpszClassName = _TEXT("TEST");
if (!RegisterClass(&wce)) return 0;
RECT r;
r.left = r.top = 0;
r.right = width ;
r.bottom = height ;
AdjustWindowRectEx(&r,WS_BORDER,false,WS_EX_CLIENTEDGE);
// Create the window
g_hWindow = CreateWindowEx(
0, // Ex Styles
WS_BORDER, // creates a window that has a thin-line border with no title bar
CW_USEDEFAULT, // x
CW_USEDEFAULT, // y
r.right-r.left, // Height
r.bottom-r.top, // Width
NULL, // Parent Window
NULL, // Menu, or windows id if child
GetModuleHandle(NULL), //
NULL // Pointer to window specific data
);
ShowWindow( g_hWindow, SW_SHOW ); // make the window visible on the display
return g_hWindow ;
}
/*
* Messaging function call back from Windows CE that is passed as
* an argument when the window is created
*/
LRESULT CALLBACK WndProc(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam )
{
switch( msg )
{
case WM_ACTIVATEAPP:
// Invalidate to get new text painted.
this->Invalidate();
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CREATE:
LPOPENFILENAME opendialog;
wchar_t szFile[260];
opendialog = (LPOPENFILENAME) malloc(sizeof (LPOPENFILENAME));
opendialog->lStructSize = sizeof (LPOPENFILENAME);
opendialog->hwndOwner = g_hWindow;
opendialog->hInstance = GetModuleHandle(NULL);
*szFile = (char_t)_TEXT("\0");
opendialog->lpstrFile = szFile;
opendialog->nFilterIndex = 0;
opendialog->nMaxFile = 256;
opendialog->lpstrInitialDir = NULL;
opendialog->Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
GetOpenFileName(opendialog);
default:
return DefWindowProc( hWnd, msg, wParam, lParam);
}
return 0;
}
/* Function to hide (or make visible) the taskbar so that the player has the full display */
void set_display(bool set_full)
{
HWND hwndTaskbar = ::FindWindow(L"HHTaskBar", NULL);
if (set_full)
{
::ShowWindow(hwndTaskbar, SW_HIDE);
}
else
{
::ShowWindow(hwndTaskbar, SW_SHOW);
}
g_full_scrn = set_full;
}
int _cdecl WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR argv, int argc )
{
memset(&g_gfx_context,0,sizeof(g_gfx_context));
create_window(1, 1); // This is a windows CE specific call.
}
LPOPENFILENAME opendialog;
mb_char_t szFile[260];
opendialog = (LPOPENFILENAME) mb_malloc(sizeof (LPOPENFILENAME));
opendialog->lStructSize = sizeof (LPOPENFILENAME);
You are declaring a pointer to an OPENFILENAME structure here, and allocating memory for it.
You should be able to allocate OPENFILENAME on stack directly, like so:
OPENFILENAME opendialog = {0};
mb_char_t szFile[260] = {0};
opendialog.lStructSize = sizeof (opendialog);
opendialog.hwndOwner = g_hWindow;
opendialog.hInstance = GetModuleHandle(NULL);
opendialog.lpstrFile = szFile;
opendialog.nFilterIndex = 0;
opendialog.nMaxFile = 256;
opendialog.lpstrInitialDir = NULL;
opendialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
GetOpenFileName(&opendialog);
Here's a problem:
opendialog->lStructSize = sizeof (LPOPENFILENAME);
That will set the struct size to the size of a pointer, which is 4 in your case. You should have:
opendialog->lStructSize = sizeof (OPENFILENAME);