CreatePen returning NULL on window resizing - c

So I am experimenting with Win32 API and trying to plot a line with colour gradient (by creating a custom Pen). The code is working fine and I am getting my desired result but when I start resizing my window, CreatePen() function starts giving NULL value, and thus Windows start using the default black pen.
This is my WndProc code:
(https://pastebin.com/xiyzX5fu for full code)
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient;
HDC hdc;
HPEN hPen;
PAINTSTRUCT ps;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
printf("%d\t%d\n", cxClient, cyClient); //Printing the resolution
hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 0, cyClient / 2, NULL);
int count = 0;
//Plotting 256 lines each of length (cxClient/256) with different color
for (int i = 0; i < cxClient; i += cxClient / 256)
{
if ((hPen = CreatePen(PS_SOLID, 10, RGB(count, 255, 255))) == NULL)
{
DWORD lasterror;
lasterror = GetLastError();
printf("We got a NULL Value and last error code was %lu\n", lasterror);
break;
}
else
SelectObject(hdc, hPen);
count += 1; // For uniform color gradient across width
LineTo(hdc, i, cyClient / 2);
}
DeleteObject(hPen);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
I am trying to debug it for the last few hours and here are some of my observations:
My calculation for RGB are correct. In fact, even a default pen
CreatePen(PS_SOLID, 1, 0x00000000) leads to same error.
The error is more profound if the increment value is smaller, i.e.
for i += cxClient/5 (plots only 5 lines) CreatePen would return
NULL values much less often while resizing as compared to i += cxClient/256 (plots 256 lines)
(Line 59)
Interesting One: I print the size of my Client Area using printf (Line 53). The moment CreatePen() returns a NULL value,
the console will stop printing the statement containing the Client
Area and will only print the NULL error statement (Line 65) again and again. It will
print both only when I add a break (Line 66) after printing the
NULL error statement.
I think I am not able to capture the error of CreatePen() using
GetlastError() (Line 64) correctly because it always prints 0
(ERROR_SUCCESS) on the console.
Based on these observations I think the problem doesn't lie in CreatePen() but in my way of calculating the length of each line.

You are creating the pen, selecting it into the dc, and then deleting it while it is still selected in the dc. This will case DeleteObject to fail. You need to remember the old pen that was selected, and then select that back into the dc before deleting your pen:
HPEN oldPen;
...
oldPen = SelectObject(hdc, hPen);
...
SelectObject(hdc, oldPen);
DeleteObject(hPen);

Yes, you are leaking GDI object. When the number of GDI objects in your process reaches 10,000, the system will no longer allow the creation of new GDI objects. This is why hPen will eventually return NULL.
Furthermore, as we saw last time, the maximum number of window manager
objects that can be created is around 32,700. Giving a program 10,000
is already a third of the total amount available. That’s already
pretty darned generous, if you ask me. Preventing a program from
running away and consuming all of the window manager objects is an
attempt to contain the damage caused by a runaway program. Even if a
program goes haywire, there’s still around 20,000 objects available
for the other programs to use.
Refer: Why is the limit of window handles per process 10,000?

Related

Access a variable from a different switch case (from WM_CREATE to WM_CTLCOLORSTATIC in the WinApi)

I'm trying to change the colour of a label in a WinApi window I'm making. I take hwndConnection, from CreateWindowW() in the WM_CREATE part of my switch statement, and try to pass it to hdcConnection() in the WM_CTLCOLORSTATIC part of my statement.
The problem is that hwndConnection is uninitialised, since it can't be passed from switch statement to switch statement. I'm therefore at a loss at how I should send it between the two.
I tried googling the answer. I found this, which didn't really help me. The accepted answer suggested moving CreateWindowW() out of WM_CREATE (which is how I interpreted it anyway), which causes no compiler errors, but instead a frozen screen. The second uses classes, and some weird syntax I've never seen before (I openly admit I don't know C++).
Here is a stripped down version of my code (I've only included WndProc to keep it consise, however I can include the rest if needed)
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HWND hwndConnection;
HDC hdcConnection;
switch (msg) {
case WM_CREATE:
hwndConnection = CreateWindowW(L"Static", L"Not Connected", WS_CHILD | WS_VISIBLE | SS_LEFT, 260, 0, 100, 20, hwnd, (HMENU)1, NULL, NULL);
break;
case WM_CTLCOLORSTATIC:
hdcConnection = GetDC(hwndConnection); // Uninitialised local variable 'hwndConnection' used
SetBkColor(hdcConnection, RGB(255, 0, 0));
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
case WM_COMMAND:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
Another way is to declare hwndConnection static:
static HWND hwndConnection(NULL);
If you are paranoid, you may want to check in your case WM_CTLCOLORSTATIC: if it was already set. However, I don't see how you can get WM_CTLCOLORSTATIC before WM_CREATE...
This code works for me:
case WM_CTLCOLORSTATIC:
if ((HWND)lParam == hwndConnection)
{
HDC hdcStatic = (HDC)wParam;
SetBkColor(hdcStatic, RGB(255, 0, 0));
return (LRESULT)GetStockObject(NULL_BRUSH);
}
break;
Your problem is that even if you move hWndConnection out of the switch so that it is in scope for both case branches, it is only initialized in the WM_CREATE branch (and WM_CTLCOLORSTATIC is only taken in a completely different call to the window procedure). However, you can use the GetDlgItem() function to get the handle of a child window, in this case:
case WM_CTLCOLORSTATIC:
hWndConnection = GetDlgItem(hWnd, 1); // 1 is the ID that was assigned as part of the CreateWindow call
hdc = GetDC(hWndConnection);
...
ReleaseDC(hdc);
break;

Not able to read value from EDIT box (windows programming and C)

I need help with this code. I need to set the focus to a edit button and read the value entered in the edit box and move it to a variable for further processing. This code creates a text prompt with TextOut() which says " Enter the value of mass:" and an editbox with an IDC_EDIT_MASS and hEditMASS next to it.
I am not able to read the value from edit box into variable mass.
And the code is as follows *
#define IDC_EDIT_MASS 103 // Edit box identifier
RESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
HWND hEditMASS;
HDC hDC;
PAINTSTRUCT Ps;
HFONT font;
float mass;
char msgMASS[]="Enter the value of mass:";
switch (message) /* handle the messages */
{
case WM_CREATE :
hEditMASS=CreateWindowEx(WS_EX_CLIENTEDGE, “EDIT",
"", WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOVSCROLL|ES_AUTOHSCROLL,
550,
200,
200,
20,
hwnd,
(HMENU)IDC_EDIT_MASS,
GetModuleHandle(NULL),
NULL);
Break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_EDIT_MASS:
SendMessage(hEditMASS,WM_GETTEXT, sizeof(buffer)/sizeof(buffer[0]),
reinterpret_cast<LPARAM>(buffer));
int ctxtlen=GetWindowTextlength(GetDlgItem(hwnd, IDC_EDIT_MASS));
GetWindowText(GetDlgItem(hwnd, IDC_EDIT_MASS), buffer,(cTxtLen + 1);
mass=atoi(buffer);
MessageBox(NULL,buffer,"Information",MB_ICONINFORMATION);
break;
}
Break;
case WM_SETFOCUS :
SetFocus (hwnd) ;
break;
case WM_PAINT:
hDC = BeginPaint(hwnd, &Ps);
//inputs prompts ...
TextOut(hDC,300,200,msgMASS,sizeof(msgMASS));
EndPaint(hwnd, &Ps);
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
hEditMASS is an local, automatic-storage-duration variable. You set it when the message is WM_CREATE. However, you then access it when the message is WM_COMMAND. Automatic-storage-duration variables do not retain their value between calls. In order for it to retain its value, you must either make it global or make it static, e.g.:
static HWND hEditMASS;
Keep in mind that you'll probably only be able to use your window procedure for one window now, since creating any other window with the same window procedure will end up using the same hEditMASS variable, and when you next try to access hEditMASS, it will point to the edit control in the most-recently-created window with that window procedure.

Subclassing list view to edit only its subitems

I wrote this small program which displays a list view and makes items and subitems editable.
I want to change this to make only the subitems editable. And I would like to make the list view window procedure stand on itself, that I don't have to forward WM_NOTIFY messages every time as I'm doing now in WndProcMain. And the purpose is that I don't use only one list view with editable subitems in my program, I'm going to use it in many different windows.
The LVN_ENDLABELEDIT notification is processed by WndProcList because the bEditing has to be changed. This flag is used for WM_PAINT when subitems have to be edited. This is a fix, otherwise the text in the first subitem disappears because it thinks the first item is being edited. However, I would like to also receive a message like LVN_ENDLABELEDIT in the window procedure of the list view owner window (in this case WndProcMain), because I want to manipulate the user input also.
Please ask if you have questions.
Thanks in advance
Midas
WNDPROC wpOrigEditProc;
RECT rcSubItem;
LRESULT CALLBACK WndProcEditList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_WINDOWPOSCHANGING:
{
WINDOWPOS *pos = (WINDOWPOS*) lParam;
pos->x = rcSubItem.left;
pos->cx = rcSubItem.right;
}
break;
default:
return CallWindowProc(wpOrigEditProc, hWnd, uMsg, wParam, lParam);
}
return 1;
}
LRESULT CALLBACK WndProcList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hEdit;
static RECT rc;
static LVITEM lvI;
static unsigned char bEditing = 0;
switch (uMsg) {
case WM_NOTIFY:
switch (((NMHDR*) lParam)->code) {
case NM_CLICK:
lvI.iItem = ((NMITEMACTIVATE*) lParam)->iItem;
lvI.iSubItem = ((NMITEMACTIVATE*) lParam)->iSubItem;
break;
case NM_DBLCLK:
SendMessage(hWnd, LVM_EDITLABEL, lvI.iItem, 0);
break;
case LVN_BEGINLABELEDIT:
{
char text[32];
bEditing = 1;
hEdit = (HWND) SendMessage(hWnd, LVM_GETEDITCONTROL, 0, 0);
rcSubItem.top = lvI.iSubItem;
rcSubItem.left = LVIR_LABEL;
SendMessage(hWnd, LVM_GETSUBITEMRECT, lvI.iItem, (long) &rcSubItem);
rcSubItem.right = SendMessage(hWnd, LVM_GETCOLUMNWIDTH, lvI.iSubItem, 0);
wpOrigEditProc = (WNDPROC) SetWindowLong(hEdit, GWL_WNDPROC, (long) WndProcEditList);
lvI.pszText = text;
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
}
break;
case LVN_ENDLABELEDIT:
bEditing = 0;
SetWindowLong(hEdit, GWL_WNDPROC, (long) wpOrigEditProc);
if (!lvI.iSubItem) return 1;
lvI.pszText = ((NMLVDISPINFO*) lParam)->item.pszText;
if (!lvI.pszText) return 1;
SendMessage(hWnd, LVM_SETITEMTEXT, lvI.iItem, (long) &lvI);
break;
default:
return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
}
break;
case WM_PAINT:
if (bEditing) {
RECT rcItem;
if (lvI.iSubItem > 0) {
rcItem.left = LVIR_LABEL;
if (SendMessage(hWnd, LVM_GETITEMRECT, lvI.iItem, (long) &rcItem))
ValidateRect(hWnd, &rcItem);
}
}
default:
return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK WndProcMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hList;
static RECT rc;
switch (uMsg) {
case WM_NOTIFY:
switch (((NMHDR*) lParam)->code) {
case NM_CLICK:
case NM_DBLCLK:
case LVN_BEGINLABELEDIT:
case LVN_ENDLABELEDIT:
return CallWindowProc(WndProcList, ((NMHDR*) lParam)->hwndFrom, uMsg, wParam, lParam);
}
break;
case WM_CREATE:
{
LVCOLUMN lvc;
LVITEM lvI;
unsigned int i;
float vertex;
char text[32];
hList = CreateWindow(WC_LISTVIEW, 0, WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, rc.left, rc.top, rc.right, rc.bottom, hWnd, 0, hInstance, 0);
SendMessage(hList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
SetWindowLong(hList, GWL_WNDPROC, (long) WndProcList);
lvc.mask = LVCF_WIDTH;
lvc.cx = 30;
SendMessage(hList, LVM_INSERTCOLUMN, 0, (LPARAM) &lvc);
lvc.mask = LVCF_TEXT;
lvc.pszText = "Vertex";
SendMessage(hList, LVM_INSERTCOLUMN, 1, (LPARAM) &lvc);
SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);
lvI.mask = LVIF_TEXT;
lvI.pszText = text;
for (i = 0; i < 10; i++) {
vertex = (float) i;
lvI.iItem = i;
lvI.iSubItem = 0;
sprintf(text, "%d", i);
SendMessage(hList, LVM_INSERTITEM, 0, (LPARAM) &lvI);
lvI.iSubItem = 1;
sprintf(text, "%f, %f, %f", vertex - 1, vertex, vertex + 1);
SendMessage(hList, LVM_SETITEM, 0, (LPARAM) &lvI);
}
}
break;
case WM_SIZE:
GetClientRect(hWnd, &rc);
MoveWindow(hList, rc.left, rc.top, rc.right, rc.bottom, 1);
SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
I posted an answer related to the question but it was in C#. Never had much of expertise in winapi rather than playing with it as a hobbyist long ago, The answer at the end of this post looks promising -- http://cboard.cprogramming.com/windows-programming/122733-%5Bc%5D-editing-subitems-listview-win32-api.html
The first problem that cropped up for me in trying to compile your code is the fact that you either aren't compiling for Unicode, or you're using the non-Unicode sprintf function to format text. That's the first thing that needs to be fixed: Windows applications have been fully Unicode for over a decade. Replace every instance of char variable declarations with wchar_t (or TCHAR), prefix your string literals with L (or surround them with the TEXT() macro), and quickly replace the calls to sprintf with calls to wsprintf. As the documentation indicates, there are certainly better functions to use than wsprintf, but the same is true for sprintf, and this gets the code to compile with minimum effort.
The other thing that looks non-idiomatic to me is your use of the Get/SetClassLong and Get/SetWindowLong functions. Nowadays, I always write code with 64-bit portability in mind, and therefore I'd replace those with the Get/SetClassLongPtr and Get/SetWindowLongPtr macros, which automatically resolve to the correct function call, depending on if you're compiling for x86 or x64. This isn't a deal-breaker, though.
You first asked if there was a way to handle the WM_NOTIFY message directly from the sub-classed control by forwarding them automatically. Unfortunately, this is not possible. The Win32 model is such that parents always own their children, and it is thus their responsibility to handle events. I agree with your intuition on the separation of concerns, but the only way to make this happen is to explicitly forward the message from the parent to the appropriate child control yourself. Frameworks like MFC (which encapsulate the Win32 API) do this for you apparently automatically, still have to forward the notification messages from the parent to the child. They do it using something called "message reflection", which you can read about here. There's nothing stopping you from implementing something similar in your own application, but at a certain point you have to stop and ask yourself if it isn't worth using one of the many available GUI frameworks just for this sort of thing.
Anyway, as I understand it, the main thrust of your question is this:
I want to change this to make only the subitems editable.
That seems like a pretty simple fix. All you have to do is check in the LVN_BEGINLABELEDIT notification message handler that the user has, in fact, requested to edit a sub-item. Since you've used it elsewhere in your code, you know that the LVITEM.iSubItem member gives you either the one-based index of the sub-item, or 0 if the structure refers to an item rather than a sub-item.
So insert this line to ensure that lvI.iSubItem is not equal to 0 at the top of the LVN_BEGINLABELEDIT handler:
if (lvI.iSubItem == 0) return TRUE; // prevent editing
As the documentation for the LVN_BEGINLABELEDIT message indicates, returning FALSE allows the user to edit the label, and returning TRUE prevents them from editing. Since we return TRUE, we prevent the edit of anything but sub-items before the edit even starts.
It looks to me like you already tried to do something similar in the LVN_ENDLABELEDIT notification message handler with this line:
if (!lvI.iSubItem) return 1;
but that's too late! If the edit is already ending, then you've already given the user the impression that they were able to edit the main item, which you don't want to do. Take that line out, and it should work as expected.
Note that your implementation does have at least one glaring bug: you don't prevent the user from modifying the contents of a sub-item to a string longer than 32 characters, but your code populating the editing control only accepts a string up to 32 characters long:
TCHAR text[32];
// ... snip ...
lvI.pszText = text;
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
Writing that code the correct way would be (and I suspect that's why you haven't done it the right way) a giant pain in the ass. Typically, I will create a string buffer that I think is long enough, try to get the text of the sub-item, and check the return value of the LVM_GETITEMTEXT message. The return value tells me how many characters were copied into the string buffer. If the number of characters copied indicates that it entirely filled the available space in the string buffer, I'll make the buffer larger (perhaps doubling the size), and then try to send the LVM_GETITEMTEXT message again. As I recall, MFC does something similar. Told you it was a pain, but it's worth getting these things right.
A simpler solution (although more limiting) would be to prevent the user from ever setting the length of one of the sub-items to a string of text longer than 32 characters. Then you wouldn't have to worry about handling long input because you'd know it would never be there, and the user would never be confused about the behavior of your control. To do that, send the edit control an EM_LIMITTEXT message at the end of your LVN_BEGINLABELEDIT handler:
case LVN_BEGINLABELEDIT:
{
// ... snip ...
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
// (begin new code)
SendMessage(hEdit, EM_LIMITTEXT, lvI.cchTextMax, 0);
}
Now the user can't enter more than the allowed number of characters, so your code knows that you will never have to deal with any more than that being there (unless you write code to place them there yourself, in which case...).
All of that said, I think I agree with Hans:
Ugh, you'll fight glitches forever. There's little point with grid controls universally available.

Where do I add my actual program after I establish a Window using WinMain() and WindowProc()? (C++)

First of all, sorry for my ignorance of the window creation process. Today is actually the first day of my experimenting with it.
I started to code a text based game a few days ago and I have the main menu, and 3 or 4 different functions that control various things with text. I was then advised to look into Windows API and create a window for the program. I have created the window which can be seen here:
#include <Windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PWSTR nCmdLine, int nCmdShow)
{
const wchar_t CLASS_NAME[] = L"WindowClass";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.lpszClassName = CLASS_NAME;
wc.hInstance = hInstance;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx( //This creats a new instance of a window
0,
CLASS_NAME,
L"MyFirstWindow",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
500,
500,
NULL,
NULL,
hInstance,
NULL);
if(hwnd == 0)
return 0;
ShowWindow(hwnd,nCmdShow);
nCmdShow = 1;
MSG msg = { };
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_DESTROY:PostQuitMessage(0); return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd,&ps);
FillRect(hdc,&ps.rcPaint,(HBRUSH)(COLOR_WINDOW+5));
EndPaint(hwnd, &ps);
}return 0;
case WM_CLOSE:
{
if(MessageBox(hwnd,L"Do you want to exit?",L"Exit",MB_OKCANCEL)==IDOK)
DestroyWindow(hwnd);
} return 0;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
This looks a bit messy, but you probably will not need it anyway.
So at this point I have my original program and this program that creates a window. My question is how, or even where, do I put my original program's code so that it can be incorporated into the window.
If you are reading this and thinking I'm a total moron for doing it this way, I'm open to ideas that are a lot simpler than what I'm doing right now.
Your code is the standard boilerplate for creating a window using c and win32 API functions. I recommend that you modify the message pump (it's the while loop in middle calling GetMessage). Instead do this:
Run an infinite loop
Peek a message
If the message is not there, execute your code
Else process messages including the quit message
Here's what the code should look like:
while (1)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//Your game code
}
}
I also want to point, that while learning game programming using C and calling Win32 API is a worthy goal, you might want to look at XNA game studio.
The reason I am recommending it is because it is easier to learn and you can make much more interesting games faster. Here are a few links to get you started if you are interested.
http://joshua-w-wise78.livejournal.com/
http://www.krissteele.net/blogdetails.aspx?id=72
http://www.codeproject.com/KB/game/xna1.aspx
http://msdn.microsoft.com/en-us/library/bb203894.aspx
If your original program was a console app, that read input and printed output, then you will probably want to get input from your window to implement your game.
Instead of looking at it from the perspective of read user input from stdin then generate output to stdout, you have to think of it from the view of window messaging. So you need to process the WM_KEYDOWN messages, you can then use DrawText() to show the user input in your client area, or you could use a c++ RichEdit control. Once you process the WM_KEYDOWN messages you know what the user has pressed and then your program can do it's thing (maybe being triggered to process an accumalated buffer of characters whenever the WM_KEYDOWN key is equal to the enter key?) and write the output to your client area using DrawText() or send WM_KEYDOWN messages to your richedit window using SendMessage().
I think this is what you meant by a text based game, anyway, you just have to start thinking of doing everything by monitoring windows messages. Anything that the user does to your window, Windows will send a message to it to let it know.

How to eliminate flicker on a sizable dialog?

I've got a sizable dialog with one child window - a list control. When the dialog is re-sized, I re-size the list control appropriately; it is basically anchored to all 4 edges of the dialog. The problem is that during sizing there is noticeable flicker around the edges of the list control, especially when the scroll bars are present. I am a novice in Win32 GUI stuff, so I don't really know how to handle this. I've seen a lot of articles about flicker-free drawing, but they are all about individual custom-drawn controls and none of them deal with flicker-free drawing of a dialog as a whole. How can I get this to work without flickering so much?
My actual dialog has multiple controls obviously, but here is a minimal code example that reproduces the issue (IDC_LIST1 is a list control in Report view, IDD_DIALOG2 has WS_CLIPCHILDREN style set).
#define NUM_COLUMNS 8
#define NUM_ROWS 32
RECT rcDialog2WindowOriginal;
RECT rcDialog2ClientOriginal;
RECT rcList1ClientOriginal;
INT_PTR Dialog2_OnInitDialog(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
GetWindowRect(hDlg, &rcDialog2WindowOriginal);
GetClientRect(hDlg, &rcDialog2ClientOriginal);
GetWindowRect(GetDlgItem(hDlg, IDC_LIST1), &rcList1ClientOriginal);
ScreenToClient(hDlg, ((LPPOINT)&rcList1ClientOriginal));
ScreenToClient(hDlg, ((LPPOINT)&rcList1ClientOriginal) + 1);
SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
TCHAR szText[32];
// add some columns
LVCOLUMN col;
ZeroMemory(&col, sizeof(LVCOLUMN));
col.mask = LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;
col.cx = 60;
col.pszText = szText;
for(int i = 0; i < NUM_COLUMNS; i++)
{
col.iSubItem = i;
_stprintf_s(szText, 32, _T("Column %d"), col.iSubItem);
SendDlgItemMessage(hDlg, IDC_LIST1, LVM_INSERTCOLUMN, col.iSubItem, LPARAM)&col);
}
// add some items
LVITEM item;
ZeroMemory(&item, sizeof(LVITEM));
item.mask = LVIF_TEXT;
item.pszText = szText;
for(int i = 0; i < NUM_ROWS; i++)
{
item.iItem = i;
for(int j = 0; j < NUM_COLUMNS; j++)
{
item.iSubItem = j;
_stprintf_s(szText, 32, _T("Item %d, SubItem %d"), i, j);
if(j == 0)
{
SendDlgItemMessage(hDlg, IDC_LIST1, LVM_INSERTITEM, 0, (LPARAM)&item);
}
else
{
SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETITEM, 0, (LPARAM)&item);
}
}
}
// autosize the columns
for(int i = 0; i < NUM_COLUMNS; i++)
{
SendDlgItemMessage(hDlg, IDC_LIST1, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE);
}
return TRUE;
}
INT_PTR Dialog2_OnGetMinMaxInfo(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
LPMINMAXINFO lpMinMaxInfo = (LPMINMAXINFO)lParam;
// don't allow dialog to be resized smaller than original size
lpMinMaxInfo->ptMinTrackSize.x = rcDialog2WindowOriginal.right - rcDialog2WindowOriginal.left;
lpMinMaxInfo->ptMinTrackSize.y = rcDialog2WindowOriginal.bottom - rcDialog2WindowOriginal.top;
return TRUE;
}
INT_PTR Dialog2_OnSize(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
int cx = LOWORD(lParam);
int cy = HIWORD(lParam);
// anchor the list control to all edges of the dialog
int left_delta = rcList1ClientOriginal.left - rcDialog2ClientOriginal.left;
int right_delta = rcDialog2ClientOriginal.right - rcList1ClientOriginal.right;
int top_delta = rcList1ClientOriginal.top - rcDialog2ClientOriginal.top;
int bottom_delta = rcDialog2ClientOriginal.bottom - rcList1ClientOriginal.bottom;
int left = left_delta;
int top = top_delta;
int width = cx - left_delta - right_delta;
int height = cy - top_delta - bottom_delta;
HWND hWndList1 = GetDlgItem(hDlg, IDC_LIST1);
SetWindowPos(hWndList1, NULL, left, top, width, height, SWP_NOZORDER);
return TRUE;
}
INT_PTR Dialog2_OnClose(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
EndDialog(hDlg, IDOK);
return TRUE;
}
INT_PTR CALLBACK Dialog2_DialogProc(HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
switch(nMsg)
{
case WM_INITDIALOG:
return Dialog2_OnInitDialog(hDlg, wParam, lParam);
case WM_GETMINMAXINFO:
return Dialog2_OnGetMinMaxInfo(hDlg, wParam, lParam);
case WM_SIZE:
return Dialog2_OnSize(hDlg, wParam, lParam);
case WM_CLOSE:
return Dialog2_OnClose(hDlg, wParam, lParam);
}
return FALSE;
}
Update
After taking a look at many other Windows applications (even ones written by Microsoft), every single one of them suffers the same flickering issues. It is particularly evident when resizing a window with both a status bar and scroll bar from the top left. I guess I will just have to live with it.
A lot of flicker comes from WM_ERASEBKGRD, handle this message and just return TRUE;
You can do several things.
Handle WM_ERASE yourself. You may still need to erase some of the background, but you can draw 4 rectangles around the edges of the window to fill the background area of the dialog without overdrawing the rectangle in the middle where your child control sits.
After calling SetWindowPos, try calling UpdateWindow() to immediately repaint the window (try it on both the list view control and your own window). This minimises the time between you making the size change and the redraw being completed.
Another approach is double buffering (you draw the entire window surface to an offscreen bitmap, and only draw it to the screen when it's all finished. This minimises the time between the start and end of the update proicess on screen, so reduces flicker. This is harder to achieve with a dialog and a windows control though, so not really applicable in your case).
In general, don't be afraid to experiment with things. Try doing things in different orders, etc and see what the results are. If something (like UpdateWindow()) doesn't give a noticeable improvement, then it's easy to remove the code again, and try something else until you get the best results.
edit - additional ideas
see also this SO question
When updating controls you can also suspend and resume repainting (BeginUpdate() and EndUpdate()) to stop them drawing more than once when you are adding many items, etc. THis is not likely to help with window resizing though.
After taking a look at many other Windows applications (even ones written by Microsoft), every single one of them suffers the same flickering issues. It is particularly evident when resizing a window with both a status bar and scroll bar from the top left. I guess I will just have to live with it.
If you try to replace any window's default windowsproc with the following code, the window will basically be updating zero pixels on the screen (besides the border and the title bar; see WM_NCPAINT and other NC-messages for that).
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_ERASEBKGND:
// Lie and tell Windows that this window has cleared its background.
return (-1);
case WM_PAINT:
// Lie and tell Windows that this window has completed all its painting.
ValidateRect(hwnd, nullptr);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
This tells us that you are free to paint the pixels however you like. With or without flickering. It's up to you.
How?
Create a compatible bitmap (CreateCompatibleBitmap) and select it into the device context of the window within WM_PAINT. When you're done drawing, BitBlt() that bitmap to the actual window DC and voila; flicker free rendering.

Resources