shuji shuji - 3 months ago 30
C++ Question

How to change WndProc for a listview control

I am trying to change a Wndproc for a listview control so the first parameter in wndproc return the control handle that receives the message, the problem is when I change it other functions stop working (I cannot insert columns or items anymore), is there something I need to change or return in order to keep using the same wndproc for all the controls

WNDPROC same for all controls:

LRESULT CALLBACK staticWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){
std::cout << (int)hwnd << " control received msg:" << uMsg << std::endl; //This must work
//event = { msg:uMsg, target:(int)hwnd, x:0, y:0, button:0, key:0 };
switch (uMsg){
case WM_DESTROY:
std::cout << "window says bye " << std::endl;
PostQuitMessage(WM_QUIT);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//msghandlercall(event); //this is handled not in c++
return 0;
}


after calling to
SetWindowLongPtr(handle, GWLP_WNDPROC, (LONG_PTR)staticWndProc);
insert messages are not working as default

int createColumn(HWND listhandle, int indexCol, char *Text, int width){
LVCOLUMN lvc={0};
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
lvc.cx = width;
lvc.pszText = Text;
lvc.iSubItem = indexCol;
//return ListView_InsertColumn(listhandle, indexCol, &lvc);
SendMessage(listhandle,LVM_INSERTCOLUMN,indexCol,(LPARAM)&lvc);
return 1;
}
void createColumns(HWND listhandle, std::vector<LPSTR> columns){
for(int i=0; i<columns.size(); i++) createColumn(listhandle, i, columns[i], 50);
}
int createItem(HWND listhandle, const std::vector<LPSTR>& row){
LVITEM lvi = {0};
//lvi.mask = LVIF_TEXT;
// lvi.pszText = row[0];
int ret = ListView_InsertItem(listhandle, &lvi);
if(ret>-1) for(unsigned i=0; i<row.size(); i++)
ListView_SetItemText(listhandle, ret, i, row[i]);
return ret;
}
HWND createList(int parenthandle=0){
if(parenthandle==0) parenthandle=(int)GetDesktopWindow();
HWND handle = CreateWindow(WC_LISTVIEW, "",WS_VISIBLE|WS_BORDER|WS_CHILD | LVS_REPORT | LVS_EDITLABELS,
10, 10, 300, 100, (HWND)parenthandle, /*(HMENU)ID_LIST*/NULL, GetModuleHandle(NULL), 0);
if(!handle){ std::cerr << "Failed to create list\n"; return 0; }
SetWindowLongPtr(handle, GWLP_WNDPROC, (LONG_PTR)staticWndProc);
ListView_SetExtendedListViewStyle(handle, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
createColumns(handle,{"col1","col2","col3"});
createItem(handle,{"item1.1","item1.2","item1.3","item1.4"});
createItem(handle,{"item2.1","item2.2","item2.3","item2.4"});
return handle;
}


NOTE I tried adding wndproc with
SetWindowSubclass()
but an error occurs:
error C2664: 'BOOL IsolationAwareSetWindowSubclass(HWND,SUBCLASSPROC,UINT_PTR,DWORD_PTR)': cannot convert argument 2 from 'LRESULT (__cdecl *)(HWND,UINT,WPARAM,LPARAM)' to 'SUBCLASSPROC' [build\binding.vcxproj]

Answer

When you subclass a window procedure using SetWindowLongPtr(GWLP_WNDPROC), it returns the previous window procedure that is being replaced. Your subclass procedure must pass unhandled messages to that previous procedure using CallWindowProc() instead of DefWindowProc(). This is stated as much in the documentation:

Calling SetWindowLongPtr with the GWLP_WNDPROC index creates a subclass of the window class used to create the window. An application can subclass a system class, but should not subclass a window class created by another process. The SetWindowLongPtr function creates the window subclass by changing the window procedure associated with a particular window class, causing the system to call the new window procedure instead of the previous one. An application must pass any messages not processed by the new window procedure to the previous window procedure by calling CallWindowProc. This allows the application to create a chain of window procedures.

You are not doing that, which is why your code is failing.

Try this instead:

WNDPROC prevWndProc;

LRESULT CALLBACK staticWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //...
    return CallWindowProc(prevWndProc, hwnd, uMsg, wParam, lParam);
}

HWND createList(HWND parenthandle = NULL)
{
    if (!parenthandle) parenthandle = GetDesktopWindow();

    HWND handle = CreateWindow(..., parenthandle, ...);
    if (!handle) {
        std::cerr << "Failed to create list\n";
        return NULL;
    }

    prevWndProc = (WNDPROC) SetWindowLongPtr(handle, GWLP_WNDPROC, (LONG_PTR)staticWndProc);
    if (!prevWndProc) {
        std::cerr << "Failed to subclass list\n";
        DestroyWindow(handle);
        return NULL;
    }

    ...

    return handle;
}

That being said, you really should not be using SetWindowLongPtr(GWLP_WNDPROC) at all. It is unsafe for exact this reason (amongst others). You should be using SetWindowSubclass() instead:

LRESULT CALLBACK staticSubClass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    //...

    if (uMsg == WM_NCDESTROY) {
        // NOTE: this requirement is NOT stated in the documentation,
        // but it is stated in Raymond Chen's blog article...
        RemoveWindowSubclass(hWnd, staticSubClass, uIdSubclass);
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

HWND createList(HWND parenthandle = NULL)
{
    if (!parenthandle) parenthandle = GetDesktopWindow();

    HWND handle = CreateWindow(..., parenthandle, ...);
    if (!handle) {
        std::cerr << "Failed to create list\n";
        return NULL;
    }

    if (!SetWindowSubclass(handle, staticSubClass, 1, 0)){
        std::cerr << "Failed to subclass list\n";
        DestroyWindow(handle);
        return NULL; 
    }

    ...

    return handle;
}

See MSDN for more details:

Subclassing Controls

Safer subclassing

Comments