Advertisement

Flickering title bar and menu bar text in Win32 double buffered code

Started by March 07, 2021 10:03 AM
6 comments, last by Endurion 3 years, 8 months ago

While testing my program, I've discovered an annoying issue that I have not been able to fix. When another applications window is being dragged over the my window, the title bar and menu bar text will flicker. This happens with anything, including folders, but is most noticeable when the ‘active’ foreground window is something like Media Player. I suspect it is something to do with the way the double buffering is implemented or where I am doing my drawing. I have tried a number of different things to remedy it:

  • using WS_CLIPCHILDREN and WS_EX_COMPOSITED in CreateWindowEx
  • removing all code that blits or draws anything to the client area
  • using runtime created menus instead of resource created menus and vice versa
  • using PeekMessage instead of GetMessage
  • using a region to only update the client area sans menu bar
  • entirely removing the timer
  • tried alternate double buffering code (all have the same issue, or other artifacts)

Below is the full code to my program. I would greatly appreciate if somebody knows if there is a way to fix this. Feel free to point out anything stupid :P

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <time.h>
#include "resource.h"

const char g_szClassName[] = "GDIWindowClass";

HDC HDCMem;

HBITMAP g_hbmBackm, g_hbmChar, g_hbmChar2, g_hbmChar3, g_hbmChar4, g_hbmChar5 = NULL;

HWND b_btnPrev = NULL;
HWND b_btnNext = NULL;

HFONT hSansFont = NULL;
HFONT hSerifFont = NULL;

HCURSOR hCursorArrow = NULL;

struct character* current_character;
struct character {

    char name [36];
    char source [36];
    char about [300];
    HBITMAP portrait;

    };

struct character characters [13] =
{
    {
        "Name1", "Source1", "About1", (HBITMAP)&g_hbmChar
    },

    {
        "Name2", "Source2", "About2", (HBITMAP)&g_hbmChar2
    },

    {
        "Name3", "Source3", "About3", (HBITMAP)&g_hbmChar3
    },

    {
        "Name4", "Source4", "About4", (HBITMAP)&g_hbmChar4
    },
    
    {
        "Name5", "Source5", "About5", (HBITMAP)&g_hbmChar5
    }

};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{

    WNDCLASSEX wc;
    HWND hWnd;
    MSG Msg;

    wc.cbSize         = sizeof ( WNDCLASSEX );
    wc.style          = 0;
    wc.lpfnWndProc    = WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hInstance      = hInstance;
    wc.hIcon          = LoadIcon ( hInstance, MAKEINTRESOURCE(IDI_ICON) );
    wc.hCursor        = hCursorArrow;
    wc.hbrBackground  = 0;
    wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName  = g_szClassName;
    wc.hIconSm        = NULL;

    RegisterClassEx ( &wc );

    hWnd = CreateWindowEx ( 0, g_szClassName, "Slideshow", WS_CLIPCHILDREN | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME, CW_USEDEFAULT, CW_USEDEFAULT, 545, 292, NULL, NULL, hInstance, NULL );

    ShowWindow ( hWnd, SW_SHOW );

    while ( GetMessage ( &Msg, NULL, 0, 0 ) > 0)
    {
        TranslateMessage( &Msg );
        DispatchMessage ( &Msg );
    }
    return Msg.wParam;

}

BOOL CALLBACK AboutDlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
	switch(Message)
	{
		case WM_INITDIALOG:
		return TRUE;

		case WM_COMMAND:
			switch(LOWORD(wParam))
			{
				case IDOK:
					EndDialog(hWnd, IDOK);
				break;

				case IDCANCEL:
					EndDialog(hWnd, IDCANCEL);
				break;
			}
		break;

		default:
			return FALSE;
	}
	return TRUE;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hdc, memDC, memDCBack;
	PAINTSTRUCT ps;

	static HBITMAP hBmp, hBmpBack;
	static RECT rect;
	static RECT textrect;

	struct tm *timeinfo;
    	time_t tim = time(NULL);
    	timeinfo = localtime(&tim);

    	static char sTime[128];

	switch(iMsg)
	{
	case WM_CREATE:

	    hCursorArrow = LoadCursor( NULL, IDC_ARROW );

        hSansFont = CreateFont ( 8, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET, 0, 0, DEFAULT_QUALITY, FF_DONTCARE, "MS Sans Serif" );
        hSerifFont = CreateFont ( 21, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET, 0, 0, DEFAULT_QUALITY, FF_DONTCARE, "MS Serif" );

        characters[0].portrait = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_CHR1), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
        characters[1].portrait = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_CHR2), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
        characters[2].portrait = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_CHR3), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
        characters[3].portrait = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_CHR4), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
        characters[4].portrait = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_CHR5), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);

        b_btnPrev  = CreateWindow ("button", "Previous", WS_VISIBLE | WS_CHILD, 193, 195, 70, 21, hwnd, (HMENU) IDB_BTN_PREV, NULL, NULL);
        b_btnNext  = CreateWindow ("button", "Next",  WS_VISIBLE | WS_CHILD, 264, 195, 70, 21, hwnd, (HMENU) IDB_BTN_NEXT, NULL, NULL);

        SendMessage ( b_btnPrev, WM_SETFONT, ( int ) hSansFont, TRUE );
        SendMessage ( b_btnNext, WM_SETFONT, ( int ) hSansFont, TRUE );

        SetRect(&textrect, 32, 88, 320, 320);

        current_character = &characters[0];
        GetClientRect(hwnd, &rect);

		hBmpBack = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, MAKEINTRESOURCE(IDB_BACK));
		
		SetTimer(hwnd, 0, 10, NULL);
		break;

	case WM_TIMER:

		localtime(&tim);
        	strftime(sTime, 80, " %I:%M %p", timeinfo);

		hdc = GetDC(hwnd);

		if(hBmp == NULL)
        	hBmp = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
		memDC = CreateCompatibleDC(hdc);
		memDCBack = CreateCompatibleDC(memDC);

		SelectObject(memDC, hBmp);
		SelectObject(memDCBack, hBmpBack);

		BitBlt(memDC, 0, 0, rect.right, rect.bottom, NULL, 0, 0, WHITENESS);
		BitBlt(memDC, 0, 0, 537, 246, memDCBack, 0, 0, SRCCOPY);

		SelectObject(memDCBack, current_character->portrait);
        	BitBlt(memDC, 364, 26, 150, 190, memDCBack, 0, 0, SRCCOPY);

		SelectObject(memDC, hSerifFont);
        	SetBkMode(memDC, TRANSPARENT);
        	SetTextColor(memDC, RGB(120, 120, 120));

		TextOut(memDC, 32, 35, current_character->name, strlen(current_character->name));
		TextOut(memDC, 32, 56, current_character->source, strlen(current_character->source));
		TextOut(memDC, 96, 196, sTime, strlen(sTime));

		SelectObject(memDC, hSansFont);
		SetTextColor(memDC, RGB(0, 0, 0));
		DrawText(memDC, current_character->about, -1, &textrect, DT_WORDBREAK);

		DeleteDC(memDC);
		DeleteDC(memDCBack);
		ReleaseDC(hwnd, hdc);

		InvalidateRgn(hwnd, NULL, FALSE); 
                               
		break;

    case WM_SETCURSOR:
        if( (HWND)wParam == hwnd)
        {
             SetCursor(hCursorArrow);
             return TRUE;
        }
        break;

	case WM_PAINT:

		hdc = BeginPaint(hwnd, &ps);

		memDC = CreateCompatibleDC(hdc);
		SelectObject(memDC, hBmp);
		BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);

		DeleteDC(memDC);
		EndPaint(hwnd, &ps);
		break;

        case WM_GETMINMAXINFO:
        {
            MINMAXINFO *minmax = (MINMAXINFO *)lParam;
            minmax->ptMinTrackSize.x = 545;
            minmax->ptMinTrackSize.y = 292;
            minmax->ptMaxTrackSize.x = 545;
            minmax->ptMaxTrackSize.y = 292;
        }
         break;

        case WM_COMMAND:
    {

       if (LOWORD(wParam) == ID_FILE_EXIT)
        {
          DestroyWindow(hwnd);
            return 0;
        }

         if (LOWORD(wParam) == ID_HELP_ABOUT)
        {
          DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
            return 0;
        }

         if (LOWORD(wParam) == IDB_BTN_NEXT)
        {
            if (current_character == &characters[6])
            {
                current_character = &characters[0];
            }
            else {
                current_character+=1;
            }
        }

         if (LOWORD(wParam) == IDB_BTN_PREV)
        {
            if (current_character == &characters[0])
            {
                current_character = &characters[6];
            }
            else {
                current_character-=1;
            }
        }
    }
      break;

    case WM_DESTROY:

        for (int i = 0; i < 4; i++ ) {
            DeleteObject(characters[i].portrait);
            DeleteObject(characters[i].about);
            DeleteObject(characters[i].source);
            DeleteObject(characters[i].name);
        };

        DeleteObject(hSansFont);
        DeleteObject(hSerifFont);

        KillTimer(hwnd, 1);
        PostQuitMessage ( 0 );
         break;
	}

	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

None

if(hBmp == NULL)
hBmp = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
memDC = CreateCompatibleDC(hdc);
memDCBack = CreateCompatibleDC(memDC);
SelectObject(memDC, hBmp);
SelectObject(memDCBack, hBmpBack); 

Please wrap the block for that if condition in curlies, so it's easier to see what you want to do

Why do you need to create these every TIMER? I realise that you delete also, but why not just do all of that once, and use the contexts across the entire lifespan where you'll need to draw?

E: and in PAINT too, for that matter.

Advertisement

@supervga

The if statement is only for the hBmp to create it once.

  • I've fixed the parenthesis and initialized it to NULL, which it wasn't previously.
  • I've moved everything over to WMPAINT.
  • I've replaced BeginPaint and EndPaint with GetDC/ ReleaseDC.

However, now when I try to call a function from WM_COMMAND to update the text, it doesn't show. So I still have to put the TextOut / TextDraw calls into WM_PAINT. Additionally, If I create or delete the DC's outside of WM_PAINT, I just get a black window. Apologies for the messy code, I have mostly neglected to clean it up.

case WM_PAINT:

	hdc = GetDC(hwnd);

	if(hBmp == NULL){
        hBmp = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        }

        memDC = CreateCompatibleDC(hdc);
	memDCBack = CreateCompatibleDC(memDC);

	SelectObject(memDC, hBmp);
	BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);

	BitBlt(memDC, 0, 0, rect.right, rect.bottom, NULL, 0, 0, WHITENESS);

	SelectObject(memDCBack, g_hbmBackm);
	BitBlt(memDC, 0, 0, 537, 246, memDCBack, 0, 0, SRCCOPY);

	SelectObject(memDCBack, current_character->portrait);
        BitBlt(memDC, 364, 26, 150, 190, memDCBack, 0, 0, SRCCOPY);

        SelectObject(memDC, hSerifFont);
        SetBkMode(memDC, TRANSPARENT);
        SetTextColor(memDC, RGB(120, 120, 120));
        strftime(sTime, 80, " %I:%M %p", timeinfo);
        
        TextOut(memDC, 96, 196, sTime, strlen(sTime));

	DeleteDC(memDC);
	DeleteDC(memDCBack);
	ReleaseDC(hwnd, hdc);

	break;

None

@Airbatz That's somewhat better. I still need to wrap my head around what it does, though. Namely the functionality surrounding current_character - that's the one that breaks easily, right?

I get that adding and subtracting from a pointer like this should, but I'm pretty scared of manipulating pointers so I'd never do it despite it being supposed to work.
Instead, if I want to "point" to something in an array, I just leave it as an integer, and do a bounds check on it whenever it's changing.

For instance, in the case of "next":

current_character += 1;
if (current_character >= sizeof(characters) / sizeof(characters[0]))
{
  current_character = 0;
}

Then I'd start every case of WM_TIMER with

character* current_character_ptr = characters[current_character];
...
current_character_ptr->name
...

etc.
I'd advise you to switch to C++ if you can, to make it less painful to handle collections and determine sizes. That might not even be your issue here, but I'm still getting to know your program.

If you never invalidate anything and paint just one frame, does it come out alright? I suppose the issue would be between that and the next frame, if that is the case.

Thanks for your reply. The current_character = &characters[0]; line is meant to set the character I want to display when the program starts. Thank you for that suggestion, I will probably re-do a portion of this code anyway. At it's core, the program is just a little slideshow which displays different entries with some text and a picture for each. It also displays the current position in the array and system time. It has been suggested to me previously to use C++ to make things easier, but I feel comfortable with just using C for the time being, at least until I gain a deeper understanding of the API.

Sample of how the program looks.

None

Don't remove BeginPaint/EndPaint.

When you handle WM_PAINT, use BeginPaint/EndPaint, but also return 0. Do not pass it on to DefWindowProc.

You might want to check out WM_ERASEBKGND, try handling it by returning simply 1. That only works, if you fully redraw everything in your WM_PAINT handler.

Everything would probably be easier, if you used real controls for the texts and the picture. Unless you want that custom background, then you're better off drawing everything yourself.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Advertisement

@Endurion

I've read elsewhere that GetDC/ ReleaseDC can be used instead of BeginPaint/ EndPaint. Earlier, I was using static controls for the text, but the text was changing slow enough to overlap the text before it. I haven't tried WM_ERASEBKND in my case but at present the text doesn't show when I try to blit from another function. Is there something I'm missing in the double buffer?

None

You can use those, but then you have to call ValidateRect manually.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

This topic is closed to new replies.

Advertisement