I have some questions about the standard operation of a simple combo box. What does the normal user expect to happen when he does certain things?
In a simple combo box, the user can double click on an item meaning "this is the one I want". No problem, just do it.
However, typing in the edit box is a little more complicated. When a user types in the edit of the combo box, it scrolls the list appropriately. It doesn't, however highlight the selection found so far. It is usually the topmost item unless the list is at the end. Is there a setting I can't find to make it highlight the item it is finding in response to the user's typing? Or is this something I need to do manually?
Then, if the user wants to select the item highlighted or use what he has typed in, and he has his hands on the keyboard and not the mouse, how does he indicate that he wants "THIS" one? Enter key?
If the user wants to abort, what is his procedure? Hit escape? Blank the edit box and hit return? or what?
I know how I would like it to work, but that is often not the way of windows. Enlightenment here would be appreciated.
Ok, obviously this is too esoteric a question. So, I've decided that pressing the enter key while in the edit box of the combobox means I want what's in the edit box.
So, I have to subclass the combobox to get the enters.. I've subclassed edit boxes, so no big deal. Right.
I'm doing something wrong here because I never get the enter keys or escape keys or any of the keys normally stolen by a dialog. What fine point of subclassing a combobox am I missing here? There's a lot of debug prints to see what I'm getting and nothing is coming through for these characters, but I'm getting the normal characters in the subclass proc.
Edited:----
I deleted this listing and code. See my last post for a corrected and functional version of what I was trying to accomplish.
This may be a stupid idea, but is there an ES_WANTRETURN equivalent for combo boxes?
Not that I can find for the combobox itself, but it should certainly be available in the edit control of the combobox, if I only knew it's handle. I tried the ChildWindowFromPoint stuff from the combobox in a toolbar example and got nothing. It was pretty much voodoo anyway, how would I know to point to 1,1 for the edit control? I thought GetWindow would work, but no luck there either. This really shouldn't be this complicated. Anyway, thanks for the post, Mark, I was beginning to feel rather lonely in this thread.
Ok, I finally found a solution. There is an api called GetComboBoxInfo. It's not in the win32.hlp file, but it is in user32.inc
It uses a structure that is not in any of the includes distributed with Masm. Here the section of the code needed-
.data?
hComboEdt dd ?
COMBOBOXINFO STRUCT
cbSize DWORD ?
rcItem RECT <?>
rcButton RECT <?>
stateButton DWORD ?
hwndCombo DWORD ?
hwndItem DWORD ?
hwndList DWORD ?
COMBOBOXINFO ENDS
ci COMBOBOXINFO <?>
.code
invoke GetDlgItem,hWin,cb
mov hcb,eax ; save handle to control
mov ci.cbSize,sizeof(COMBOBOXINFO)
inv GetComboBoxInfo,hcb,addr ci
mov eax,ci.hwndItem
mov hComboEdt,eax
inv SetWindowLong,hComboEdt,GWL_WNDPROC,EditWndProc
mov OldComboProc,eax
I had run across this function in the last 3 days of searching, but for some reason I got it in my head that it some later function for NT only since it wasn't in win32.hlp. So why didn't someone tell me about this sooner? (whine).
Hutch- can we include this structure somewhere in the Masm distribution?
Ok, I spoke too soon AGAIN!
At least I'm getting something in the subclassed combobox/edit control routine, but for an enter key all I'm getting is two 87h uMsgs and a 101h messasge. I think 87h is a WM_GETDLGCODE message, but I have no idea what to do with it. After two hours of screwing around, I have nothing. I think I'm going to give up and just use an ok and cancel command button with the combobox just like everybody else, but I think this is really lame. :'(
Ok,
Here is a simple example of subclassing the edit control in a combobox.
It uses the ChildWindowFromPoint method described at MSDN. This
example only allows numeric input to the edit control. Not exactly
what you want but it should get you going in the right direction.
OOPS! I did not realize there were no file attachments, Here is the code:
;==========================================================================
WinMain PROTO
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
NewEditProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
;==========================================================================
uselib MACRO libname
include \masm32\include\libname.inc
includelib \masm32\lib\libname.lib
ENDM
;==========================================================================
include \masm32\include\windows.inc
uselib gdi32
uselib user32
uselib kernel32
uselib Comctl32
uselib comdlg32
;==========================================================================
.const
IDR_MAINICON equ 500
IDR_MAINMENU equ 501
IDM_EXIT equ 1000
IDM_ABOUT equ 1100
COMBO_ID equ 2000
.data
szClassName db "BLClass",0
szProgramName db "Subclass Combobox",0
szAboutText db "Simple example of subclassing the edit control in a combobox",0
szComboBox db "COMBOBOX",0
szString1 db "100",0
szString2 db "200",0
wc WNDCLASSEX<sizeof WNDCLASSEX,CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW,\
offset WndProc,NULL,NULL,NULL,0,0,COLOR_BTNFACE+1,NULL,offset szClassName,0>
.data?
hInstance dd ?
hWnd dd ?
hMenu dd ?
hIcon dd ?
hComboBox dd ?
hComboEdit dd ?
OldEditProc dd ?
;==========================================================================
.386
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
;==========================================================================
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke InitCommonControls
invoke WinMain
invoke ExitProcess,eax
;==========================================================================
WinMain proc
LOCAL msg:MSG
invoke LoadIcon,hInstance,IDR_MAINICON
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke LoadMenu,hInstance,IDR_MAINMENU
mov hMenu,eax
invoke RegisterClassEx,addr wc
;Center the Window
invoke GetSystemMetrics,SM_CXSCREEN
mov esi,eax
invoke GetSystemMetrics,SM_CYSCREEN
mov ecx,eax
shr esi,1
shr ecx,1
sub esi,100/2
sub ecx,200/2
invoke CreateWindowEx,WS_EX_LEFT,addr szClassName,addr szProgramName,
WS_VISIBLE or WS_OVERLAPPEDWINDOW,esi,ecx,200,100,NULL,NULL,hInstance,NULL
mov hWnd,eax
invoke SetMenu,hWnd,hMenu
.while TRUE
invoke GetMessage,addr msg,NULL,0,0
.break .if (!eax)
; invoke IsDialogMessage,hWnd,addr msg <- commented out
; .if eax == FALSE <- commented out
invoke TranslateMessage,addr msg
invoke DispatchMessage,addr msg
; .endif <- commented out
.endw
mov eax,msg.wParam
ret
WinMain endp
;==========================================================================
WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg == WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr szComboBox,NULL,
WS_CHILD or WS_BORDER or WS_VISIBLE or WS_TABSTOP or WS_VSCROLL or \
CBS_HASSTRINGS or CBS_DROPDOWN or CBS_SORT,10,10,80,80,hWin,COMBO_ID,hInstance,NULL
mov hComboBox,eax
;Get ComboBox's Edit Control handle
invoke ChildWindowFromPoint,hComboBox,10,10
mov hComboEdit,eax
;Subclass the ComboBox's Edit Control
invoke SetWindowLong,hComboEdit,GWL_WNDPROC,addr NewEditProc
mov OldEditProc,eax
;Some strings for the ComboBox List
invoke SendMessage,hComboBox,CB_ADDSTRING,0,addr szString1
invoke SendMessage,hComboBox,CB_ADDSTRING,0,addr szString2
.elseif uMsg == WM_COMMAND
.if wParam == IDM_EXIT
invoke SendMessage,hWin,WM_SYSCOMMAND,SC_CLOSE,NULL
.elseif wParam == IDM_ABOUT
invoke MessageBox,hWin,addr szAboutText,addr szProgramName,MB_OK
.endif
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif
invoke DefWindowProc,hWin,uMsg,wParam,lParam
ret
WndProc endp
;==========================================================================
NewEditProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
; Only allow '1' thru '9' and backspace and show a message box if enter key
.if uMsg == WM_CHAR
mov eax,wParam
.if (al >= '0' && al <= '9') || al == VK_BACK
invoke CallWindowProc,OldEditProc,hWin,uMsg,wParam,lParam
ret
.elseif al == VK_RETURN ; <- new code
invoke MessageBox,hWnd,addr szAboutText,addr szProgramName,MB_OK ; <- new code
ret ; <- new code
.else
ret
.endif
.else
invoke CallWindowProc,OldEditProc,hWin,uMsg,wParam,lParam
ret
.endif
NewEditProc endp
;==========================================================================
end start
Ok, now we're getting somewhere. I assume you tried this and it works on your computer, i.e. you trapped a carriage return. It doesn't trap a carriage return on my computer. It traps all the normal characters, but I already had that as I stated in my previous message. If you can trap the enter key, tab key, escape key, etc., then there is something wrong with my machine. Would someone else also try this to see if you can trap the carriage return? To see if I was trapping them I included masm32lib and debuglib and just put this code in the WM_CHAR handler-
.if uMsg == WM_CHAR
mov eax,wParam
PrintHex eax,"wmchar"
Sorry, I did not try to trap the carriage return. I did not think it
would be a problem. I did play with it for a few minutes and had
some success, but I now see that there are problems. I will look
at it some more tommorrow and let you know what I found out.
Back again, I edited the code above and it now works on my machine
to catch carriage returns. I commented out the references to
IsDialogMessage in the message loop and added code to catch the
Enter Key. It just shows a message box when you hit Enter, but it
shows that you can trap it.
Excellent. So "IsDialogMessage" is the key. I've never used it, so now just a little more research to see how to use it in my normal "dialog as main" program.
Thank you. :U
isDialogMessage makes a regular window act like a dialog window. It allows the tab key to work like you would expect without
having to subclass all your controls. BUT it also handles other keys like Enter. This is where the problem comes in. There is a way
to let windows know that your subclass procedure will handle certain keys by processing the WM_GETDLGCODE message. Its not
exactly well explained at MSDN on how to do this. If you remove the comments from the isDialogMessage in the main message
loop in the posted code above and replace the subclass procedure with this new one, then the code will work as expected.
isDialogMessage will still handle all the keys it normally would except for the Enter key which we handle ourself. Since I don't use
dialog boxes as a main window, you may have to adjust to suit your own needs. Good Luck !
NewEditProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
; Only allow '1' thru '9' and backspace and display message box if enter key
.if uMsg == WM_CHAR
mov eax,wParam
.if (al >= '0' && al <= '9') || al == VK_BACK
invoke CallWindowProc,OldEditProc,hWin,uMsg,wParam,lParam
ret
.elseif al == VK_RETURN
invoke MessageBox,hWnd,addr szAboutText,addr szProgramName,MB_OK
.else
ret
.endif
.elseif uMsg == WM_GETDLGCODE
mov ecx,lParam
.if ecx != NULL
mov eax,(MSG ptr[ecx]).message
mov ebx,(MSG ptr[ecx]).wParam
.if (eax == WM_KEYDOWN) && (ebx == VK_RETURN)
mov eax,DLGC_WANTALLKEYS
ret
.endif
.endif
.endif
invoke CallWindowProc,OldEditProc,hWin,uMsg,wParam,lParam
ret
NewEditProc endp
Got it, Thanks!
I had too many bits and pieces of code from too many different places. I started over clean and got it to work with what I've learned. I post some code as soon as I get it cleanup up a bit. One of the key bits is to trap uMsg==WM_KEYUP. Enter keys don't show up for me when uMsg==WM_KEYDOWN.
Ok, one final post on this turkey thread. After 5 days of struggling with windows, I've finally made the simple combo box behave. With this code the user can type in what he wants and hit enter to accept it. I think this should have been an intrinsic part of a simple combo box from its creation. Oh well.
I think I have solved all the problems except I don't get the CBN_KILLFOCUS message when the combobox is subclassed (??). The user has to press the escape key to abort which seems a little non-intuitive. Feel free to offer any suggestions or comments.
; An example of a simple combo box (style=CBS_SIMPLE) with the edit box portion subclassed so that
; the user can type in his own selection and hit return to accept it, or hit escape to abort.
.686p
.model flat, stdcall
option casemap :none ; case sensitive
.nolist
include windows.inc
; some miscellaneous macros
soff Macro QuotedText:Vararg ; returns offset to a string
Local LocalText
.data
LocalText db QuotedText,0
.code
EXITM <offset LocalText>
Endm
GetHandle Macro MyId:Req,MyHandle:Req
invoke GetDlgItem,hWin,MyId
mov MyHandle,eax ; save handle to control
endm
uselib MACRO libname
include libname.inc
includelib libname.lib
ENDM
uselib user32
uselib kernel32
uselib comctl32
;uselib shell32
;uselib comdlg32
;uselib gdi32
;uselib masm32 ; for debug
;uselib debug ; for debug
.listall
inv equ invoke
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
cb equ 1041 ; id of combobox
.data?
hInst dd ?
hWin dd ?
hcb dd ? ; handle of combobox
hComboEdit dd ? ; edit part of combo box
OldComboEdit dd ? ; address of combobox edit before subclass
buff db 100 dup (?) ; scratch
COMBOBOXINFO STRUCT
cbSize DWORD ?
rcItem RECT <>
rcButton RECT <>
stateButton DWORD ?
hwndCombo DWORD ?
hwndItem DWORD ?
hwndList DWORD ?
COMBOBOXINFO ENDS
ci COMBOBOXINFO <?>
.code
Program:
invoke GetModuleHandle, NULL
mov hInst,eax
invoke InitCommonControls
invoke DialogBoxParam, hInst, 101, 0, ADDR DlgProc, 0
invoke ExitProcess, eax
DlgProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg == WM_COMMAND
mov eax,wParam ; identifier of the control, or accelerator
mov edx,eax ; check hiword (notification code)
shr edx,16
.if lParam==0 ; menu item or accelerator if zero, else a control
; etc
.else ; a control
.if ax==cb ; our combo box
.if dx==CBN_DBLCLK ; user double clicked or hit enter key.
call ComboGetText
.elseif dx==CBN_KILLFOCUS ; don't seem to get this with the combobox edit subclassed?
;Normally, you would close the combobox here
.elseif dx==CBN_SELENDCANCEL ; this is not normally sent for a CBS_SIMPLE style combobox,
; we faked it in EditWndProc so user could abort by hitting escape key.
invoke MessageBox,0,soff("combo box selection aborted, would normally just close the combobox here"),0,0
.endif
.endif
.endif
.elseif uMsg == WM_INITDIALOG
mov eax,hWnd ; save our handle
mov hWin,eax
GetHandle cb,hcb ; the the handle to the combobox
mov ci.cbSize,sizeof(COMBOBOXINFO) ; now subclass the edit box within the combobox
inv GetComboBoxInfo,hcb,addr ci
mov eax,ci.hwndItem ; want the edit box
mov hComboEdit,eax ; save
inv SetWindowLong,hComboEdit,GWL_WNDPROC,EditWndProc
mov OldComboEdit,eax ; save old address for later
call FillCombo ; put some data in for our tests
invoke SetFocus,hComboEdit ;normally we would fill on demand but this is just a test
.elseif uMsg == WM_CLOSE
invoke EndDialog,hWin,0
.endif
xor eax,eax
ret
DlgProc endp
EditWndProc PROC hEdit:DWord,uMsg:DWord,wParam:DWord,lParam:DWord
;traps messages to the Combobox to catch enter key
.if uMsg==WM_KEYDOWN
mov eax,hEdit
.if eax==hComboEdit ; that's us
mov eax,wParam
.if eax==VK_RETURN ; treat as double click
inv PostMessage,hWin,WM_COMMAND,(CBN_DBLCLK shl 16) or cb,hcb
.elseif eax==VK_ESCAPE ; user wants to quit
inv PostMessage,hWin,WM_COMMAND,(CBN_SELENDCANCEL shl 16) or cb,hcb
.endif
.endif
.elseif uMsg == WM_GETDLGCODE
mov eax,DLGC_WANTALLKEYS ; need this to get enter key on WM_KEYDOWN
ret
.endif
inv CallWindowProc,OldComboEdit,hEdit,uMsg,wParam,lParam
ret
EditWndProc Endp
ComboGetText proc ; retrieve the users entry and put it in an edit box for a test
inv SendMessage,hcb,WM_GETTEXT,100,addr buff
invoke GetDlgItem,hWin,1004
invoke SendMessage,eax,WM_SETTEXT,0,addr buff
ret
ComboGetText EndP
FillCombo proc ; add test junk lines
inv SendMessage,hcb,CB_ADDSTRING,0,soff("aline 1")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("bline 2")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("cline 3")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("dline 4")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("eline 5")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("fline 6")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("gline 7")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("hline 8")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("iline 9")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("jline 10")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("kline 11")
inv SendMessage,hcb,CB_ADDSTRING,0,soff("lline 12")
ret
FillCombo EndP
end Program
[attachment deleted by admin]