I'm trying to load a bitmap to a window I created. The bitmap should be the background of the window (I want to add labels on it an a progress bar later on).
This is my code:
HINSTANCE hInst;
LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX WndCls;
static WCHAR szAppName[] = L"BitmapIntro";
MSG Msg;
hInst = hInstance;
WndCls.cbSize = sizeof(WndCls);
WndCls.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
WndCls.lpfnWndProc = WindProcedure;
WndCls.cbClsExtra = 0;
WndCls.cbWndExtra = 0;
WndCls.hInstance = hInst;
WndCls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndCls.lpszMenuName = NULL;
WndCls.lpszClassName = szAppName;
WndCls.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
RegisterClassEx(&WndCls);
CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
szAppName,
L"Bitmaps Fundamentals",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
while (GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return static_cast<int>(Msg.wParam);
}
LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
HDC hDC, MemDCExercising;
PAINTSTRUCT Ps;
HBITMAP bmpExercising;
switch (Msg)
{
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &Ps);
// Load the bitmap from the resource
bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Create a memory device compatible with the above DC variable
MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
SelectObject(MemDCExercising, bmpExercising);
// Copy the bits from the memory DC into the current dc
BitBlt(hDC, 10, 10, 450, 400, MemDCExercising, 0, 0, SRCCOPY);
// Restore the old bitmap
DeleteDC(MemDCExercising);
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
The problem is, the PNG size is small compared to the window, so when the PNG opens, it only occupies the left high corner. How can I make it stretch to my window size or at least draw it over and over until it fills the window?
How can I make it stretch to my window size
Use StretchBlt() instead of BitBlt().
case WM_PAINT:
{
// Get the window dimensions
RECT r;
GetClientRect(hWnd, &r);
// Load the bitmap from the resource
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Get the bitmap dimensions
BITMAP bmp;
GetObject(bmpExercising, sizeof(BITMAP), &bmp);
PAINTSTRUCT Ps;
HDC hDC = BeginPaint(hWnd, &Ps);
// Create a memory device compatible with the above DC variable
HDC MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
HBITMAP hOldBmp = SelectObject(MemDCExercising, bmpExercising);
// Copy the bits from the memory DC into the current dc
StretchBlt(hDC, 0, 0, r.right - r.left, r.bottom - r.top, MemDCExercising, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
// Restore the old bitmap
SelectObject(MemDCExercising, hOldBmp);
// Destroy the memory device
DeleteDC(MemDCExercising);
// Destroy the bitmap
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
}
or at least draw it over and over until it fills the window?
There are two different ways to handle that.
at startup, load the bitmap and create an HBRUSH around it using CreatePatternBrush(), and then assign that to the WNDCLASS::hbrBackground field when you register your window class. Let the OS draw the window background using the bitmap for you.
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
WndCls.hbrBackground = CreatePatternBrush(bmpExercising);
if you want to paint the bitmap manually, have your paint handler call BitBlt() in a couple of loops. You know the dimensions of the bitmap (which you can retrieve in code using GetObject() and the BITMAP structure), and you know the dimensions of the window (which you can retrieve in code using GetWindowRect() or GetClientRect()). So simply draw the same bitmap more than one time at different offsets as needed. Start by drawing it once in the top-left corner, then move right bitmap-width pixels and draw it again, repeating until you move past window-width pixels. Then move left back to 0 and move down bitmap-height pixels and repeat the whole width-line again, repeating until you move past window-height pixels.
case WM_PAINT:
{
// Get the window dimensions
RECT r;
GetClientRect(hWnd, &r);
// Load the bitmap from the resource
HBITMAP bmpExercising = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
// Get the bitmap dimensions
BITMAP bmp;
GetObject(bmpExercising, sizeof(BITMAP), &bmp);
PAINTSTRUCT Ps;
HDC hDC = BeginPaint(hWnd, &Ps);
// Create a memory device compatible with the above DC variable
HDC MemDCExercising = CreateCompatibleDC(hDC);
// Select the new bitmap
HBITMAP hOldBmp = SelectObject(MemDCExercising, bmpExercising);
int width = r.right - r.left;
int height = r.bottom - r.top;
// Copy the bits from the memory DC into the current dc
for(int y = 0; y < height; y += bmp.bmHeight)
{
for(int x = 0; x < width; x += bmp.bmWidth)
{
BitBlt(hDC, x, y, bmp.bmWidth, bmp.bmHeight, MemDCExercising, 0, 0, SRCCOPY);
}
}
// Restore the old bitmap
SelectObject(MemDCExercising, hOldBmp);
// Destroy the memory device
DeleteDC(MemDCExercising);
// Destroy the bitmap
DeleteObject(bmpExercising);
EndPaint(hWnd, &Ps);
break;
}
Now, with that said, here are some additional notes:
you should not be loading the bitmap inside of your paint handler. Load it one time before creating the window, and then reuse the same HBITMAP for each paint operation until the window is destroyed, then free the bitmap.
LoadBitmap() is deprecated, you should be using LoadImage() instead, eg:
HBITMAP bmpExercising = (HBITMAP) LoadImage(hInst, MAKEINTRESOURCE(IDB_BITMAP3), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
you said "The bitmap should be the background of the window", so you should be drawing the bitmap in response to the WM_ERASEBKGND message instead of the WM_PAINT message.
case WM_ERASEBKGND:
{
HDC hDC = (HDC) wParam;
// draw the bitmap on hDC as needed...
return 1;
}
Related
I am studying Double buffering in windows winapi.
When I draw text on HDC direct using DrawText function, it works well like under code.
case WM_PAINT:
hDC = BeginPaint(hwnd,&ps);
DrawText(hDC,"test",4,&rt,DT_CENTER | DT_WORDBREAK);
DeleteDC(hMemDC);
ReleaseDC(hwnd,hDC);
EndPaint(hwnd,&ps);
break;
But, I would like to want to use double buffering, so I make memory dc and bitblt function.
Under code is that,It don't works.I can show white empty screen.
case WM_PAINT:
hDC = BeginPaint(hwnd,&ps);
hMemDC = CreateCompatibleDC(hDC);
//GetClientRect(hwnd, &crt);
//hBitmap = CreateCompatibleBitmap(hDC, crt.right, crt.bottom);
//OldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
DrawText(hMemDC,"test",4,&rt,DT_CENTER | DT_WORDBREAK);
BitBlt(hDC,0,0,800,800,hMemDC,0,0,SRCCOPY);
DeleteDC(hMemDC);
ReleaseDC(hwnd,hDC);
EndPaint(hwnd,&ps);
break;
Is memory dc different original dc ?
If I use CreateCompatibleBitmap function, it work well.
What is concept am i missing ?
Is there website well-organized website?
A typical non-double-buffered drawing routine looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rcWnd;
GetClientRect(hWnd, &rcWnd);
{
DrawText(hdc, _T("Hello, world!"), -1,
&rcWnd, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
EndPaint(hWnd, &ps);
}
break;
A typical double-buffered drawing routine looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rcWnd;
GetClientRect(hWnd, &rcWnd);
{
const int width = rcWnd.right - rcWnd.left
const int height = rcWnd.bottom - rcWnd.top;
// create a new DC based on the target HDC
HDC hDCMem = CreateCompatibleDC(hdc);
// create a bitmap that is compatible with the target DC
HBITMAP hMemBmp = CreateCompatibleBitmap(hdc, width, height);
// select the new bitmap in to the DC, saving the old bitmap
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDCMem, hMemBmp);
// do your drawing
HBRUSH hBr = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hDCMem, &rcWnd, hBr);
DeleteObject(hBr);
DrawText(hDCMem, _T("Hello, world!"), -1,
&rcWnd, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// copy all the bits from our new DC over to the target DC
BitBlt(hdc, rcWnd.left, rcWnd.top, width, height, hDCMem, 0, 0, SRCCOPY);
// select the original bitmap the DC came with
SelectObject(hDCMem, hOldBmp);
// delete our bitmap
DeleteObject(hMemBmp);
// delete the DC
DeleteDC(hDCMem);
}
EndPaint(hWnd, &ps);
}
break;
case WM_ERASEBKGND:
// since we are drawing the entire area because we are double-buffering, there is
// no need to erase the background. This will speed up your drawing.
return TRUE;
HDC objects are all the same -- there is no special things that happen when you create another one in memory. I mean, they are all in memory, really. You are just creating another canvas to do your drawing on, then copying that canvas over in one shot.
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.
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.
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;
}