HOME    C++    JAVA    TUTORIALS    PROJECTS    SMALL PROGRAMS    CONTACT    LINKS   
 
 

Tutorials


background texture on an edit control
 

How to add a background texture to an edit control

Download

Examples (source included)


Introduction

I was coding a small Win32 application and I thought a background texture would make this much more fun to code. I started off by texturing the main window then a button but when i came to the edit box it started to get tricky. I tried searching for information about this but i didn't find any, maybe I'm bad at writing google queries I don't know. Anyway, I manage to get it working with the information I gathered.

There are plenty of information about how to change the background color of an edit control, if that's all you want to do try #WinProg on EFNet homepage (in the FAQ section).

I want to warn you before proceeding, this is probably not the best way to add a background texture it might not work on every windows version. Also I won't explain how to load a texture, create a window or any other component required there are other tutorials for that (check out #Winrpog for Win32 tutorials).

Enough shit chat...

WM_PAINT

The first thing we need to do is catch WM_PAINT and draw our texture instead of letting windows draw it's ugly background.

Main window procedure:


LRESULT CALLBACK winProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    {
	case WM_CREATE:
		{
			loadTexture();

			hEditControl = CreateWindowEx(WS_EX_TRANSPARENT, "EDIT","Edit control",
							WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | WS_TABSTOP,
							10,20,EDIT_WIDTH,EDIT_HEIGHT,hwnd,
							(HMENU)IDC_EDIT_CONTROL,GetModuleHandle(NULL),NULL);

			font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
			SendMessage(hEditControl, WM_SETFONT, (WPARAM)font, MAKELPARAM(FALSE, 0));

			oldWndProc = (WndProc)SetWindowLong(hEditControl, GWL_WNDPROC, (LONG)winProcEditControl);
		}
	break;
	case WM_CLOSE:
		DestroyWindow(hwnd);
	break;
	case WM_DESTROY:
        	PostQuitMessage(WM_QUIT);
        break;
	default:
		return DefWindowProc(hwnd, Msg, wParam, lParam);
	break;
    }

    return 0;
}

The edit controls window procedure:


LRESULT CALLBACK winProcEditControl(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps; 
	switch  (Msg) 
	{	
	case WM_PAINT: 
		{
			HDC hdc = BeginPaint(hwnd, &ps); 

			BitBlt(hdc,0, 0, EDIT_WIDTH, EDIT_HEIGHT,bitmapDC,0,0,SRCCOPY);


			long editStyle = GetWindowLong(hwnd,GWL_STYLE);
			long textStyle = DT_TOP;
			if (editStyle & ES_CENTER)
				textStyle |= DT_CENTER;
			if (editStyle & ES_LEFT)
				textStyle |= DT_LEFT;
			if (editStyle & ES_RIGHT)
				textStyle |= DT_RIGHT;
			if (!(editStyle & ES_MULTILINE))
				textStyle |= DT_SINGLELINE;
			char caption[255];
			GetWindowText(hwnd,caption,255);
			SetBkMode(hdc,TRANSPARENT);
			SetTextColor(hdc,0xFFFFFF);
			RECT rcEC;
			GetClientRect(hwnd,&rcEC);
			SelectObject(hdc,(HGDIOBJ)font);
			DrawText(hdc,caption,strlen(caption),&rcEC,textStyle);

			EndPaint(hwnd, &ps); 
			return 0;
		}
	break;
	}
	return oldWndProc(hwnd, Msg, wParam, lParam);
}

This is the "Example 1" example in the Examples file.

Everything looks good at start up, but once you select the edit control the ugly windows background are there again. How do we get rid of that ?

WM_CTLCOLOREDIT

This message get sent to the main window every time an edit control is about to get redrawn (Note for read only edit controls WM_CTLCOLORSTATIC is sent instead). This redraw has nothing to do with the WM_PAINT message we took care of in the previous section, this is redraw of the text and it's background.
What we want to do here is to make sure that the text background isn't being drawn we do this by calling SetBkMode and returning a hollow brush.

Main window procedure:


LRESULT CALLBACK winProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    {
	case WM_CREATE:
		{
			loadTexture();

			hEditControl = CreateWindowEx(WS_EX_TRANSPARENT, "EDIT","Edit control",
							WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | WS_TABSTOP,
							10,20,EDIT_WIDTH,EDIT_HEIGHT,hwnd,
							(HMENU)IDC_EDIT_CONTROL,GetModuleHandle(NULL),NULL);

			font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
			SendMessage(hEditControl, WM_SETFONT, (WPARAM)font, MAKELPARAM(FALSE, 0));

			oldWndProc = (WndProc)SetWindowLong(hEditControl, GWL_WNDPROC, (LONG)winProcEditControl);
		}
	break;
	case WM_CLOSE:
		DestroyWindow(hwnd);
	break;
	case WM_DESTROY:
		PostQuitMessage(WM_QUIT);
        break;
	case WM_CTLCOLORSTATIC:
	case WM_CTLCOLOREDIT:
		{
			// we don't want that background to be drawn
			SetBkMode((HDC)wParam, TRANSPARENT);
			SetTextColor((HDC)wParam, RGB(255, 255, 255));
			return (LRESULT)GetStockObject(HOLLOW_BRUSH);
		}
	break;
	default:
		return DefWindowProc(hwnd, Msg, wParam, lParam);
	break;
    }

    return 0;
}

This is the "Example 2" example in the Examples file.

Now it doesn't paint over the background BUT what happens if we the press backspace.... because we override the WM_CTLCOLOREDIT message the character is never erased, this is described in Microsoft Q & A from May 1997. However he doesn't give a solution to the problem.

So what can we do fix this ?
There is a windows API function called InvalidateRect() that adds a rectangle to the windows update region (the region that has to be redrawn). If we invalidate the edit controls rectangle it will be set to be redrawn and a WM_PAINT message will be sent to it. By doing this we will ensure that every time the edit box is trying to redraw itself our WM_PAINT method is being called.

Main window procedure:


LRESULT CALLBACK winProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    {
	case WM_CREATE:
		{
			loadTexture();

			hEditControl = CreateWindowEx(WS_EX_TRANSPARENT, "EDIT","Edit control",
							WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | WS_TABSTOP,
							10,20,EDIT_WIDTH,EDIT_HEIGHT,hwnd,
							(HMENU)IDC_EDIT_CONTROL,GetModuleHandle(NULL),NULL);

			font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
			SendMessage(hEditControl, WM_SETFONT, (WPARAM)font, MAKELPARAM(FALSE, 0));

			oldWndProc = (WndProc)SetWindowLong(hEditControl, GWL_WNDPROC, (LONG)winProcEditControl);
		}
	break;
	case WM_CLOSE:
		DestroyWindow(hwnd);
	break;
	case WM_DESTROY:
		PostQuitMessage(WM_QUIT);
        break;
	case WM_CTLCOLORSTATIC:
	case WM_CTLCOLOREDIT:
		{
			// tell windows to update the edit box region
			RECT rc;
			GetClientRect((HWND)lParam,&rc);
			InvalidateRect((HWND)lParam,&rc,false);

			// we don't want that background to be drawn
			SetBkMode((HDC)wParam, TRANSPARENT);
			SetTextColor((HDC)wParam, RGB(255, 255, 255));
			return (LRESULT)GetStockObject(HOLLOW_BRUSH);
		}
	break;
	default:
		return DefWindowProc(hwnd, Msg, wParam, lParam);
	break;
    }

    return 0;
}

This is the "Example 3" example in the Examples file.

It might not be the most beautiful way to do it but it works (on the systems I have tested).
One might think that the use of InvalidateRect() is redundant. We can just place the painting code in a separate function and call it directly where InvalidateRect() is. This will work but if you have more than one edit box it will get tricky, then you will have to use GetDlgCtrlID() to get the ID of the control and then do a switch statement to determine which edit box to paint (if you want different textures on the edit boxes that is).

A very interesting thing is text selection, it appears to be drawn after the WM_PAINT method so text selection actually works even with textured background. I haven't looked into why this happens so if anyone wants to remove text selection I can't help you, I'm interested in hearing about the result though so send me a e-mail at drx [at] drx.dk if you find a way to remove text selection.

Conclusion

It might e a ugly way but who cares ? :)
If you have any questions, suggestions, information about another method or information about if it doesn't work please e-mail me at drx [at] drx.dk.

The code highlighting on this page is made by Ivan Sagalaev neat JavaScript.


Portions of this website were created using the free web resources available at www.elated.com and are copyright Elated Communications Ltd 1996-2002