GDI+ PenAlignmentInset seems to be different than GDI PS_INSIDEFRAME - c++

I've encountered a strange issue when trying to draw rectangles with GDI+, but I'm not sure if I'm doing something wrong.
The GDI+ pen alignment setting, PenAlignmentInset, seems to not work properly when the pen width is 1.
I've created a minimally reproducible demo below in order to illustrate the point. First, a red GDI rectangle is drawn using PS_INSIDEFRAME, and then a blue GDI+ rectangle is drawn right over it using PenAlignmentInset. When the pen widths are 4 and 2, for example, the GDI+ rectangle completely covers the red GDI rectangle, as you’d expect. Their alignment corresponds. But when the pen width is 1, the GDI+ rectangle’s border placement is incorrect, I believe, and part of the red GDI rectangle is thus visible.
#include <windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
const wchar_t CLASS_NAME[] = L"MyClass";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"GDI+ bug demo", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
// Demonstrating a width of 4 units
{
HPEN hpen = CreatePen(PS_INSIDEFRAME, 4, RGB(255, 0, 0));
HGDIOBJ hgdiobjOldPen = SelectObject(hdc, hpen);
Rectangle(hdc, 50, 50, 150, 150);
SelectObject(hdc, hgdiobjOldPen);
DeleteObject(hpen);
Gdiplus::Graphics gfx(hdc);
Gdiplus::Pen pen(Gdiplus::Color(0, 0, 255), 4);
pen.SetAlignment(Gdiplus::PenAlignmentInset);
gfx.DrawRectangle(&pen, 50, 50, 100, 100);
}
// Demonstrating a width of 2 units
{
HPEN hpen = CreatePen(PS_INSIDEFRAME, 2, RGB(255, 0, 0));
HGDIOBJ hgdiobjOldPen = SelectObject(hdc, hpen);
Rectangle(hdc, 200, 50, 300, 150);
SelectObject(hdc, hgdiobjOldPen);
DeleteObject(hpen);
Gdiplus::Graphics gfx(hdc);
Gdiplus::Pen pen(Gdiplus::Color(0, 0, 255), 2);
pen.SetAlignment(Gdiplus::PenAlignmentInset);
gfx.DrawRectangle(&pen, 200, 50, 100, 100);
}
// Demonstrating a width of 1 unit - PROBLEM !!!!
{
HPEN hpen = CreatePen(PS_INSIDEFRAME, 1, RGB(255, 0, 0));
HGDIOBJ hgdiobjOldPen = SelectObject(hdc, hpen);
Rectangle(hdc, 350, 50, 450, 150);
SelectObject(hdc, hgdiobjOldPen);
DeleteObject(hpen);
Gdiplus::Graphics gfx(hdc);
Gdiplus::Pen pen(Gdiplus::Color(0, 0, 255), 1);
pen.SetAlignment(Gdiplus::PenAlignmentInset);
gfx.DrawRectangle(&pen, 350, 50, 100, 100);
}
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Does anyone know why that might be happening? Thank you for any input.

Related

How to erase the background of a window?

Please another way than using a layered window.
Setting SetLayeredWindowAttributes caused some issues in my GUI, for example, some controls inherit the background color as they contains transparency, same with pictures containing transparency, also there are a lot of other controls loaded on it which makes it hard to work with a layered window.
My goal is to create a GUI with rounded borders, for that, I will load a picture to behave as the background.
SetWindowRgn would not help, as it doesn't produce good edges borders because the pictures are being drawn with rounded corners and anti-aliasing.
Result usingSetLayeredWindowAttributes and SetWindowRgn:
I have tried to set WM_ERASEBKGND to true and inside of WM_PAINT use BitBlt with the rasters SRCCOPY | CAPTUREBLT painting an empty bitmap into the window DC, but the window still contains a background.
Also tried to just paint the image above, but the empty area is painted with the default background color.
The image used: https://i.imgur.com/TTLHoCf.png
I have created a similar ask-for-help topic in the Microsoft forum, the code below was adapted from an answer given by the user Castorix:
#include <windows.h>
#include <tchar.h>
// Gdiplus
#pragma comment( lib, "gdiplus.lib" )
#pragma comment( lib, "Msimg32.lib" )
#include <gdiplus.h>
#include <wingdi.h>
//#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nWidth = 601, nHeight = 301;
#define IDC_BUTTON 11
HBITMAP hBitmap = NULL;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
hInst = hInstance;
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,
};
if (!RegisterClassEx(&wcex))
return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test"), WS_POPUP, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
if (!hWnd)
return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
//WndProc(hWnd, 15, 0, 0);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hWndButton = NULL, hWndStatic = NULL;
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hDC;
BLENDFUNCTION bf{};
int x = 0, y = 0, dx = 0, dy = 0;
switch (message)
{
case WM_CREATE:
{
hWndButton = CreateWindowEx(0, L"Button", L"Click", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE, 50, 60, 60, 32, hWnd, (HMENU)IDC_BUTTON, hInst, NULL);
// https://i.imgur.com/TTLHoCf.png
// Start Gdiplus
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Load the image
Gdiplus::Color Color{ 255, 255, 255 };
hBitmap = NULL;
Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile(L"C:\\MEGAsync\\pic2.png", false);
if (bitmap)
{
bitmap->GetHBITMAP(Color, &hBitmap);
delete bitmap;
}
//hBitmap = (HBITMAP)LoadImage(NULL, L"C:\\MEGAsync\\pic.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
return 0;
}
break;
case WM_COMMAND:
{
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDC_BUTTON:
{
if (wmEvent == BN_CLICKED)
{
Beep(1000, 10);
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_ERASEBKGND:
return 0;
case WM_NCHITTEST:
return HTCAPTION;
case WM_NCRBUTTONDOWN:
{
Sleep(200);
DestroyWindow(hWnd);
}
break;
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &ps);
OutputDebugString(L"WM_PAINT");
if (hBitmap)
{
BITMAP bm;
GetObject(hBitmap, sizeof(bm), &bm);
HDC hDCMem = CreateCompatibleDC(NULL);
HBITMAP hBitmapOld = (HBITMAP)SelectObject(hDCMem, hBitmap);
//SetBkMode(hDC, TRANSPARENT);
//SetBkMode(hDCMem, TRANSPARENT);
//TransparentBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, bm.bmWidth, bm.bmHeight, RGB(192, 0, 192));
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = 1; // 0 - ignore source alpha, AC_SRC_ALPHA (1) - use source alpha
bf.SourceConstantAlpha = 10;
x = 0;
y = 0;
dx = nWidth;
dy = nHeight;
AlphaBlend(hDC, x, y, dx, dy, hDCMem, x, y, dx, dy, bf);
auto err = GetLastError();
//BitBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCCOPY | CAPTUREBLT);
//SelectObject(hDCMem, hBitmapOld);
DeleteDC(hDCMem);
}
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Working into the WM_PAINT message, I tried to draw the picture into the background using AlphaBlend, but it resulted in the window background being drawn with whatever be underneath it:
A layered window with the LWA_COLORKEY attribute is problematic if you want (Windows standard) child controls because there is no perfect color to pick as the transparent color.
However, layered windows have another mode; UpdateLayeredWindow. This function is perfect if you have an image (with alpha transparency) you want to use as the background. Just make sure the bitmap is pre-multiplied 32-bit ARGB before selecting it into the DC.
If for some crazy reason you can't use layered windows, the older option is SetWindowRgn.
The newer option is DirectComposition but I'm not sure if you are forced to set the layered style on the window.

Drawing a semi-transparent rectangle in a transparent window

I found this code in stackoverflow and it really draws a rectangle on a transparent window using a bitmap. But somehow I cannot change the transparency of the rectangle. More precisely, I can, but it gets darker. As if the bitmap itself has a black background. How do I make the rectangle transparent?
void paintRect(HDC hdc, RECT dim, COLORREF penCol, COLORREF brushCol, int opacity)
{
HDC tempHDC = CreateCompatibleDC(hdc);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = dim.right - dim.left;
bitmapInfo.bmiHeader.biHeight = dim.bottom - dim.top;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = (dim.right - dim.left) * (dim.bottom - dim.top) * 4;
HBITMAP hBitmap = CreateDIBSection(tempHDC, &bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0x0);
SelectObject(tempHDC, hBitmap);
SetDCPenColor(tempHDC, RGB(0, 0, 255));
SetDCBrushColor(tempHDC, RGB(0, 0, 255));
FillRect(tempHDC, &dim, CreateSolidBrush(RGB(0, 0, 255)));
BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, 0 };
AlphaBlend(hdc, dim.left, dim.top, dim.right, dim.bottom, tempHDC, dim.left, dim.top, dim.right, dim.bottom, blend);
}
Full code
#include <Windows.h>
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
LRESULT CALLBACK windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
WNDCLASS windowClass {};
windowClass.lpfnWndProc = windowProc;
windowClass.hInstance = hInstance;
windowClass.lpszClassName = L"Keystrokes";
windowClass.style = CS_NOCLOSE;
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
if (!RegisterClass(&windowClass))
{
return 0;
}
HWND hwnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW, L"Keystrokes", L"Keystrokes", WS_POPUP, 0, 0, 148, 140, 0, 0, hInstance, 0);
if (!hwnd)
{
return 0;
}
SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
ShowWindow(hwnd, nCmdShow);
MSG msg {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void paintRect(HDC hdc, RECT dim, COLORREF penCol, COLORREF brushCol, int opacity)
{
HDC tempHDC = CreateCompatibleDC(hdc);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = dim.right - dim.left;
bitmapInfo.bmiHeader.biHeight = dim.bottom - dim.top;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = (dim.right - dim.left) * (dim.bottom - dim.top) * 4;
HBITMAP hBitmap = CreateDIBSection(tempHDC, &bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0x0);
SelectObject(tempHDC, hBitmap);
SetDCPenColor(tempHDC, RGB(0, 0, 255));
SetDCBrushColor(tempHDC, RGB(0, 0, 255));
FillRect(tempHDC, &dim, CreateSolidBrush(RGB(0, 0, 255)));
BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, 0 };
AlphaBlend(hdc, dim.left, dim.top, dim.right, dim.bottom, tempHDC, dim.left, dim.top, dim.right, dim.bottom, blend);
}
LRESULT CALLBACK windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
{
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
break;
case WM_MBUTTONDOWN:
{
PostQuitMessage(0);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
paintRect(hDC, { 0, 0, 48, 48 }, RGB(255, 0, 0), RGB(255, 255, 255), 255);
EndPaint(hwnd, &ps);
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
“As if the bitmap itself has a black background.”
Because your background is set to:
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
Of course you can set it to NULL_BRUSH to make it look transparent:
windowClass.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
But when you move it, you will find that this is not to make the window transparent, but to stop drawing the background:
As #Ben answers here:
You haven't made the window transparent, you have just stopped drawing the background. What you see as the background is just whatever happened to be underneath the window when it was first drawn.
And you need to refer to Layered Windows
You can't add rectangles directly on the window, you should use a layered window, and then add what you need.
Here is a sample that you can refer to it:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szCmdLine, _In_ int iCmdShow)
{
static TCHAR szAppName[] = TEXT("test window");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW, szAppName,
TEXT("the hello program"),
WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,
148,
148,
NULL,
NULL,
hInstance,
NULL);
SetLayeredWindowAttributes(hwnd, 0, 1, LWA_ALPHA);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
VOID FadeRect(RECT* prc, HDC hdc)
{
BOOL fFade = FALSE;
static HWND hwnd;
SIZE size;
POINT ptSrc = { 0, 0 };
BLENDFUNCTION blend;
SystemParametersInfo(SPI_GETSELECTIONFADE, 0, &fFade, 0);
if (!fFade)
return;
if (!hwnd) hwnd = CreateWindowEx(WS_EX_LAYERED |
WS_EX_TRANSPARENT |
WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
L"static", L"static", WS_POPUP | WS_VISIBLE, prc->left,
prc->top, prc->right, prc->bottom, NULL, (HMENU)0, NULL, NULL);
else MoveWindow(hwnd, prc->left, prc->top, prc->right, prc->bottom, TRUE);
RECT rect{ prc->left,prc->top,prc->right,prc->bottom };
size.cx = prc->right - prc->left;
size.cy = prc->bottom - prc->top;
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.AlphaFormat = 0;
blend.SourceConstantAlpha = 150;
UpdateLayeredWindow(hwnd, NULL, NULL, &size, hdc, &ptSrc, 0,
&blend, ULW_ALPHA);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
{
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_MBUTTONDOWN:
{
PostQuitMessage(0);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT rect;
GetClientRect(hwnd, &rect);
MapWindowPoints(hwnd, GetParent(hwnd), (LPPOINT)&rect, 2);
HDC hDC = BeginPaint(hwnd, &ps);
RECT rc{ rect.left,rect.top,rect.left + 48,rect.top + 48 };
FadeRect(&rc, hDC);
EndPaint(hwnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

CreateFont, DeleteObject and GDI handle/memory leak

I am looking at this sample https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createfonta of CreateFont API
It clearly says that after the font been created with CreateFont it should be destroyed by DeleteObject call. DeleteObject(hFont); is only called once. CreateFont is called 3 times. Is this a bug in the MS docs? Shouldn't the old font be stored by retrieving it with SelectObject and set back after new fonts been used?
Yes, two of the created font objects are leaked.
Note that MS sample code is generally poor about error handling and object cleanup (they generally focus on demonstrating whatever is central to the sample - here the CreateFont call - while ignoring or minimizing those issues).
The example in the documentation did cause the leak of the font object
I built a sample as follows:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szCmdLine, _In_ int iCmdShow)
{
static TCHAR szAppName[] = TEXT("hello windows");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
TEXT("the hello program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
RECT rect;
HBRUSH hBrush;
HFONT hFont;
hdc = BeginPaint(hWnd, &ps);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
//The width, when set to 0, will cause the font mapper to choose the closest matching value.
//The font face name will be Impact.
hFont = CreateFont(48, 0, 0, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Impact"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100, 100, 700, 200);
SetTextColor(hdc, RGB(255, 0, 0));
DrawText(hdc, TEXT("Drawing Text with Impact"), -1, &rect, DT_NOCLIP);
//DeleteObject(hFont);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 20, will cause the font mapper to choose a font which, in this case, is stretched.
//The font face name will be Times New Roman. This time nEscapement is at -300 tenths of a degree (-30 degrees)
hFont = CreateFont(36, 20, -300, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Times New Roman"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100, 200, 900, 800);
SetTextColor(hdc, RGB(0, 128, 0));
DrawText(hdc, TEXT("Drawing Text with Times New Roman"), -1, &rect, DT_NOCLIP);
//DeleteObject(hFont);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 10, will cause the font mapper to choose a font which, in this case, is compressed.
//The font face name will be Arial. This time nEscapement is at 250 tenths of a degree (25 degrees)
hFont = CreateFont(36, 10, 250, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH, TEXT("Arial"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 500, 200, 1400, 600);
SetTextColor(hdc, RGB(0, 0, 255));
DrawText(hdc, TEXT("Drawing Text with Arial"), -1, &rect, DT_NOCLIP);
DeleteObject(hFont);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Start the task manager after running the sample, you can see it in details:
Then trigger a WM_PAINT message:
We can find that its GDI Objects has increased by 2, and it will increase every time it is triggered, so this example will cause object leakage.
When we call DeleteObject(hFont); after each use(In line 75 and line 88 of my sample), and repeat the above steps, we will find that GDI Objects will not increase, thus solving the problem of object leakage.

Draw border on top of another application window

I'm need to draw border on top another application's window (the main purpose is to highlight window user chooses from running applications list). I'm trying to draw border on top of native window border, but the border isn't drawn. Here is the code:
HPEN framePen = ::CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
HWND handle = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");
WINDOWPLACEMENT winPlacement;
GetWindowPlacement(handle, &winPlacement);
if (winPlacement.showCmd == SW_SHOWMINIMIZED)
{
ShowWindow(handle, SW_RESTORE);
}
SetWindowPos(handle, HWND_TOP, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(handle);
PAINTSTRUCT ps;
RECT rect = {};
::GetClientRect(handle, &rect);
HDC hdc = ::BeginPaint(handle, &ps);
::SelectObject(hdc, framePen);
::Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
::EndPaint(handle, &ps);
In this example used handle of cmd window, but in fact it doesn't matter.
Could you please tell why border isn't drawn and how to draw it?
Thanks.
You can't draw directly on another window, because the system may refresh the window at any time, overwriting your drawing.
To make your drawing persistent, create a layered window, positioned on top of the other window.
Create the window with WS_EX_LAYERED flag.
Pass a color key to SetLayeredWindowAttributes().
In your WM_PAINT handler, draw the inside of the rectangle with the color key (by using it for the brush). Everything you draw with the color key will become transparent. Draw the border of the rectangle with the desired color (by using it for the pen).
Here is a minimal example to get you started. The frame can be moved around by drag-n-drop.
Note there is no error handling to keep the sample code concise. You should check the return value of each Windows API call.
#include <windows.h>
const COLORREF MY_COLOR_KEY = RGB( 255, 0, 255 );
int APIENTRY wWinMain(
HINSTANCE hInstance, HINSTANCE /*hPrevInst*/, LPWSTR /*lpCmdLine*/, int nCmdShow )
{
WNDCLASSW wc{ sizeof( wc ) };
wc.hInstance = hInstance;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hCursor = LoadCursor( nullptr, IDC_ARROW );
wc.hbrBackground = reinterpret_cast<HBRUSH>( COLOR_BTNFACE + 1 );
wc.lpszClassName = L"MyTransparentFrame";
wc.lpfnWndProc = []( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) -> LRESULT
{
switch( msg )
{
case WM_PAINT:
{
PAINTSTRUCT ps{};
HDC hdc = BeginPaint( hwnd, &ps );
RECT rc{}; GetClientRect( hwnd, &rc );
HPEN hPen = CreatePen( PS_SOLID, 20, GetSysColor( COLOR_HIGHLIGHT ) );
HBRUSH hBrush = CreateSolidBrush( MY_COLOR_KEY );
HGDIOBJ hOldPen = SelectObject( hdc, hPen );
HGDIOBJ hOldBrush = SelectObject( hdc, hBrush );
Rectangle( hdc, rc.left, rc.top, rc.right, rc.bottom );
if( hOldPen )
SelectObject( hdc, hOldPen );
if( hOldBrush )
SelectObject( hdc, hOldBrush );
if( hPen )
DeleteObject( hPen );
if( hBrush )
DeleteObject( hBrush );
EndPaint( hwnd, &ps );
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_NCHITTEST:
return HTCAPTION; // to be able to drag the window around
break;
default:
return DefWindowProcW( hwnd, msg, wp, lp );
}
return 0;
};
RegisterClassW( &wc );
HWND hwnd = CreateWindowExW( WS_EX_LAYERED, wc.lpszClassName, L"", WS_POPUP,
200, 200, 800, 600, nullptr, nullptr, hInstance, nullptr );
SetLayeredWindowAttributes( hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY );
ShowWindow( hwnd, nCmdShow );
MSG msg;
while( GetMessage( &msg, nullptr, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return (int) msg.wParam;
}
Finally I managed to solve the problem with the following code:
const COLORREF MY_COLOR_KEY = RGB(255, 128, 0);
HWND cmdHanlde = NULL;
constexpr unsigned int timerIdWindowUpdate = 1;
constexpr unsigned int timerIdFrameColor = 2;
bool tick = false;
bool minimized = false;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"MyTransparentFrame";
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps{};
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc{}; GetClientRect(hwnd, &rc);
HPEN hPen = CreatePen(PS_SOLID, 5, tick ? RGB(255, 128, 1) : RGB(255, 201, 14));
HBRUSH hBrush = CreateSolidBrush(MY_COLOR_KEY);
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
if (hOldPen)
SelectObject(hdc, hOldPen);
if (hOldBrush)
SelectObject(hdc, hOldBrush);
if (hPen)
DeleteObject(hPen);
if (hBrush)
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
}
break;
case WM_TIMER:
{
if (wp == timerIdWindowUpdate)
{
WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT), };
if (::GetWindowPlacement(cmdHanlde, &windowPlacement))
{
if (windowPlacement.showCmd == SW_SHOWMINIMIZED
|| !IsWindowVisible(cmdHanlde))
{
ShowWindow(hwnd, SW_HIDE);
minimized = true;
}
else
{
RECT rect = {};
DwmGetWindowAttribute(cmdHanlde, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
MONITORINFO monInfo;
monInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(MonitorFromWindow(cmdHanlde, MONITOR_DEFAULTTONEAREST), &monInfo);
if (cmdHanlde != NULL && ::IsZoomed(cmdHanlde))
{
rect.left = monInfo.rcWork.left;
rect.top = monInfo.rcWork.top;
rect.bottom = monInfo.rcWork.bottom > rect.bottom ? rect.bottom : monInfo.rcWork.bottom;
rect.right = monInfo.rcWork.right > rect.right ? rect.right : monInfo.rcWork.right;
}
if (minimized)
{
::SetWindowPos(hwnd, cmdHanlde, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
minimized = false;
}
else
{
::SetWindowPos(cmdHanlde, hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
::SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_SHOWWINDOW);
}
}
}
}
else if (wp == timerIdFrameColor)
{
tick = !tick;
::RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE);
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
return 0;
};
RegisterClassEx(&wc);
HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT, wc.lpszClassName, L"", WS_POPUP | WS_VISIBLE | WS_DISABLED,
0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
::SetTimer(hwnd, timerIdWindowUpdate, 50, NULL);
::SetTimer(hwnd, timerIdFrameColor, 500, NULL);
SetLayeredWindowAttributes(hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY);
ShowWindow(hwnd, SW_SHOW);
cmdHanlde = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
May be it is not the best solution, but it works for me. Could you please take a look on it and tell if there something to improve?

Transparency in GDI DCs

I have the "simple" goal of drawing a bitmap with some transparency around it on the screen. That bit wasn't so hard:
#include <windows.h>
#include "BBKG.h"
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static int wH = 156;
static int wW = 166;
HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
HDC mem0, mem1;
HBITMAP hbmMask;
BITMAP bm;
GetObject(hbmColour, sizeof(BITMAP), &bm);
hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
mem0 = CreateCompatibleDC(0);
mem1 = CreateCompatibleDC(0);
SelectObject(mem0, hbmColour);
SelectObject(mem1, hbmMask);
SetBkColor(mem0, crTransparent);
BitBlt(mem1, 0, 0, bm.bmWidth, bm.bmHeight, mem0, 0, 0, SRCCOPY);
BitBlt(mem0, 0, 0, bm.bmWidth, bm.bmHeight, mem1, 0, 0, SRCINVERT);
DeleteDC(mem0);
DeleteDC(mem1);
return hbmMask;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
hInst = hInstance;
MSG msg;
HWND hwnd;
WNDCLASSW wc;
wc.style = 0;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.lpszClassName = L"nope";
wc.hInstance = hInst;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"",
WS_VISIBLE | WS_POPUP| WS_EX_TRANSPARENT,
100, 100, wW, wH, NULL, NULL, hInst, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0)) {
//Workaround for focusables stealing my Esc key
if (msg.message == WM_KEYDOWN){
if (msg.wParam == VK_ESCAPE) {
SendMessage(hwnd, WM_CLOSE, 0, 0);
}
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
static int px;
static int py;
static HBITMAP bhbm;
static RECT nRect = { 0, 0, wW, wH };
switch (msg)
{
case WM_CREATE:
{
HWND bb = CreateWindowW(L"STATIC", L"",
WS_VISIBLE | WS_CHILD ,
0, 0, wW, wH,
hwnd, (HMENU)11, hInst, NULL);
//SetTimer(hwnd, 1, 80, NULL);
return 0;
}
case WM_PAINT: {
//Vars
RECT wRect;
if (GetUpdateRect(hwnd, &wRect, FALSE) == 0) {
return 0; //Nothing to paint
}
PAINTSTRUCT gps;
PAINTSTRUCT ps;
BeginPaint(hwnd, &gps);
HWND bb = GetDlgItem(hwnd, 11);
HDC bbhdc = BeginPaint(bb, &ps);
HDC mdc = CreateCompatibleDC(bbhdc);
//Load Image
BITMAP pBM;
HBITMAP pHBM = (HBITMAP)LoadImage(NULL, L"twi00.bmp", 0, 0, 0, LR_LOADFROMFILE);
HBITMAP pMBM = CreateBitmapMask((HBITMAP)pHBM, 0x00000000);
GetObject(pHBM, sizeof(pBM), &pBM);
//Paint
HBITMAP oldBM = (HBITMAP)SelectObject(mdc, pMBM);
BitBlt(bbhdc, 0, 0, pBM.bmWidth, pBM.bmHeight, mdc, 0, 0, SRCAND);
SelectObject(mdc, pHBM);
BitBlt(bbhdc, 0, 0, pBM.bmWidth, pBM.bmHeight, mdc, 0, 0, SRCPAINT);
//Cleanup
SelectObject(mdc, oldBM);
DeleteObject(pHBM);
DeleteObject(pMBM);
DeleteDC(mdc);
EndPaint(bb, &ps);
EndPaint(hwnd, &gps);
return 1;
}
case WM_ERASEBKGND: {
return 0;
}
case WM_DESTROY:
{
DeleteObject(bhbm);
PostQuitMessage(0);
return 0;
}
case WM_LBUTTONDOWN:
SetCapture(hwnd);
px = LOWORD(lParam);
py = HIWORD(lParam);
return 1;
case WM_LBUTTONUP:
{
ReleaseCapture();
return 1;
}
case WM_MOUSEMOVE:
{
if (GetCapture() == hwnd)
{
RECT rcWindow;
GetWindowRect(hwnd, &rcWindow);
SetWindowPos(hwnd, NULL, rcWindow.left + LOWORD(lParam) - px, rcWindow.top + HIWORD(lParam) - py, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
break;
}
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
Use any generic bmp with a black border will do, I used this:
Now the question is, how can I make it so that when I move the window (click/drag) the background updates? I was hoping for something like putting the bitmap into a transparent window so that it's overlayed on top of things but it seems to just grab the pixels of what ever is behind it.
I'm attempting to do this without GDI+ or other libraries, if possible.
CreateWindow() does not accept extended window styles, such as WS_EX_TRANSPARENT (which is why it has EX in its name). You have to use CreateWindowEx() instead:
hwnd = CreateWindowExW(WS_EX_TRANSPARENT,
wc.lpszClassName, L"",
WS_VISIBLE | WS_POPUP,
100, 100, wW, wH, NULL, NULL, hInst, NULL);
A better option is to create a layered window (see also this) by using the WS_EX_LAYERED extended style). Then you can use the UpdateLayeredWindow() function to provide the window with the bitmap and the transparent color (you can also specify alpha as well). Let the window manage all of the hard work of drawing the bitmap transparently for you.
Your WndProc() can also respond to the WM_NCHITTEST message to tell the OS that all clicks on the window should be treated as if the user were clicking on the window's titlebar. Let the window handle the mouse tracking and auto-positioning for you.
Try something more like this:
#include <windows.h>
HINSTANCE hInst;
static int wH = 156;
static int wW = 166;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
hInst = hInstance;
WNDCLASSW wc = {0};
wc.lpszClassName = L"nope";
wc.hInstance = hInst;
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassW(&wc);
HWND hwnd = CreateWindowEx(WS_EX_LAYERED,
wc.lpszClassName, L"",
WS_POPUP, 100, 100, wW, wH, NULL, NULL,
hInst, NULL);
HBITMAP hBmp = (HBITMAP) LoadImage(NULL, L"twi00.bmp", 0, 0, 0, LR_LOADFROMFILE);
HDC hdcScreen = GetDC(0);
HDC hdcBmp = CreateCompatibleDC(hdcScreen);
HBITMAP oldBM = (HBITMAP) SelectObject(hdcBmp, hBmp);
POINT pt = {0};
UpdateLayeredWindow(hwnd,
hdcScreen,
NULL, NULL,
hdcBmp, &pt,
RGB(0, 0, 0), // black
NULL, ULW_COLORKEY
);
SelectObject(hdcBmp, oldBM);
DeleteDC(hdcBmp);
ReleaseDC(0, hdcScreen);
DeleteObject(hBmp);
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
//Workaround for focusables stealing my Esc key
if ((msg.message == WM_KEYDOWN) && (msg.wParam == VK_ESCAPE) {
SendMessage(hwnd, WM_CLOSE, 0, 0);
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
case WM_NCHITTEST:
{
return HTCAPTION;
}
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}