Introduction
Some time ago I worked with Altera Max Plus II to design simple electronic chip.
I noticed that this application draws a company logo on Main Frame background.
Once I had more free time then usually, I wanted to implement this by myself.
This article is the result :)
Implementation
So you may wonder how to draw image on background window of MDI application.
First you have to subclass window you want to draw on.
To do this call GetWindowLong(hMain, GWL_WNDPROC)
.
This function returns
pointer to old window procedure and we want to store it for further use. Place this code
in CBackgroundImageApp::InitInstance()
just before showing the Main Frame.
HWND hMain = pMainFrame->GetWindow(GW_CHILD)->GetSafeHwnd();
pfnOldWndProc = (WNDPROC)GetWindowLong(hMain, GWL_WNDPROC);
SetWindowLong(hMain, GWL_WNDPROC, (long)pfnNewWndProc);
Now we have sub-classed window, but the window procedure is missing. Lets write it now.
The window procedure should handle at least two messages to work well :
First message is sent when Windows wants application
to draw its background - we'll use this to draw our image.
Second message is also important because when you resize
window image should also change its size to fit into
new window size. Our window proc would look like this :
LRESULT CALLBACK pfnNewWndProc(HWND hwnd,
UINT uMsg, WPARAM wParam,LPARAM lParam)
{
switch (uMsg) {
case WM_SIZE :
case WM_ERASEBKGND :
return 1;
default :
return CallWindowProc(pfnOldWndProc,
hwnd, uMsg, wParam, lParam);
}
}
Important note
Returing 1 from WM_ERASEBKGND
case is very important
because it tells Windows that we already erased window and
it has nothing to do, otherwise Windows would erase this
window again with standard background,
which I suppose is not we wanted to achieve.
What about other messages ?
Here is the moment we are using stored old window procedure
pointer. We simply call old procedure and return its result.
So lets look closer to the WM_SIZE
and WM_ERASEBKGND
cases.
WM_SIZE
- Simply force window background to redraw
itself by sending WM_ERASEBKGND
to it :
SendMessage(hwnd, WM_ERASEBKGND, (WPARAM)GetDC(hwnd), 0);
return 1;
WM_ERASEBKGND
- Code below will simply BitBlt
the bitmap.
Because the window size generally very rarely is the same as image size
BitBlt()
becomes StretchBlt()
. Finally it looks like below :
hcompdc = CreateCompatibleDC(hdc);
SelectObject(hcompdc, hBmp);
GetClientRect(hwnd, &rect);
if (0 == StretchBlt(hdc,
rect.left, rect.top, rect.right, rect.bottom,
hcompdc, 0, 0, 1152, 864, SRCCOPY))
MessageBox(hwnd, "error",
"while StretchBlt", MB_OK);
DeleteDC(hcompdc);
return 1;
Where :
-
hdc
, hcompdc
- Device context
-
hBmp
- bitmap handle
It is very important to invoke DeleteDC()
because we don't want to cause memory leaks.
Final notes
Performance of this solution strongly depends on operating system ,
graphics card installed, and on window size.
In my case Windows 98 was better choice than Windows 2000.
On second one redrawing an image took significantly
more time to make the user feel comfortably.
Application running on Windows 98 looked very nice.
My graphics device is Geforce 2 GTS. If you have
experiences from other systems or configurations please post it below
at article's discussion board. StretchBlt
function
also differs between systems.
One used in Windows 98 was better this time too.