Click here to Skip to main content
15,893,622 members
Please Sign up or sign in to vote.
4.00/5 (3 votes)
See more:
Gentle people,
I simply just don't understand ...
There is a Combobox child window. If I set the focus to this combobox, I can enter the desired text and it is shown in the box, but I am unable to check whether the user has completed his entry. I.e. I get all CB notifications under WM_COMMAND, but none in WM_CHAR or WM_KEYDOWN. On the other hand if I inactivate the SetFocus to this combobox, I am unable to enter text. I.e. I get none of the CB notifications under WM_COMMAND, but WM_CHAR or WM_KEYDOWN work well ...

Could somebody please explain and give me an idea how to proceed? Any help will be appreciated!

The following simple piece of code already shows this behaviour:

int APIENTRY _tWinMain (HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow)
{           :
  	    :
  	    :
// Create Main window  	    	
  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
  hWnd  = CreateWindow (szWindowClass,szTitle,
                        WS_TILED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
                        100,100,905,667,NULL,NULL,hInstance,NULL);  
// Create Combobox Child window  	    	                        
  LoadString(hInstance, IDC_CMB, szClass, MAX_LOADSTRING);
  hListtitel = CreateWindow(szClass,"",
                        WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | _VSCROLL,   
                        200, 170, 540, 400,hWnd,NULL,hInstance,NULL);   
  InvalidateRect(hWnd,NULL,TRUE); 
  ShowWindow(hWnd, SW_SHOWNORMAL);
  UpdateWindow(hWnd);
  SetFocus(hListtitel);       <----------------------------------
  
  while (GetMessage(&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg); 
    }
  }
  return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {         :
  	    :
  	    :
    case WM_CHAR: 
     /*     do something     */
      break; 
            :
  	    :
  	    :
    case WM_COMMAND:
      wmEvent = HIWORD(wParam);
      if ((hListtitel != 0) && (lParam == (LPARAM)hListtitel))
      {
        switch (wmEvent)
			  {
       	  case CBN_EDITUPDATE: 
     /*     do something     */
            break; 
       	  case CBN_KILLFOCUS:  
     /*     do something     */
            break; 
          case CBN_SELCHANGE:  
     /*     do something     */
            break; 
          }	
        break; 
      }
  }
  return DefWindowProc(hWnd, message, wParam, lParam);

}
Posted

Gday there,

The reason for this behaviour is that the (default) Combo Box doesn't sent send notifications of WM_CHAR or WM_KEYDOWN/WM_KEYUP to it's parent.

When you give focus to the dialog, it's the dialog that's giving you the WM_CHAR WM_KEYxxxx messages, NOT the Combo Box.


The way around this is to sub-class the combo box, just the same way that you sub-class the app's window. Then, you'll have your own WindowProc function that's called whenever anything happens with the ComboBox. From there, you process any messages you're interested in, before passing all messages to the default window proc for the Combo Box.

It's been ages since I did any sub-classing, so my memory of the ins and outs have faded considerably. :(
However, this[^] example looks like it will be helpful.
 
Share this answer
 
v2
Comments
Maciej Los 29-May-12 15:02pm    
Good answer and interesting link, my 5!
enhzflep 29-May-12 15:37pm    
Thank-you!
I thought it seemed to be a pretty good reference source too.
Ernst G. Gruber 29-May-12 15:32pm    
Thanks a lot for your answer! I feared something like this may turn up as an answer to my question, but the article you pointed me to, looks very solid.

Thanks again and I will post the outcome of my attempts ...
enhzflep 29-May-12 15:36pm    
You're more than welcome - It's a pleasure. Don't worry too much - it's actually pretty simple from (my fading) memory.
Nelek 29-May-12 17:36pm    
+5
Sorry! I do have to apologize! You were right all the time!

While preparing the code to show the problem with the least amount of code and recompiling all the time to check whether the problem still exists, the thing suddenly miraculously started working properly. Please don't ask me what the culprit was - I only can assume there was a flaw in my code, which was rectified by my preparations ...

Sorry about that and thank you again for your patience and for your valuable help!
Ernst

NB: I only find "Have a question or comment" or "Add your solution", but no "reply" ...

This is the working solution for a combobox not being part of a dialogue:
C++
//#include "stdafx.h"
#include "TestCombo.h"
#include "resource.h"
#include <string.h>
#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#define MAX_LOADSTRING 100
#define WM_TAB (WM_USER) 
#define WM_ESC (WM_USER + 1) 
#define WM_ENTER (WM_USER + 2) 

WNDCLASSEX  wcex;
WNDPROC     lpfnParProc;
HACCEL      hAccelTable;
TCHAR       szClass[MAX_LOADSTRING];
TCHAR       szTitle[MAX_LOADSTRING];       
TCHAR       szWindowClass[MAX_LOADSTRING]; 
MSG         msg;  
HWND        hMain;
HWND        hListtitel;
HWND        hEdittitel; 
HDC         hdc;
HINSTANCE   hInst;
RECT        rt;
PAINTSTRUCT ps;
POINT       pt; 
int         wmId;
int         wmEvent;
int         lmId;
int         lmEvent;

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

int APIENTRY _tWinMain (HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow)
{
	hInst = hInstance;
  hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTCOMBO));
  LoadString(hInstance, IDC_TESTCOMBO, szWindowClass, MAX_LOADSTRING);
  wcex.cbSize        = sizeof(WNDCLASSEX);
  wcex.style         = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc   = WndProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInstance;
  wcex.hIcon         = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_TESTCOMBO));
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName  = MAKEINTRESOURCE(IDC_TESTCOMBO);
  wcex.lpszClassName = szWindowClass;
  wcex.hIconSm       = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
  if (!RegisterClassEx(&wcex))
  { return FALSE; }
  
  hMain  = CreateWindow (szWindowClass,"Test Combobox",
               WS_TILED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
               100,100,905,667,NULL,NULL,hInstance,NULL);  
               
  hListtitel = CreateWindow("COMBOBOX","",
               WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | WS_VSCROLL | CBS_HASSTRINGS, 
               200, 170, 540, 400,hMain,NULL,hInst,NULL);  
               
  hEdittitel = FindWindowEx(hListtitel,NULL,"EDIT",NULL);
  lpfnParProc = (WNDPROC) SetWindowLong(hEdittitel,GWL_WNDPROC, (DWORD) ChldProc);
  
  InvalidateRect(hMain,NULL,TRUE); 
  ShowWindow(hMain, SW_SHOWNORMAL);
  UpdateWindow(hMain);
  SetFocus(hListtitel);
  
  while (GetMessage(&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg); 
    }
  }
  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
    case WM_PAINT:
      hdc = BeginPaint(hwnd, &ps);
      EndPaint(hwnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;        
    case WM_TAB: 
                                       // Do something
      break; 
    case WM_ESC: 
                                       // Do something 
      break;
    case WM_ENTER: 
                                       // Do something
      break; 
    case WM_COMMAND:
      wmEvent = HIWORD(wParam);
      if ((hListtitel != 0) && (lParam == (LPARAM)hListtitel))
      {
        switch (wmEvent)
			  {
       	  case CBN_EDITUPDATE: 
		                                	  // Do something
            break; 
       	  case CBN_KILLFOCUS:  
		    	                              // Do something
            break; 
          case CBN_SELCHANGE:  
		    	                              // Do something
            break; 
          }	
        break; 
      }
    default:
    	return DefWindowProc(hwnd, message, wParam, lParam);
  }
  return 0;
}

LRESULT CALLBACK ChldProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {        
  	case WM_KEYDOWN: 
      switch (wParam) 
      { 
        case VK_TAB: 
          SendMessage(hMain, WM_TAB, 0, 0); 
          return 0; 
        case VK_ESCAPE:  
          SendMessage(hMain, WM_ESC, 0, 0); 
          return 0; 
        case VK_RETURN: 
          SendMessage(hMain, WM_ENTER, 0, 0); 
          return 0; 
       } 
       break; 
  } 
  return CallWindowProc(lpfnParProc, hwnd, msg, wParam, lParam); 
 
Share this answer
 
v2
Comments
enhzflep 30-May-12 17:03pm    
Sorry? Huh.. :confused: Have no idea what for, there's been anything but a problem on this end of the line.

I've had the chance to refresh (& upgrade) my knowledge of subclassing and you've just nailed it. Seems pretty much win-win to me. :)

Oh, about the mechanism for replying - it's different for a 'solution' and a 'comment'. The solution has the button at the bottom-left of it, while the comments have a "<- reply" button in their top-right.(thats a curly green arrow)

:Grins: I just read every word of every post in the News item related to how somebody (& about a million readers) got involved in computers. Just like so many others in the thread, I can't get enough-of programming.
For me, CodeProject surfing is a compulsion, not a choice.....
(just like it was to edit your post to make all of that horrible red go away :D )

Getting started with computing:
http://www.codeproject.com/Messages/4265512/She-let-me-take-the-computer-home.aspx
Ernst G. Gruber 30-May-12 17:20pm    
Good when you feel this way! As for me - I was and should have been on the right track after your first answer! And not carrying on pestering you unnecessarily ...
Take care! E
Well, I am back to square one ...
Added
C++
  hListtitel = CreateWindow("COMBOBOX","",
               WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | WS_VSCROLL | CBS_HASSTRINGS, 
               200, 170, 540, 400,hWnd,NULL,hInst,NULL);          
  pt.x = 1; 
  pt.y = 1; 
// get the handle to the edit part of the combobox
  hEdittitel = ChildWindowFromPoint(hListtitel, pt); 
// set pointer to the desired procedure
  lpfnParProc = (WNDPROC) SetWindowLong(hEdittitel,GWL_WNDPROC, (DWORD) ChldProc); 
as in code sample above and added also a "ChldProc", similar to the article mentioned, to process all the messages coming from the edit part of the combobox.

I get loads of messages in ChldProc, but none for WM_CHAR or WM_KEYDOWN ...
:O(((! Sob ...
 
Share this answer
 
Comments
enhzflep 30-May-12 7:40am    
Knew there was something I was forgetting...
The ComboBox is a complex control - that is to say, it's made up of a couple of different windows.
You've got the ComboBox, which is a host to two windows. The class names of these two children are 'Edit' & 'ComboLBox'.
You can see this using Spy++ (MS) or another tool with the same functionality if you're not using VS.

Re-reading your first post, I guess that you're probably interested in the Edit control at the top that you can type text into.
If so, you'll need to get the Hwnd of the ComboBox, followed by using FindWindowEx to get the Hwnd of the edit control. You'll also need to save your dialog/frame's hwnd into a global variable, so that you can send a message to it from your sub-classed Edit WindowProc.

You would probably choose to do this window-jiggery in response to WM_INITDIALOG or WM_CREATE (Dialog/Frame) message. Here's an example of getting the Hwnd of the Edit control. - The printf is just there so I could check the value I retrieved matched the value that Spy++ returns.


With all of that being the case, I guess you'd just write a WindowProc to handle the messages the Edit control receives, using SendMessage to pass on WM_CHAR / WM_KEYxxxx messages to dlgHwnd.

//////////////////////////////////////////////

HWND dlgHwnd, comboHwnd, comboEditHwnd;

BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
dlgHwnd = hwndDlg;
comboHwnd = GetDlgItem(hwndDlg, IDC_COMBO1);

comboEditHwnd = FindWindowEx(comboHwnd, NULL, "Edit", NULL);
printf("0x%X\n", comboEditHwnd);
return TRUE;
Ernst G. Gruber 30-May-12 9:46am    
Thanks again for talking to me!
Trouble is - there is not much more than what you see in my original post. Now the original post has been extented by "get the handle of the edit part" and "set the pointer to the child procedure (and save the original pointer to the parent procedure at the same time)) as shown in my second post. Plus the child procedure for the edit part, which looks pretty much the same as in the example you pointed me to. There is no INITDIALOG or similar, just a main window and a combobox (which doesn't work). What worries me is - now I have two different ways of getting the handle of the edit part (ChildWindowFromPoint (from the example) and your FindWindowEx). Both ways deliver different results ... and both do not give me the WM_CHAR and ....

As a (very) old OS/390 warhorse I just wonder how complicated the use of one of the most common forms of a combobox can be ....

Just drop me a line if you want to have a look at the "total code".

With best regards, Ernst
Ernst G. Gruber 30-May-12 9:51am    
Just forgot to say - handle to combobox is no problem - it stems directly from CreateWindow ...
enhzflep 30-May-12 13:44pm    
Answer deserved formatting, I've entered a new solution, rather than muck about pasting code into a comment. Please see below.
It's a pleasure.
If you remember, it'd be worth hitting the "<- reply" link at the top of the comment you wish to respond to, so that the commenter gets an email notification. I happened to come back to see if I'd included something in particular and saw you'd responded.


I've had it working, you need very few changes to the code in your original question to get it working. Let me outline them.
  • Add a global var to hold a pointer to the original WindowProcedure for the edit box
  • Add a prototype for the SubclassProcedure
  • Add code to replace the windowProc for the edit-box
  • Add the subclass procedure


At the risk of being overly verbose, I'll post the code I have.
Obviously (or perhaps not?) Since this is a dialog-based app, the combo-box is already around at the time the main window is created - meaning I can respond to WM_INITDIALOG. You _cannot_ simply put the code that responds to that message into a WM_INIT message handler for your window, since you're creating it manually
and the comboBox isn't a part of it yet. The appropriate place for FindWindow & SetWindowLong would be immediately before your InvalidateRect statement.

Regards,
S.


C++
#define WIN32_LEAN_AND_MEAN

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

HINSTANCE hInst;
HWND dlgHwnd, comboHwnd, comboEditHwnd;
WNDPROC lpfnEditWndProc;

LRESULT CALLBACK SubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);


BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_INITDIALOG:
            dlgHwnd = hwndDlg;
            comboHwnd = GetDlgItem(hwndDlg, IDC_COMBO1);


            comboEditHwnd = FindWindowEx(comboHwnd, NULL, "Edit", NULL);
            lpfnEditWndProc = (WNDPROC) SetWindowLong(comboEditHwnd, GWL_WNDPROC, (DWORD) SubClassProc);
            return TRUE;

        case WM_CLOSE:
            EndDialog(hwndDlg, 0);
            return TRUE;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                /*
                 * TODO: Add more control ID's, when needed.
                 */
                case IDC_BTN_QUIT:
                    EndDialog(hwndDlg, 0);
                    return TRUE;

                case IDC_BTN_TEST:
                    MessageBox(hwndDlg, "You clicked \"Test\" button!", "Information", MB_ICONINFORMATION);
                    return TRUE;
            }
    }

    return FALSE;
}


/********************************************************

    FUNCTION:   SubClassProc

    PURPOSE:    Pass WM_KEYDOWN, WM_KEYUP, WM_CHAR messages to the parent of the comboBox that
                owns this Edit window.

    NOTE:       dlgHwnd is a global var, initialized when the dialog is initialized

*********************************************************/
LRESULT CALLBACK SubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_KEYDOWN:
        case WM_KEYUP:
        case WM_CHAR:
                PostMessage(dlgHwnd, msg, wParam, lParam);
        break;
    }
    //  Call the original window procedure for default processing.
     return CallWindowProc(lpfnEditWndProc, hwnd, msg, wParam, lParam);
}


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    hInst = hInstance;

    // The user interface is a modal dialog box
    return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900