Introduction
This tutorial is intended for beginners in Windows 32 API programming in C. When we try to scroll some text in the dialog with a background image, we can clearly see the flicker of the text. This problem is due to the improperly handled Windows dialog paint messages and procedures. Here's a few easy techniques to use the double buffer painting to solve this problem.
We will go step by step to discover this technique. There's nothing much to worry about. Here's a quick recipe of things we need or are going to perform for this operation.
We need an array of text which will be displayed in the scroll text. Then we will calculate the size taken by the total text. Now we will paint the image in dialog box using the WM_PAINT
message. Now using the DrawText()
function, we will draw the text.
The correct method step by step will go below.
Background
The basic principle about the double buffer is nothing much. In the double buffer as the name itself declares, we will first create the buffer for the image which will be drawn on the dialog. Now in that buffer in memory, we will draw the text and finally we will paint the new image in the dialog, hence producing a flicker free drawing.
Using the Code
Step 1: Calculating the text size needed for the scroll.
We will count the number of lines in the given text by comparing the character by character with '\n
'. A sample source code to perform this operation goes below.
TextLen = lstrlen(Text);
for(int i=0;i<TextLen;i++)
{
if(Text[i]=='\n')
nLines++; }
Step 2: Now after having the number of lines, we need to get the required size for text scroll in the dialog box.
SIZE sizeAboutText;
HDC hDC = GetDC(hWnd);
HDC hDCMem = CreateCompatibleDC(hDC);
SelectObject(hDCMem, hFont);
GetTextExtentPoint32A(hDCMem, Text, TextLen, &sizeAboutText);
Step 3: Now we will need to get the RECT
size needed to scroll the text.
GetClientRect(hWnd, &rcClient);
rcText.bottom += (sizeAboutText.cy * (nLines+3))+rcClient.bottom;
rcText.top = rcClient.bottom;
rcText.right = rcClient.right;
rcText.left = rcClient.left;
Step 4: Now when we have the RECT
size needed for the text scroll in the dialog box, we will need the height and width of the image which we are going to paint in the background of the dialog box. We will need them when we will use double buffer later on. To find the height and width of the BMP image, we are going to use a simple set of functions.
hSkin = LoadBitmap(hIns,MAKEINTRESOURCE(IDB_BITMAP1));
BITMAP bm = {0};
GetObject( hSkin, sizeof(bm), &bm );
cx = bm.bmWidth;
cy = bm.bmHeight;
Step 5: The WM_PAINT
message handling.
In this message, we are going to create the double buffer and then we will draw it in the dialog.
case WM_PAINT:
{
PAINTSTRUCT ps;
if(BeginPaint(hWnd,&ps))
{
HDC hdcMem = CreateCompatibleDC(ps.hdc);
int ndcmem = SaveDC(hdcMem);
HBITMAP hbmMem = CreateCompatibleBitmap(ps.hdc, cx, cy);
SelectObject(hdcMem, hbmMem);
BitBlt(hdcMem, 0, 0, cx, cy, hdcBackground, 0, 0, SRCCOPY);
SelectObject(hdcMem, hFont);
SetTextColor(hdcMem, CLR);
SetBkMode(hdcMem, TRANSPARENT);
DrawText(hdcMem,Text,-1,&rcText,DT_CENTER | DT_TOP | DT_NOPREFIX | DT_NOCLIP);
BitBlt(ps.hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
RestoreDC(hdcMem, ndcmem);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
}
else
{
MessageBox(hWnd,"Unable to render graphics\nError :
Can not start painting!","Error",MB_ICONERROR);
}
}
return TRUE;
Step 6: Now when we have the text and image drawn in the dialog box, we will need a timer to scroll the text in dialog.
case WM_TIMER:
rcText.top+=ScrollConst;
rcText.bottom+=ScrollConst;
if(rcText.top>=rcClient.bottom+10)
{
ScrollConst=-1;
CLR=RGB(0,0,255);
}
if(rcText.bottom<=rcClient.top)
{
ScrollConst=1;
CLR=RGB(255,0,0);
}
InvalidateRect(hWnd, NULL, FALSE); return TRUE;
Finally, the full source code looks like below:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "resource.h"
HINSTANCE hIns;
HBITMAP hSkin;
RECT rcClient, rcText;
int nLines = 0;
int TextLen = 0;
LOGFONT lf;
HFONT hFont;
int ScrollConst =1;
COLORREF CLR;
LONG cx,cy;
HDC hdcBackground;
int ndcBackground;
char Text[] = "Simple flicker free text scrolling\nCoded by
Tejashwi Kalp Taru\n\nEnjoy the double buffer\n\nThanks to codeproject.com
for a nice place\nfor developer to developer";
BOOL CALLBACK DlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
{
hSkin = LoadBitmap(hIns,MAKEINTRESOURCE(IDB_BITMAP1));
BITMAP bm = {0};
GetObject( hSkin, sizeof(bm), &bm );
cx = bm.bmWidth;
cy = bm.bmHeight;
HDC hdcScreen = GetDC(hWnd);
hdcBackground = CreateCompatibleDC(hdcScreen);
ndcBackground = SaveDC(hdcBackground);
SelectObject(hdcBackground, hSkin);
ReleaseDC(hWnd, hdcScreen);
TextLen = lstrlen(Text);
for(int i=0;i<TextLen;i++)
{
if(Text[i]=='\n')
nLines++;
}
ZeroMemory(&lf, sizeof(LOGFONT));
strcpy(lf.lfFaceName,"Lucida Console");
lf.lfHeight = 20;
lf.lfWeight = FW_BOLD;
lf.lfQuality = ANTIALIASED_QUALITY;
hFont=CreateFontIndirect(&lf);
GetClientRect(hWnd, &rcClient);
SIZE sizeAboutText;
HDC hDC = GetDC(hWnd);
HDC hDCMem = CreateCompatibleDC(hDC);
SelectObject(hDCMem, hFont);
GetTextExtentPoint32A(hDCMem, Text, TextLen, &sizeAboutText);
ReleaseDC(hWnd,hDC);
DeleteDC(hDCMem);
rcText.bottom += (sizeAboutText.cy * (nLines+3))+rcClient.bottom;
rcText.top = rcClient.bottom;
rcText.right = rcClient.right;
rcText.left = rcClient.left;
SetTimer(hWnd, 1, 20, NULL); }
return TRUE;
case WM_TIMER:
rcText.top+=ScrollConst;
rcText.bottom+=ScrollConst;
if(rcText.top>=rcClient.bottom+10)
{
ScrollConst=-1;
CLR=RGB(0,0,255);
}
if(rcText.bottom<=rcClient.top)
{
ScrollConst=1;
CLR=RGB(255,0,0);
}
InvalidateRect(hWnd, NULL, FALSE); return TRUE;
case WM_PAINT:
{
PAINTSTRUCT ps;
if(BeginPaint(hWnd,&ps))
{
HDC hdcMem = CreateCompatibleDC(ps.hdc);
int ndcmem = SaveDC(hdcMem);
HBITMAP hbmMem = CreateCompatibleBitmap(ps.hdc, cx, cy);
SelectObject(hdcMem, hbmMem);
BitBlt(hdcMem, 0, 0, cx, cy, hdcBackground, 0, 0, SRCCOPY);
SelectObject(hdcMem, hFont);
SetTextColor(hdcMem, CLR);
SetBkMode(hdcMem, TRANSPARENT);
DrawText(hdcMem,Text,-1,&rcText,DT_CENTER |
DT_TOP | DT_NOPREFIX | DT_NOCLIP);
BitBlt(ps.hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
RestoreDC(hdcMem, ndcmem);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
}
else
{
KillTimer(hWnd,1);
MessageBox(hWnd,"Unable to render graphics\nError :
Can not start painting!","Error",MB_ICONERROR);
}
}
return TRUE;
case WM_CLOSE:
KillTimer(hWnd,1);
RestoreDC(hdcBackground, ndcBackground);
DeleteDC(hdcBackground);
DeleteObject(hSkin);
DeleteObject(hFont);
EndDialog(hWnd,0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
hIns = hInstance;
DialogBoxParam(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,DLGPROC(DlgProc),0);
}
Points of Interest
While writing and using this code, I found the double buffer very useful. As I usually make skinned application, double buffer is the way to perform flicker free and best drawing. Although Direct3D and GDI++ are far more helpful and fast, this is not the way for a learner. Hence, when I coded this source, I thought that it might be helpful for someone else.
History