I need to draw a custom caption bar, where I draw the window caption by myself.
HDC hdc = GetWindowDC(hwnd);
if (hdc && prepareTitleBarDC(getWidth(), 27)) {
SetWindowText(hwnd, _T(""));
DefWindowProc(hwnd, WM_NCPAINT, wParam, lParam);
m_titleBar->setSize(getWidth(), 27);
m_titleBar->setBkColor(SkColorSetARGB(0x00, 0x00, 0x00, 0x00));
m_titleBar->paintEvent(m_pTitleBarDC);
FnSkBitmap::SaveSkBitmap(m_pTitleBarDC->canvas(), L"e:\\titlebar.bmp");
HDC hdcPaint = CreateCompatibleDC(hdc);
HBITMAP hbm = CreateCompatibleBitmap(hdc, getWidth(), 27);
SelectObject(hdcPaint, hbm);
FnSkBitmap::DrawSkBitmap(m_pTitleBarDC->bitmap(), hdcPaint);
BLENDFUNCTION bfn = {0};
bfn.BlendOp = AC_SRC_OVER;
bfn.BlendFlags = 0;
bfn.SourceConstantAlpha = 255;
bfn.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, getWidth(), 27, hdcPaint, 0, 0, getWidth(), 27, bfn);
}
ReleaseDC(hwnd, hdc);
return 0;
And use AlphaBlend to mix the standard frame with myself, but if I use SetWindowText(_T("")), then the title in Alt+Tab switcher gone.
I try to handle WM_GETTEXT message and return the caption string, but failed. How could I draw the caption text by myself but still make the title in alt+tab switcher?
Since you are already drawing a "custom caption bar" there is no reason to have it actually draw using the actual window's text
there are two ways to accomplish this, one using the traditional DrawCaption from Win9x Win32Api, the other is using the more fresher "theme api"
here is an example that uses both:
#include <Windows.h>
#include <Uxtheme.h>
#include <vssym32.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
const LPCWSTR WINDOW_CLASS = L"Test Window Class";
const LPCWSTR WINDOW_CAPTION = L"This is my test window";
const LPCWSTR CUSTOM_CAPTION = L"Custom Caption Text";
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
WNDCLASSEX wndClassEx = {};
wndClassEx.lpszClassName = WINDOW_CLASS;
wndClassEx.hInstance = hInstance;
wndClassEx.lpfnWndProc = WindowProc;
wndClassEx.cbSize = sizeof(wndClassEx);
wndClassEx.hCursor = (HCURSOR) LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
wndClassEx.style = CS_DBLCLKS | CS_DROPSHADOW;
ATOM registeredClass = RegisterClassEx(&wndClassEx);
HWND hwnd = CreateWindowEx(
0,
WINDOW_CLASS,
WINDOW_CAPTION,
WS_SYSMENU,
200, 200, 500, 300,
NULL, // parent
NULL, // menu
hInstance,
NULL // extra
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDBLCLK:
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// fill the window with a color
HBRUSH hbrush = CreateSolidBrush(RGB(33, 33, 33));
FillRect(hdc, &ps.rcPaint, hbrush);
DeleteObject(hbrush);
// get a drawing area
RECT rect = {};
GetClientRect(hwnd, &rect);
rect.bottom = rect.top + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYEDGE) * 2;
// draw a simple win9x style caption (switch out the window text while drawing)
SetWindowText(hwnd, CUSTOM_CAPTION);
DrawCaption(hwnd, hdc, &rect, DC_GRADIENT | DC_TEXT | DC_ACTIVE | DC_ICON);
SetWindowText(hwnd, WINDOW_CAPTION);
// use theme framework
HTHEME htheme = OpenThemeData(hwnd, L"Window");
// move downwards and then use new APIs for size
rect.top += rect.bottom + 20;
rect.bottom = rect.top + GetThemeSysSize(htheme, SM_CYSIZE) + GetThemeSysSize(htheme, SM_CXPADDEDBORDER) * 2;
// draw the background
DrawThemeBackground(htheme, hdc, WP_CAPTION, CS_ACTIVE, &rect, &ps.rcPaint);
// load the caption font and save the old one
LOGFONTW captionfont = {};
GetThemeSysFont(htheme, TMT_CAPTIONFONT, &captionfont);
HFONT newfont = CreateFontIndirect(&captionfont);
HGDIOBJ oldfont = SelectObject(hdc, newfont);
// center the font and draw
rect.top += GetThemeSysSize(htheme, SM_CXPADDEDBORDER);
DrawThemeTextEx(htheme, hdc, WP_CAPTION, CS_ACTIVE, CUSTOM_CAPTION, -1, DT_CENTER, &rect, NULL);
// cleanup fonts
SelectObject(hdc, oldfont);
DeleteObject(newfont);
// adjust draw location, load icon and draw
rect.left += GetThemeSysSize(htheme, SM_CXPADDEDBORDER) * 2;
rect.top += GetThemeSysSize(htheme, SM_CXPADDEDBORDER);
HICON icon = (HICON) LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
DrawIconEx(hdc, rect.left, rect.top, icon, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0, NULL, DI_NORMAL);
// close theme
CloseThemeData(htheme);
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
the resulting window looks like:
You can see that the 2 "custom drawn" title bars display custom text, not the text on the window.
A quick glance at the code will tell you that attempting the theme window caption using a custom routine is a lot more difficult than the legacy. The trade off of course is that it gives you way more control. You'll also take note that I switch out the window text to make it display what I want when using the legacy method. Additionally you need to remember that the legacy method takes its queues on how to draw itself from the styles associated with the window, if your window style has no icon, it will not draw one even if you specify it...
Either of these techniques will accomplish your goal. If I switched this code around not to draw multiple title bars and get rid of the default one created by the window style the result would look like:
you can see here how the task switch still displays the actual window text, and my "custom" caption bar looks like the real deal...
good luck, i hope this helps -ck
on a side note: i am running Windows8 and am not sure why the caption drawing routines are ignoring my theme... perhaps i forgot a directive
Related
I need to get the client rect offsets of a window relative to the non-client rect of that window.
I have tried GetClientRect but this only gets the client rect relative to the client rect itself, meaning: rect.left = 0, rect.top = 0, rect.right = clientWidth and rect.bottom = clientHeight.
So, how to do it?
Thanks in advance.
It sounds like you want the non-client offsets from the edges of the Window into the client rectangle. That is, the left and top internal leading pixels along with the right and bottom trailing pixels. Within these offsets (sizes), you could have the title bar, menu bar and edges that adorn your client rectangle if you were doing this for a top-level window such as a dialog. Windows doesn't calculate this for you, but it provides everything you need to calculate it yourself. Here is some sample code that you might use to perform this calculation from a given window handle:
RECT rcNonCli = {0,0,0,0}; //calculate non-client offsets here
RECT rcWin = {0,0,0,0};
RECT rcClient = {0,0};
POINT ptClient = {0,0};
if (GetWindowRect(hwnd,&rcWin) && GetClientRect(hwnd,&rcClient) && ClientToScreen(hwnd,&ptClient))
{
rcNonCli.left = ptClient.x - rcWin.left;
rcNonCli.top = ptClient.y - rcWin.top;
rcNonCli.right = rcWin.right - ptClient.x - rcClient.right;
rcNonCli.bottom = rcWin.bottom - ptClient.y - rcClient.bottom;
printf("nonclient offsets={%d,%d,%d,%d}\n",rcNonCli.left,rcNonCli.top,rcNonCli.right,rcNonCli.bottom);
}
EXPLANATION:
ClientToScreen() gives us the beginning coordinate of the client area providing the top and left offsets. Once we know this, we can subtract from the complete Window size (from GetWindowRect()), the client size (from GetClientRect()) and the non-client top and left offsets to give us what remains which would be the bottom and right offsets.
As Jonathan Potter commented, I recommend you to use MapWindowPoints.
You can pass the rect obtained by GetClientRect into MapWindowPoints, and set the cPoints parameter to 2, to get the coordinates relative to the window you want.
Here is the sample:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, 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)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawTextW(hdc, (L"Hello,Windows"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&rect, 2);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
You can observe the result of rect after calling the MapWindowPoints. It converts the original coordinates to the coordinates relative to the desktop.
By default a rich edit control has a "3d" border. I draw a thin border around a rich edit control this way:
if (message == WM_NCPAINT)
{
RECT rc;
HDC hdc;
HPEN pen;
HBRUSH brush;
HGDIOBJ oldP, oldB;
POINT tl, br;
::GetWindowRect(hWnd, &rc);
hdc = ::GetDC(hWnd);
tl.x = rc.left;
tl.y = rc.top;
br.x = rc.right;
br.y = rc.bottom;
::ScreenToClient(hWnd, &tl);
::ScreenToClient(hWnd, &br);
pen = ::CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
brush = (HBRUSH) ::GetStockObject(HOLLOW_BRUSH);
oldP = ::SelectObject(hdc, pen);
oldB = ::SelectObject(hdc, brush);
::Rectangle(hdc, tl.x, tl.y, br.x, br.y);
::SelectObject(hdc, oldP);
::SelectObject(hdc, oldB);
::DeleteObject(pen);
::ReleaseDC(hWnd, hdc);
return 0;
}
The border looks fine but the area under the old border is not redrawn. It looks like I have to redraw to whole content of the rich edit control. After that a text shouldn't be a litte bit cut from the bottom. Here you can see what I mean (the second rich edit control has custom border). How to achieve it ?
GetDC returns DC for client area. In this case you need GetWindowDC for the whole richedit window.
Either way, overriding border color in edit and rich-edit can be difficult, because you also have to handle scrollbar painting in WM_NCPAINT
Assuming you don't need vertical and/or horizontal scrollbar, use GetWindowDC as follows:
LRESULT CALLBACK RichEditProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp,
UINT_PTR, DWORD_PTR)
{
static int border_thickness = 1;
switch(msg)
{
case WM_NCPAINT:
{
HDC hdc = GetWindowDC(hwnd);
RECT rc;
GetClientRect(hwnd, &rc);
rc.right += 2 * border_thickness + 1;
rc.bottom += 2 * border_thickness + 1;
HBRUSH hbrush = (HBRUSH)GetStockObject(NULL_BRUSH);
HPEN hpen = CreatePen(PS_SOLID, 2 * border_thickness, RGB(255, 0, 0));
HBRUSH oldbrush = (HBRUSH)SelectObject(hdc, hbrush);
HPEN oldpen = (HPEN)SelectObject(hdc, hpen);
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
SelectObject(hdc, oldpen);
SelectObject(hdc, oldbrush);
DeleteObject(hpen);
DeleteObject(hbrush);
ReleaseDC(hwnd, hdc);
return 0;
}
case WM_NCCALCSIZE:
if(lp)
{
NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lp;
InflateRect(&sz->rgrc[0], -border_thickness, -border_thickness);
return 0;
}
break;
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, RichEditProc, 0);
break;
}
return DefSubclassProc(hwnd, msg, wp, lp);
}
By default, WM_NCCALCSIZE is not called in subclass procedure, you have to call SetWindowPos with SWP_FRAMECHANGED
HWND hrichedit = CreateWindowEx(...);
SetWindowSubclass(hrichedit, RichEditProc, 0, 0);
SetWindowPos(hrichedit, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
If you do need horizontal/vertical scrollbar, you might consider using a borderless richedit, then paint around the rich control in parent window's WM_PAINT procedure.
On Windows, I'm trying to use CEF (Chromium Embedded Framework) to create a window application with parent window to be transparent and its child window to be opaque (I want to have a rounded corner and an arrow pointing to the status bar in the child window). Something similar to:
I tried to use SetLayeredWindowAttributes to make the parent window transparent but it also makes the child window transparent. Is there a way to make this happen on Windows?
SetLayeredWindowAttributes needs a color for transparency. Make sure the transparency color is not used by the child window. You can pick a random color, for example RGB(255, 0, 254) and assume the child window is not using it.
If you have no control over the child window, and you can't be sure what colors it might use, then SetWindowRgn is another option to create non-rectangular windows.
The example below shows how to set the region such that the corners are round with a triangle on top.
You can use GDI+ to gain more flexibility for drawing the region, and for anti-aliasing effect so that the borders look more smooth.
#include <Windows.h>
int triangle_height = 30;
int corner_size = 20;
int caption_height = 60;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_CREATE:
{
//create a child button for testing
CreateWindowW(L"BUTTON", L"Close", WS_CHILD | WS_VISIBLE,
10, caption_height + 10, 100, 30,
hwnd, HMENU(100), GetModuleHandle(NULL), NULL);
RECT rc;
GetClientRect(hwnd, &rc);
//create a triangle region
int w = rc.right;
int h = rc.bottom;
int z = triangle_height;
POINT pt[3];
pt[0] = { w / 2, 0 };
pt[1] = { w / 2 - z, z };
pt[2] = { w / 2 + z, z };
HRGN htri = CreatePolygonRgn(pt, 3, WINDING);
//create a round rectangle region
HRGN hrgn = CreateRoundRectRgn(0, z, w, h - z, corner_size, corner_size);
//combine the triangle with round rectangle
CombineRgn(hrgn, htri, hrgn, RGN_OR);
//set the new region
SetWindowRgn(hwnd, hrgn, TRUE);
DeleteObject(htri);
DeleteObject(hrgn);
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
//we don't have a standard title bar, paint one here:
rc.bottom = caption_height;
SetDCBrushColor(hdc, RGB(80, 80, 80));
FillRect(hdc, &rc, (HBRUSH)GetStockObject(DC_BRUSH));
//paint the background
rc = ps.rcPaint;
rc.top = caption_height;
SetDCBrushColor(hdc, RGB(240, 240, 240));
FillRect(hdc, &rc, (HBRUSH)GetStockObject(DC_BRUSH));
//use FrameRgn to paint a border around the region
HRGN hrgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hrgn);
SetDCBrushColor(hdc, RGB(128, 128, 128));
FrameRgn(hdc, hrgn, (HBRUSH)GetStockObject(DC_BRUSH), 1, 1);
DeleteObject(hrgn);
EndPaint(hwnd, &ps);
return 0;
}
case WM_NCHITTEST:
{
//we don't have a standard title-bar
//respond to our custome title-bar manually:
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
if(pt.y < caption_height)
return HTCAPTION;
break;
}
case WM_COMMAND:
if(HIWORD(wparam) == BN_CLICKED)
if(LOWORD(wparam) == 100)
SendMessage(hwnd, WM_CLOSE, 0, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
WNDCLASSEXW wcex = { sizeof(wcex) };
wcex.style = CS_DROPSHADOW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = NULL;
wcex.lpszClassName = L"classname";
RegisterClassExW(&wcex);
CreateWindowW(wcex.lpszClassName, L"Test", WS_VISIBLE | WS_POPUP,
200, 200, 600, 400, 0, 0, hInstance, 0);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
For xtow, I want to draw a top-level window with the standard non-client area and the client area filled with a bitmap which has an alpha channel.
I now discover the way I have implemented this works on Windows 7, but doesn't render correctly on Windows 8.1, leaving behind images of the window contents when it is moved or maximized.
To investigate, I made a simple test program alpha-test, which
Uses DwmEnableBlurBehindWindow() to set a non-intersecting blur region, so that alpha values in the window are honoured, without blur.
Uses BitBlt() to copy a bitmap with alpha into it.
//
// g++ alpha-test.cc -o alpha-test -mwindows -lgdiplus -ldwmapi
//
#define _WIN32_WINNT 0x0600
#include <assert.h>
#include <stdio.h>
#include <windows.h>
#include <gdiplus.h>
#include <dwmapi.h>
int width = 360;
int height = 360;
HBITMAP hBitmap;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdcUpdate = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
HBRUSH hbrush = CreateSolidBrush(RGB(0,0,0));
FillRect(hdcUpdate, &rc, hbrush);
DeleteObject(hbrush);
HDC hdcMem = CreateCompatibleDC(hdcUpdate);
HBITMAP hbmpold = (HBITMAP)SelectObject(hdcMem, hBitmap);
if (!BitBlt(hdcUpdate, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY))
{
printf("BitBlt failed: 0x%08x\n", (int)GetLastError());
}
SelectObject(hdcMem, hbmpold);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
LPCTSTR szWindowClass = "TransparentClass";
// Register class
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
wcex.hbrBackground = (HBRUSH)CreateSolidBrush(0x00000000);
RegisterClassEx(&wcex);
// Create window
HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW,
szWindowClass,
"Transparent Window",
WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, width, height,
NULL, NULL, hInstance, NULL);
Gdiplus::Bitmap *m_pImage = Gdiplus::Bitmap::FromFile(L"sample.png", FALSE);
Gdiplus::Color bg(0,0,0,0);
m_pImage->GetHBITMAP(bg, &hBitmap);
assert(hBitmap);
DWM_BLURBEHIND blurBehind = { 0 };
blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
blurBehind.hRgnBlur = CreateRectRgn(0, 0, -1, -1);
blurBehind.fEnable = TRUE;
blurBehind.fTransitionOnMaximized = FALSE;
DwmEnableBlurBehindWindow(hWnd, &blurBehind);
DeleteObject(blurBehind.hRgnBlur);
ShowWindow(hWnd, SW_SHOW);
// Main message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Is this really broken? How can I fix my code? Is this a Windows bug/limitation?
Is there another way to achieve my goal of drawing a bitmap with alpha into a window with a border?
Update:
I did some tests using Direct2D and Direct3D to fill the client area with the
bitmaps, but they mis-rendered in the same way.
The DWM doesn't do blurring any more (this feature was deemed too power hungry and was removed in Windows 8), so I'd guess that it's not properly compositing the background area of your window any more - and therefore you aren't getting the "automatic" alpha effect it was giving you in Windows 7.
This is kind of an unusual way to draw transparent windows to be honest. Using UpdateLayeredWindow is the "official" way and would have the benefit of working on Windows 8 as well as Windows 7.
what is the proper way of implementing custom rounded border for EDIT control in pure WinAPI (no MFC)? I need an edit with border like this:
Should I subclass edit control and do custom painting in WM_NCPAINT or something like that?
I guess you have two options:
As you said, you could sub-class and override WM_NCPAINT, etc to provide your own non-client area
Alternatively, you could simply turn off the border styles on the edit control and make the parent window responsible for drawing the frame.
With option #1, you would need to override WM_NCCALCSIZE to make the non-client area of the edit control larger (i.e. make the client area smaller), and then WM_NCPAINT to render your custom frame. You may also need to handle WM_NCHITTEST. And of course you'd need to make the control itself physically larger to account for the extra frame thickness.
It depends on your application design and how many controls like this you wish to use, but if it were me I would go with option #2. Modifying the standard drawing behaviour of system controls, many of which have decades of accumulated kludges and compatibility fixes attached to them, is often not as easy as you might expect.
If you make sure the WS_BORDER and WS_EX_CLIENTEDGE styles aren't set on the edit control, it will have no visible border of its own. Then all you have to do is have the parent window, when processing WM_PAINT, draw the frame around it. Make sure you set the WS_CLIPCHILDREN style on the parent window so that your custom drawing doesn't overwrite the edit control.
Either path would probably work in the end though so it's up to you which way you go.
This is an implementation that works for me.
It subclass the "EDIT" class control and replaces the WM_NCPAINT handler to draw a rectangle with rounded corners for all edit boxes with the WS_BORDER or WS_EX_CLIENTEDGE style. It draws the border on the parent DC.
The diameter of the corner is now fixed (10), I guess that should depend on the font size ...
Thanks to Darren Sessions for the GDI+ example how to draw the rounded rect:
https://www.codeproject.com/Articles/27228/A-class-for-creating-round-rectangles-in-GDI-with
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
inline void GetRoundRectPath(GraphicsPath* pPath, Rect r, int dia)
{
// diameter can't exceed width or height
if (dia > r.Width) dia = r.Width;
if (dia > r.Height) dia = r.Height;
// define a corner
Rect Corner(r.X, r.Y, dia, dia);
// begin path
pPath->Reset();
// top left
pPath->AddArc(Corner, 180, 90);
// top right
Corner.X += (r.Width - dia - 1);
pPath->AddArc(Corner, 270, 90);
// bottom right
Corner.Y += (r.Height - dia - 1);
pPath->AddArc(Corner, 0, 90);
// bottom left
Corner.X -= (r.Width - dia - 1);
pPath->AddArc(Corner, 90, 90);
// end path
pPath->CloseFigure();
}
inline void GetChildRect(HWND hChild, LPRECT rc)
{
GetWindowRect(hChild,rc);
SIZE si = { rc->right - rc->left, rc->bottom - rc->top };
ScreenToClient(GetParent(hChild), (LPPOINT)rc);
rc->right = rc->left + si.cx;
rc->bottom = rc->top + si.cy;
}
inline void DrawRoundedBorder(HWND hWnd, COLORREF rgba = 0xFF0000FF, int radius = 5)
{
BYTE* c = (BYTE*)&rgba;
Pen pen(Color(c[0], c[1], c[2], c[3]));
if (pen.GetLastStatus() == GdiplusNotInitialized)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
pen.SetColor(Color(c[0], c[1], c[2], c[3]));
}
pen.SetAlignment(PenAlignmentCenter);
SolidBrush brush(Color(255, 255, 255, 255));
RECT rc = { 0 };
GetChildRect(hWnd, &rc);
// the normal EX_CLIENTEDGE is 2 pixels thick.
// up to a radius of 5, this just works out.
// for a larger radius, the rectangle must be inflated
if (radius > 5)
{
int s = radius / 2 - 2;
InflateRect(&rc, s, s);
}
GraphicsPath path;
GetRoundRectPath(&path, Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top), radius * 2);
HWND hParent = GetParent(hWnd);
HDC hdc = GetDC(hParent);
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.FillPath(&brush, &path);
graphics.DrawPath(&pen, &path);
ReleaseDC(hParent, hdc);
}
static WNDPROC pfOldEditWndProc = NULL;
static LRESULT CALLBACK EditRounderBorderWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_NCCREATE:
{
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
if (style & WS_BORDER)
{
// WS_EX_CLIENTEDGE style will make the border 2 pixels thick...
style = GetWindowLong(hWnd, GWL_EXSTYLE);
if (!(style & WS_EX_CLIENTEDGE))
{
style |= WS_EX_CLIENTEDGE;
SetWindowLong(hWnd, GWL_EXSTYLE, style);
}
}
// to draw on the parent DC, CLIPCHILDREN must be off
HWND hParent = GetParent(hWnd);
style = GetWindowLong(hParent, GWL_STYLE);
if (style & WS_CLIPCHILDREN)
{
style &= ~WS_CLIPCHILDREN;
SetWindowLong(hParent, GWL_STYLE, style);
}
}
break;
case WM_NCPAINT:
if (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)
{
DrawRoundedBorder(hWnd);
return 0;
}
}
return CallWindowProc(pfOldEditWndProc, hWnd, uMsg, wParam, lParam);
}
class CRoundedEditBorder
{
public:
CRoundedEditBorder()
{
Subclass();
}
~CRoundedEditBorder()
{
Unsubclass();
}
private:
void Subclass()
{
HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
pfOldEditWndProc = (WNDPROC)GetClassLongPtr(hEdit, GCLP_WNDPROC);
SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)EditRounderBorderWndProc);
DestroyWindow(hEdit);
}
void Unsubclass()
{
HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)pfOldEditWndProc);
DestroyWindow(hEdit);
}
};
CRoundedEditBorder g_RoundedEditBorder;
LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY: PostQuitMessage(0); return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
#define WNDCLASSNAME L"RoundedEditBorderTestClass"
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
WNDCLASSEXW wcex = { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW,ParentWndProc,0,0,hInstance,NULL,NULL,CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW)),NULL,WNDCLASSNAME,NULL };
RegisterClassExW(&wcex);
HWND hWnd = CreateWindowW(WNDCLASSNAME, L"Rounded Edit Border Test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
CreateWindowEx(0, L"EDIT", L"no border", WS_CHILD | WS_VISIBLE, 10, 10, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
CreateWindowEx(0, L"EDIT", L"no ex style", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 50, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"Ex_ClientEdge", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 90, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GdiplusShutdown(gdiplusToken);
return (int)msg.wParam;
}