News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Simple Combo Box

Started by Jimg, August 10, 2005, 03:54:52 PM

Previous topic - Next topic

Jimg

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.

Jimg

#1
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.

Mark Jones

This may be a stupid idea, but is there an ES_WANTRETURN equivalent for combo boxes?
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

Jimg

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.

Jimg

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?

Jimg

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.   :'(

anon

#6
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

Jimg

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"

anon

#8
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.

Jimg

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

anon

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


Jimg

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.

Jimg

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]