Win32 API child window flicker - c

I'm new to the Win32 API and can't understand why all of my child windows are flickering so much whenever I input something into them. Does anyone know why it is flickering? Below is what I coded; it doesn't seem to work as intended. Even though I'm sure I handled all of my handles correctly and used the pointers properly throughout the code.
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#define CONVERT_TAX 1
LRESULT CALLBACK WindowProcedure(HWND,UINT,WPARAM,LPARAM);
void AddMenus(HWND hWnd);
void AddControls(HWND hWnd);
int TaxConversion(int TaxRate);
HWND hOut;
HWND hTax;
int main(HINSTANCE hInst, HINSTANCE vPrevInst,LPSTR args, int ncmdshow){
WNDCLASSW wc ={};
wc.hIcon=LoadIcon(NULL, IDI_APPLICATION); //The icon handle for the window class. LoadIcon(NULL, IDI_APPLICATION) loads the default application icon.
wc.cbWndExtra=0; //The number of extra bytes to allocate for each individual window. Do not confuse this with cbClsExtra, which is common to all instances. This is often 0.
wc.cbClsExtra=0; //The number of extra bytes to allocate for the window class. For most situations, this member is 0.
wc.style=0; //
wc.hbrBackground=(HBRUSH)COLOR_WINDOW; //A handle to the background brush. GetStockObject (WHITE_BRUSH) gives a handle to a white brush. The return value must be cast because GetStockObject returns a generic object.
wc.hCursor=LoadCursor(NULL,IDC_ARROW); //The cursor handle for the window class. LoadCursor(NULL, IDC_ARROW) loads the default cursor.
wc.hInstance=hInst; //The instance handle. Just assign the hInst argument in WinMain to this field.
wc.lpszClassName=L"myWindowClass"; //The class name that identifies this window class structure. In this example, the CLSNAME global variable stores the window class name.
wc.lpfnWndProc=WindowProcedure; //Stores the address of the window procedure, a function that handles events for all windows that are instances of this window class.
if(!RegisterClassW(&wc))
return -1;
CreateWindowW(L"myWindowClass",L"My Window",WS_OVERLAPPEDWINDOW|WS_VISIBLE,500,500,500,500,NULL,NULL,NULL,NULL);
MSG msg = {0};
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProcedure(HWND hWnd,UINT msg,WPARAM wp,LPARAM lp){ //Procedures for Events
switch(msg){
case WM_COMMAND:
switch(wp){
case CONVERT_TAX:
char Tax[100];
GetWindowText(hTax,Tax,100);
SetWindowText(hOut,Tax);
break;
}
case WM_CREATE:
AddControls(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hWnd,msg,wp,lp);
}
return 0;
}
void AddControls(HWND hWnd){
CreateWindowW(L"Static",L"Enter Tax Rate",WS_CHILD|WS_VISIBLE,1,1,90,90,hWnd,0,0,0);
hTax=CreateWindowW(L"Edit",L"",WS_CHILD|WS_BORDER|WS_VISIBLE,1,92,90,90,hWnd,0,0,0);
CreateWindowW(L"Button",L"Find Amount",WS_CHILD|WS_BORDER|WS_VISIBLE,1,183,90,90,hWnd,(HMENU)CONVERT_TAX,0,0);
hOut=CreateWindowW(L"Edit",L"",WS_CHILD|WS_BORDER|WS_VISIBLE,1,267,90,90,hWnd,0,0,0);
}

I see two mistakes in your code:
when setting wc.hbrBackground, you must add +1 to the color value, per the documentation:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw
hbrBackground
Type: HBRUSH
A handle to the class background brush. This member can be a handle to the physical brush to be used for painting the background, or it can be a color value. A color value must be one of the following standard system colors (the value 1 must be added to the chosen color)...
wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); // <-- add +1
your WM_COMMAND handler is missing a break after the switch block, so every command message received will fall through into your WM_CREATE handler, thus creating more and more child controls.
case WM_COMMAND:
switch(wp){
case CONVERT_TAX:
char Tax[100];
GetWindowText(hTax,Tax,100);
SetWindowText(hOut,Tax);
break;
}
break; // <-- add this

Related

How would I go about disabling the close button?

First of all, let me get my point for this:
This is an emergency alert system, it pulls from a website which we manipulate in the back office, the program can be minimized but cannot be closed (easily, at least none of the people that will use it will know how). As you can see, if the program was closed the Emergency Alerts wouldn't be seen/heard....that's an issue. I have to deploy this application on over 200 computers so I want it simple, I don't want to create a scheduled task, etc.. to keep it running. I simply want it to not be easy to close.
See my code, below:
/* example.c
This is a Win32 C application (ie, no MFC, WTL, nor even any C++ -- just plain C) that demonstrates
how to embed a browser "control" (actually, an OLE object) in your own window (in order to display a
web page, or an HTML file on disk). The bulk of the OLE/COM code is in DLL.c which creates a DLL that
we use in this simple app. Furthermore, we use LoadLibrary and GetProcAddress, so our DLL is not
actually loaded until/unless we need it.
NOTE: The DLL we create does not normally use UNICODE strings. If you compile this example as UNICODE,
then you should do the same with DLL.C.
*/
#include <windows.h>
#include "..\CWebPage.h" /* Declarations of the functions in DLL.c */
// A running count of how many windows we have open that contain a browser object
unsigned char WindowCount = 0;
// The class name of our Window to host the browser. It can be anything of your choosing.
static const TCHAR ClassName[] = "EAS";
// Where we store the pointers to CWebPage.dll's functions
EmbedBrowserObjectPtr *lpEmbedBrowserObject;
UnEmbedBrowserObjectPtr *lpUnEmbedBrowserObject;
DisplayHTMLPagePtr *lpDisplayHTMLPage;
DisplayHTMLStrPtr *lpDisplayHTMLStr;
/****************************** WindowProc() ***************************
* Our message handler for our window to host the browser.
*/
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_CREATE)
{
// Embed the browser object into our host window. We need do this only
// once. Note that the browser object will start calling some of our
// IOleInPlaceFrame and IOleClientSite functions as soon as we start
// calling browser object functions in EmbedBrowserObject().
if ((*lpEmbedBrowserObject)(hwnd)) return(-1);
// Another window created with an embedded browser object
++WindowCount;
// Success
return(0);
}
if (uMsg == WM_DESTROY)
{
// Detach the browser object from this window, and free resources.
(*lpUnEmbedBrowserObject)(hwnd);
// One less window
--WindowCount;
// If all the windows are now closed, quit this app
if (!WindowCount) PostQuitMessage(0);
return(TRUE);
}
// NOTE: If you want to resize the area that the browser object occupies when you
// resize the window, then handle WM_SIZE and use the IWebBrowser2's put_Width()
// and put_Height() to give it the new dimensions.
return(DefWindowProc(hwnd, uMsg, wParam, lParam));
}
/****************************** WinMain() ***************************
* C program entry point.
*
* This creates a window to host the web browser, and displays a web
* page.
*/
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hInstNULL, LPSTR lpszCmdLine, int nCmdShow)
{
HINSTANCE cwebdll;
MSG msg;
WNDCLASSEX wc;
// Load our DLL containing the OLE/COM code. We do this once-only. It's named "cwebpage.dll"
if ((cwebdll = LoadLibrary("cwebpage.dll")))
{
// Get pointers to the EmbedBrowserObject, DisplayHTMLPage, DisplayHTMLStr, and UnEmbedBrowserObject
// functions, and store them in some globals.
// Get the address of the EmbedBrowserObject() function. NOTE: Only Reginald has this one
lpEmbedBrowserObject = (EmbedBrowserObjectPtr *)GetProcAddress((HINSTANCE)cwebdll, "EmbedBrowserObject");
// Get the address of the UnEmbedBrowserObject() function. NOTE: Only Reginald has this one
lpUnEmbedBrowserObject = (UnEmbedBrowserObjectPtr *)GetProcAddress((HINSTANCE)cwebdll, "UnEmbedBrowserObject");
// Get the address of the DisplayHTMLPagePtr() function
lpDisplayHTMLPage = (DisplayHTMLPagePtr *)GetProcAddress((HINSTANCE)cwebdll, "DisplayHTMLPage");
// Get the address of the DisplayHTMLStr() function
lpDisplayHTMLStr = (DisplayHTMLStrPtr *)GetProcAddress((HINSTANCE)cwebdll, "DisplayHTMLStr");
// Register the class of our window to host the browser. 'WindowProc' is our message handler
// and 'ClassName' is the class name. You can choose any class name you want.
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WindowProc;
wc.lpszClassName = &ClassName[0];
RegisterClassEx(&wc);
// Create another window with another browser object embedded in it.
if ((msg.hwnd = CreateWindowEx(0, &ClassName[0], "Emergency Alert System", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
HWND_DESKTOP, NULL, hInstance, 0)))
{
// For this window, display a URL. This could also be a HTML file on disk such as "c:\\myfile.htm".
(*lpDisplayHTMLPage)(msg.hwnd, "http://www.google.com");
// Show the window.
ShowWindow(msg.hwnd, nCmdShow);
UpdateWindow(msg.hwnd);
}
// Do a message loop until WM_QUIT.
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Free the DLL.
FreeLibrary(cwebdll);
return(0);
}
MessageBox(0, "Can't open cwebpage.dll! You are not protected by Emergency Alerting System. Click OK to terminate this application. Contact the developer, Scott Plunkett.", "ERROR", MB_OK);
return(-1);
}
It took this from an example tutorial I found, I have never done any windows programming before so I had to figure out a quick solution.
I appreciate any and all help on this.
If you create a handler for the WM_SYSCOMMAND message and check for the SC_SYSCLOSE parameter, you can stop it from executing the default action of closing the window.
I noticed it's not an MFC application, so you have to call Win32 Library to enable the feature.
Try this out:http://www.davekb.com/browse_programming_tips:win32_disable_close_button:txt
---EDIT---
Sorry the code did not work out. I did not have resources to debug it.
To disable the X of the window, you can either set CS_NOCLOSE property of WNDCLASSEX:
wc.style = CS_NOCLOSE;//in your code
or rewrite the WM_CLOSE message handler function. Thanks.

A windows API program on random ractangles...I don't know how the variable get its value

<pre>
#include<Windows.h>
#include<process.h>
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);
HWND hwnd;
int clientx,clienty;
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
static TCHAR szAppName[]=TEXT("hello");
MSG msg;
WNDCLASS wndclass;
wndclass.style=CS_HREDRAW|CS_VREDRAW;
wndclass.hInstance=hInstance;
wndclass.cbClsExtra=0;
wndclass.cbWndExtra=0;
wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndclass.lpfnWndProc=WndProc;
wndclass.lpszClassName=szAppName;
wndclass.lpszMenuName=NULL;
if(!RegisterClass(&wndclass))
{
MessageBox(NULL,TEXT("this program requires windows NT"),TEXT("wrong"),MB_ICONERROR);
return 0;
}
hwnd=CreateWindow(szAppName,TEXT("random rectangles"),
WS_OVERLAPPEDWINDOW,
100,100,800,600,
NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
VOID Thread(PVOID pvoid)
{
HBRUSH hbrush;
HDC hdc;
int xleft,xright,ytop,ybottom,ired,igreen,iblue;
while(TRUE)
{
if(clientx!=0||clienty!=0)
{
xleft=rand()%clientx;
xright=rand()%clientx;
ytop=rand()%clienty;
ybottom=rand()%clienty;
ired=rand()%255;
igreen=rand()%255;
iblue=rand()%255;
hdc=GetDC(hwnd);
hbrush=CreateSolidBrush(RGB(ired,igreen,iblue));
SelectObject(hdc,hbrush);
Rectangle(hdc,min(xleft,xright),min(ytop,ybottom),max(xleft,xright),max(ytop,ybottom));
ReleaseDC(hwnd,hdc);
DeleteObject(hbrush);
}
}//while
}
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
_beginthread(Thread,0,NULL);
return 0;
case WM_SIZE:
clientx=LOWORD(lParam);
clienty=HIWORD(lParam);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
<code>
I do not know how the variable clientx and clienty at the top of the program get their value s when the program runs...Because I didn't see any value assignments in the program ....I used to debug it in my visual studio 2010, when it ran to "ShowWindow(hwnd,iCmdShow);"in the WinMain(),the clientx and clienty got their values (735 and 654 random...).But before that the clientx and clienty both were "0". I was confused~~ Thanks a lot~~~ :)
I think you are asking why clientx and clienty both have value zero when they are not explicitly initialised to zero.
A global variable, as clientx and clienty, has static storage duration. If a variable with static storage duration is not explicitly initialised (from section 6.7.8 Initialization of C99 standard):
if it has pointer type, it is initialized to a null pointer;
if it has arithmetic type, it is initialized to (positive or unsigned) zero;
if it is an aggregate, every member is initialized (recursively) according to these rules;
if it is a union, the first named member is initialized (recursively) according to these
rules.
Client x and and client y are initialised to zero (as mentioned by hmjd) as they are global.
When the application opens Windows sends a WM_RESIZE message to the window procedure to tell it how big the window is (and this message is sent again if the user resizes the window). Near the bottom you can see the code where the clientx and clienty are set according to the parameters of the RESIZE message - essentially they are the height and width of the client window in pixels.
These values comes from your previous session. When you close your top-level window, Windows remembers its size and location.
http://support.microsoft.com/kb/235994
Windows saves size and location information for closed windows in the
following registry location:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Streams
Windows saves size and location information for up to 28 different
windows. Each window's size and location parameters are stored in a
subkey of the Streams key.

Is it possible to eliminate flickering entirely when resizing a window?

Normally, even when using double buffering, when resizing a window, it seems that it's inevitable that the flickering will happen.
Step 1, the original window.
Step 2, the window is resized, but the extra area hasn't been painted.
Step 3, the window is resized, and the extra area has been painted.
Is it possible somehow to hide setp 2? Can I suspend the resizing process until the painting action is done?
Here's an example:
#include <Windows.h>
#include <windowsx.h>
#include <Uxtheme.h>
#pragma comment(lib, "Uxtheme.lib")
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void MainWindow_OnDestroy(HWND hWnd);
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy);
void MainWindow_OnPaint(HWND hWnd);
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex = { 0 };
HWND hWnd;
MSG msg;
BOOL ret;
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
wcex.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
wcex.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
wcex.lpszClassName = TEXT("MainWindow");
wcex.hIconSm = wcex.hIcon;
if (!RegisterClassEx(&wcex))
{
return 1;
}
hWnd = CreateWindow(wcex.lpszClassName, TEXT("CWin32"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
if (!hWnd)
{
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
return 1;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, MainWindow_OnSize);
HANDLE_MSG(hWnd, WM_PAINT, MainWindow_OnPaint);
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
BufferedPaintInit();
return TRUE;
}
void MainWindow_OnDestroy(HWND hWnd)
{
BufferedPaintUnInit();
PostQuitMessage(0);
}
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy)
{
InvalidateRect(hWnd, NULL, FALSE);
}
void MainWindow_OnPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HPAINTBUFFER hpb;
HDC hdc;
BeginPaint(hWnd, &ps);
hpb = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &hdc);
FillRect(hdc, &ps.rcPaint, GetStockBrush(DKGRAY_BRUSH));
Sleep(320); // This simulates some slow drawing actions.
EndBufferedPaint(hpb, TRUE);
EndPaint(hWnd, &ps);
}
Is it possible to eliminate the flickering?
When the window is updated during a drag operation, then the OS has to show something in the extended window region. If you can't provide anything then it will show the background until you do. Since you didn't specify any background you get blackness. Surely you ought to be specifying a background brush? Simply adding the following to your code makes the behaviour more palatable:
wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH);
However, if you take as long as 320ms to respond to a WM_PAINT then you ruin the resize UI for the user. It becomes jerky and unresponsive. The system is designed around the assumption that you can paint the window quickly enough for dragging to feel smooth. The right way to fix your problem is to make WM_PAINT run in a reasonable time.
If you really can't achieve quick enough painting for smooth dragging then I suggest a couple of alternatives:
Disable window updates during dragging. I'm sure this can be done for individual windows, but I can't remember how to do it off the top of my head.
Paint something fake whilst a resize/drag is active, and postpone the real painting until when the resize/drag has completed. Listening for WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE are the keys to this. This Microsoft sample program illustrates how to do that: https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/fulldrag/
Use WM_SIZING instead of WM_SIZE and don't forget about WM_ERASEBKGND.
If you go into the System Properties control panel applet, choose the Advanced tab, and then click Settings... in the Performance group box, you'll see a checkbox setting called Show window contents while dragging. If you uncheck that and try resizing a window, you'll see that only the window frame moves until you complete the drag operation, and then the window repaints just once at the new size. This is how window sizing used to work when we had slow, crufty computers.
Now we don't really want to change the setting globally (which you would do by calling SystemParametersInfo with SPI_SETDRAGFULLWINDOWS, but don't really do that because your users won't like it).
What happens when the user grabs the resize border is that the thread enters a modal loop controlled by the window manager. Your window will get WM_ENTERSIZEMOVE as that loop begins and WM_EXITSIZEMOVE when the operation is complete. At some point you'll also get a WM_GETMINMAXINFO, which probably isn't relevant to what you need to do. You'll also get WM_SIZING, WM_SIZE messages rapidly as the user drags the sizing frame (and the rapid WM_SIZEs often lead to WM_PAINTs).
The global Show window contents while dragging setting is responsible for getting the rapid WM_SIZE messages. If that setting is off, you'll just get one WM_SIZE message when it's all over.
If your window is complicated, you probably have layout code computing stuff (and maybe moving child windows) in the WM_SIZE handler and a lot of painting code in the WM_PAINT handler. If all that code is too slow (as your sample 320 ms delay suggests), then you'll have a flickery, jerky experience.
We really don't want to change the global setting, but it does inspire a solution to your problem:
Do simpler drawing during the resize operation and then do your (slower) complex drawing just once when the operation is over.
Solution:
Set a flag when you see the WM_ENTERSIZEMOVE.
Change your WM_SIZE handler to check the flag and do nothing if it's set.
Change your WM_PAINT handler to check the flag and do a simple, fast fill of the window in a solid color if it's set.
Clear the flag when you see WM_EXITSIZEMOVE, and then trigger your layout code and invalidate your window so that everything gets updated based on the final size.
If your slow window is a child rather than your application's top-level window, you'll have to signal the child window when the top-level window gets the WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE in order to implement steps 1 and 4.
Yes you can entirely delete flickering :)
You can do all the window message handling in one thread, and painting its context in another. Your window always keeps responsive. It works great, can not understand why this is not established best practice.
If you bind a Direct3D context for example, it can have an instant scaling while resizing, completely without having the context updated!
My code looks like this:
int WINAPI wWinMain( HINSTANCE a_hInstance, HINSTANCE a_hPrevInstance, LPWSTR a_lpCmdLine, int a_nCmdShow )
{
Win32WindowRunnable* runnableWindow=new Win32WindowRunnable(a_hInstance, a_nCmdShow);
IThread* threadWindow=new Win32Thread(runnableWindow);
threadWindow->start();
Scene1* scene=new Scene1(runnableWindow->waitForWindowHandle());
IThread* threadRender=new Win32Thread(scene);
threadRender->start();
threadWindow->join();
threadRender->pause();
threadRender->kill();
delete runnableWindow;
return 0;
}
Full source example here:
https://github.com/TheWhiteAmbit/TheWhiteAmbit/blob/master/Win32App/Win32Main.cpp

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.

Resources