
In this part of the tutorial, we will cover:
By now, you've probably had a play with the other controls, and you'll be about ready to move on. This is a graphical user interface, after all, so let's get some graphics up.
Again, we run up against a problem: we somehow have to co-operate with other applications. When we draw to our window, we have no idea what is going to be on the screen: other windows might be overlapping ours, we might be in any colour depth or resolution. We might be drawing to the screen, printer, bitmap or metafile. We need two things:
Device independence means that we can use the same commands to draw to any output device. At one level, we can use device drivers to achieve this end - but we should be careful to avoid assumptions about our output device.
We also need to have some way of saying 'we're drawing in this window, with this clipping region, this scaling and this current information about state', so that multiple windows can draw at the same time. Windows encapsulates this state information internally and gives us back a handle called a device context. A DC is a system resource; once you have one, you have a handle to a significant amount of information, so you should release it as soon as possible.
You can draw lines, points, polygons, arcs, circles, paths and all kinds of things using the Windows API. What we're going to concentrate on, however, is drawing bitmaps to the screen. We're going to write a bitmap viewer program, capable of displaying .BMP files.
Create a new project and copy your files across. We're going to need a new dialog. Again, I'm going to leave it as IDD_DIALOG1, but you can call it IDD_BITMAPVIEWER, or whatever you like. You will need OK and Cancel buttons as before, as well as an edit box to hold the file name (with ID IDC_FILENAME) and a Browse button (IDC_BROWSE). We also need a rectangular area (a static control, IDC_VIEW):

We will modify the code from Step 1 to display the bitmap. First, however, we will need to discuss the Browse button, which opens up a common dialog.
Common dialog boxes are provided by Windows. They include:
We will be using the Open dialog box. This dialog box is displayed by filling in an OPENFILENAME structure and then calling GetOpenFileName:
BOOL GetBitmapFileName(TCHAR *filename, int len, HWND hWnd) { OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = _T("Bitmap Files (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0\0"); ofn.lpstrFile = filename; ofn.nMaxFile = len; ofn.lpstrTitle = _T("Browse"); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; return GetOpenFileName(&ofn); }
When you call this function, you will get the standard Open dialog box you've seen a million times.
We now need a simple function to load the bitmap file from disk:
HBITMAP LoadBitmapFile(const TCHAR *filename) { return (HBITMAP)LoadImage(NULL, filename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); }
These two functions will be called from the command handlers:
TCHAR szBitmapFilename[_MAX_PATH];
HBITMAP hBitmap;
BOOL MainDialog_OnCommand(HWND hWnd, WORD wCommand, WORD wNotify, HWND hControl)
{
switch (wCommand)
{
case IDC_BROWSE:
if (GetBitmapFileName(szBitmapFilename,
sizeof(szBitmapFilename) / sizeof(TCHAR), hWnd))
SetDlgItemText(hWnd, IDC_FILENAME, szBitmapFilename);
// fall through into OK handler
case IDOK:
if (hBitmap != NULL)
DeleteObject(hBitmap);
GetDlgItemText(hWnd, IDC_FILENAME, szBitmapFilename,
sizeof(szBitmapFilename) / sizeof(TCHAR));
hBitmap = LoadBitmapFile(szBitmapFilename);
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
break;
case IDCANCEL:
EndDialog(hWnd, wCommand);
break;
}
return TRUE;
}
So, now we can load a bitmap from a file into memory, and get a handle to a bitmap back from it. This is useful, since we can use this handle to draw the bitmap later.
To draw the bitmap, we will need to handle the WM_PAINT message that Windows sends us when our window needs redrawing. Remember that the window could be covered and uncovered at any time, so we need to be able to handle this message, rather than just painting when we load the bitmap.
We will need to add another message, so our main dialog procedure becomes
BOOL MainDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hDC;
switch (uMsg)
{
case WM_COMMAND:
return MainDialog_OnCommand(hWnd, LOWORD(wParam),
HIWORD(wParam), (HWND)lParam);
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
MainDialog_OnPaint(hWnd, hDC);
EndPaint(hWnd, &ps);
return TRUE;
case WM_CLOSE:
EndDialog(hWnd, 0);
return TRUE;
}
return FALSE;
}
BeginPaint and EndPaint handle the job of getting a device context during a WM_PAINT message. They also signal to Windows that the window has been painted, thank you, you don't need to ask again. NOTE: if you don't call Begin/EndPaint, but you handle the WM_PAINT message, Windows will continuously send you WM_PAINT messages and your user interface will lock up.
We now need a painting function. This function has to locate the area to draw the bitmap (the top left of the IDC_VIEW rectangle), then convert that into the coordinate system of the window, then draw the bitmap at that position.
void MainDialog_OnPaint(HWND hWnd, HDC hDC) { if (hBitmap == NULL) return; RECT r; POINT p; GetClientRect(GetDlgItem(hWnd, IDC_VIEW), &r); p.x = r.left; p.y = r.top; ClientToScreen(GetDlgItem(hWnd, IDC_VIEW), &p); ScreenToClient(hWnd, &p); DrawState(hDC, NULL, NULL, (LPARAM)hBitmap, 0, p.x, p.y, 0, 0, DST_BITMAP | DSS_NORMAL); }
Older books on drawing bitmaps will talk about BitBlt and memory DCs. You can do that; it's quicker if you're drawing multiple bitmaps, and it's much more capable - you can stretch, clip and apply raster operations - but for simplicity, nothing beats DrawState.
Now we've progressed to full-blown utilities, let's kill off the last hangover - the console window.
Replace your _tmain with:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MainDialogProc, 0); return 0; }
Note that the module (instance) handle is passed to WinMain, so we don't have to call GetModuleHandle.
Now, you will have to change your settings to allow this change.

This is a shot of the bitmap viewer (slightly smaller) displaying a bitmap (a Windows NT background):

In the next section, we'll move from dialog boxes to generic windows, and talk about message loops, idle processing and keyboard input.