Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Flicker Free Text Scrolling with Double Buffering

4.50/5 (3 votes)
24 Jun 2013CPOL3 min read 15.9K   506  
When we have to scroll some text in the dialog box with a background image, you can see flicker. Here's the technique of double buffer to solve this problem

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.

C++
// Counts the number of lines in scroll text
TextLen = lstrlen(Text);
for(int i=0;i<TextLen;i++)
{
  if(Text[i]=='\n')
     nLines++; // Counter for the total number of lines we found.
} 

Step 2: Now after having the number of lines, we need to get the required size for text scroll in the dialog box.

C++
// Gets the size of total scroll texts
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.

C++
// Calculates the needed size for given scroller 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.

C++
 // Loads the bitmap, get its CX,CY and get compatible device context 
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.

C++
 case WM_PAINT:
{
	PAINTSTRUCT ps;
	if(BeginPaint(hWnd,&ps))
	{
		//Creating double buffer
		HDC hdcMem = CreateCompatibleDC(ps.hdc);
		int ndcmem = SaveDC(hdcMem);
		HBITMAP hbmMem = CreateCompatibleBitmap(ps.hdc, cx, cy);
		SelectObject(hdcMem, hbmMem);
		//-------------------------------------------------------

		// Copy background bitmap into double buffer
		BitBlt(hdcMem, 0, 0, cx, cy, hdcBackground, 0, 0, SRCCOPY);
		//---------------------------------------------------------

		// Draw the text
		SelectObject(hdcMem, hFont);
		SetTextColor(hdcMem, CLR);
		SetBkMode(hdcMem, TRANSPARENT);
		DrawText(hdcMem,Text,-1,&rcText,DT_CENTER | DT_TOP | DT_NOPREFIX | DT_NOCLIP);
		//-----------------------------------------------------------------------------

		// Copy double buffer to screen
		BitBlt(ps.hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
		//--------------------------------------------------

		// Clean up
		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.

C++
case WM_TIMER:
// Check and set the current range of scroll text and sets color of text accordingly
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); // Invalidates the window, WM_PAINT caused !!!
return TRUE;

Finally, the full source code looks like below:

C++
#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:
			{
				// Loads the bitmap, get its CX,CY and get compatible device context 
				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);
				//------------------------------------------------------------------

				// Counts the number of lines in scroll text
				TextLen = lstrlen(Text);
				for(int i=0;i<TextLen;i++)
				{
					if(Text[i]=='\n')
						nLines++;
				}
				//------------------------------------------

				// Create a font as desired
				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);
				//-----------------------------------
				
				// Gets the size of total scroll texts
				SIZE sizeAboutText;
				HDC hDC = GetDC(hWnd);
				HDC hDCMem = CreateCompatibleDC(hDC);
				SelectObject(hDCMem, hFont);
				GetTextExtentPoint32A(hDCMem, Text, TextLen, &sizeAboutText);
				ReleaseDC(hWnd,hDC);
				DeleteDC(hDCMem);
				//-----------------------------------------------------------

				// Calculates the needed size for given scroller text
				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); // Starts timer of duration of 20MS
			}
			return TRUE;
		case WM_TIMER:
			// Check and set the current range of scroll text 
			// and sets color of text accordingly
			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); // Invalidates the window, 
							// WM_PAINT caused !!!
			return TRUE;
		case WM_PAINT:
			{
				PAINTSTRUCT ps;
				if(BeginPaint(hWnd,&ps))
				{
					//Creating double buffer
					HDC hdcMem = CreateCompatibleDC(ps.hdc);
					int ndcmem = SaveDC(hdcMem);
					HBITMAP hbmMem = CreateCompatibleBitmap(ps.hdc, cx, cy);
					SelectObject(hdcMem, hbmMem);
					//-------------------------------------------------------

					// Copy background bitmap into double buffer
					BitBlt(hdcMem, 0, 0, cx, cy, hdcBackground, 0, 0, SRCCOPY);
					//---------------------------------------------------------

					// Draw the text
					SelectObject(hdcMem, hFont);
					SetTextColor(hdcMem, CLR);
					SetBkMode(hdcMem, TRANSPARENT);
					DrawText(hdcMem,Text,-1,&rcText,DT_CENTER | 
								DT_TOP | DT_NOPREFIX | DT_NOCLIP);
					//-----------------------------------------------------------------------------

					// Copy double buffer to screen
					BitBlt(ps.hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
					//--------------------------------------------------

					// Clean up
					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

  • 1.0: Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)