Related
I'm working on a Win32 app with layered windows. The window contents are drawn using Cairo. The transparency is very unpredictable however. Sometimes everything is correct; opaque parts are opaque, transparent parts transparent. Other times what should be opaque becomes transparent or vanishes completely.
This is the most minimal example I could reduce the problem to (based on this answer). It should draw a square in the upper left corner of the screen, with the left half transparent grey and the right half opaque black.
import core.sys.windows.windows;
import std.utf;
import std.stdio;
import cairo;
import cairo_win32;
const UINT WIDTH = 500;
const UINT HEIGHT = 500;
extern(Windows)
LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) nothrow {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void main(string[] args) {
WNDCLASS wndclass;
wndclass.lpszClassName = "Test".toUTF16z;
wndclass.lpfnWndProc = &WndProc;
RegisterClass(&wndclass);
HINSTANCE hInstance = GetModuleHandle(NULL);
HWND hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, wndclass.lpszClassName, "Test".toUTF16z, WS_POPUP, 50, 50, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, SW_SHOWNA);
HDC hdcScreen = GetDC(NULL);
// Offscreen hdc for painting
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmMem = CreateCompatibleBitmap(hdcScreen, WIDTH, HEIGHT);
auto hOld = SelectObject(hdcMem, hbmMem);
// Draw using offscreen hdc
auto surface = cairo_win32_surface_create(hdcMem);
auto cr = cairo_create(surface);
// Transparent grey
cairo_rectangle(cr, 0, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.4, 0.4, 0.4, 0.8);
cairo_fill(cr);
// Black
cairo_rectangle(cr, 250, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_fill(cr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
// Show on screen
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
RECT win_rect;
GetWindowRect(hwnd, &win_rect);
POINT ptZero = POINT(0, 0);
POINT win_pos = POINT(win_rect.left, win_rect.top);
SIZE win_dims = SIZE(WIDTH, HEIGHT);
UpdateLayeredWindow(hwnd, hdcScreen, &win_pos, &win_dims, hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// Reset offscreen hdc to default bitmap
SelectObject(hdcMem, hOld);
// Cleanup
DeleteObject(hbmMem);
DeleteDC (hdcMem);
ReleaseDC(NULL, hdcScreen);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
This works, as you can see here.
However, when I now switch the order of the two Cairo draw calls like this:
// Opaque black
cairo_rectangle(cr, 250, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.01, 0.01, 0.01, 1.0);
cairo_fill(cr);
// Transparent grey
cairo_rectangle(cr, 0, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.4, 0.4, 0.4, 0.8);
cairo_fill(cr);
Then the opaque right half becomes completely transparent.
What's going on here? I assume I must be doing something more fundamentally wrong, probably with the whole HDC and UpdateLayeredWindow business.
Update: I tried to create an image surface with CAIRO_FORMAT_ARGB32, used the exact same drawing operations and wrote it to a PNG file. In that case both images look correct and identical.
It seems like the first operation can't have an alpha of exactly 1.0. If I set the first rectangle's alpha to 1.0, the second (black opaque) rectangle vanishes. If I set the first rectangle's alpha to 0.99 instead, the second rectangle is drawn correctly.
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.
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;
}
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
I am trying to draw a bitmap with an alpha channel (via AlphaBlend) as the face of an owner-drawn button. The problem is that I'm not sure how to draw the background of the button. The button's bitmap is circular, and the button is on top of a static control that draws a rectangular bitmap (via SS_BITMAP). It looks fine the first time it is drawn, but subsequent drawings end up alphablending the bitmap with its remains in the DC so the edges (where the alpha pixels lie) get ugly. I tried copying the dialog background to the DC I get in WM_DRAWITEM, but that only gets me the dialog background; it does not get me the part of the static control that is under the button. How do I do this?
My bitmaps are similar to this, except the dialog has a custom background (bitmap drawn during WM_ERASEBKGND) and the rectangle extends further out horizontally.
I found a better solution. It's basically the same structure as my previous solution, only instead of copying what's already on the device context to a bitmap I send all the relevant controls WM_ERASEBKGND and WM_PRINTCLIENT messages. I based it off of the code in this KB article.
Well, I found one method that works for my needs; I don't know if it's the ideal solution, but if nobody can come up with anything better then I'll accept my own answer in a few days.
So here's the trick I'm using (transposed from use of ATL's CImage to raw Win32 APIs, so there could be some mistakes):
LRESULT CALLBACK MyButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_ERASEBKGND:
if(!m_BackgroundBitmap)
{
// first time the button's background is being erased
// all the controls below it in the z-order have already
// been drawn at this point, so take a snapshot of the
// contents of the device context to use in the future
RECT rc;
GetWindowRect(hWnd, &rc);
int cx = rc.right - rc.left;
int cy = rc.bottom - rc.top;
HDC hDC = (HDC)wParam;
HDC hDCMem = CreateCompatibleDC(hDC);
m_BackgroundBitmap = CreateCompatibleBitmap(hDC, cx, cy);
HBITMAP hBmpOld = (HBITMAP)SelectObject(hDCMem, m_BackgroundBitmap);
BitBlt(hDCMem, 0, 0, cx, cy, hDC, 0, 0, SRCCOPY);
SelectObject(hDCMem, hBmpOld);
hBmpOld = NULL;
DeleteDC(hDCMem);
hDCMem = NULL;
}
break;
}
return CallWindowProc(m_PrevProc, hWnd, uMsg, wParam, lParam);
}
INT_PTR CALLBACK MyDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
// load resources, subclass buttons, etc.
return TRUE;
case WM_DRAWITEM:
// figure out if display needs to be updated, which face to draw, etc.
HDC hDC = lpDrawItemStruct->hDC;
HDC hDCMem = CreateCompatibleDC(hDC);
// first copy the background from the snapshot taken earlier
HBITMAP hBmpOld = (HBITMAP)SelectObject(hDCMem, m_BackgroundBitmap);
BitBlt(hDC, x, y, w, h, hDCMem, 0, 0, SRCCOPY);
// then alphablend the button face on top of that
SelectObject(hDCMem, m_AlphaButtonBitmap);
AlphaBlend(hDC, x, y, w, h, hDCMem, 0, 0, w, h, bf);
SelectObject(hDCMem, hBmpOld);
hBmpOld = NULL;
DeleteDC(hDCMem);
hDCMem = NULL;
return TRUE;
}
return FALSE;
}