An Issue with Combobox Focus...

Windows specific questions.
Post Reply
datwill310
Posts: 355
Joined: May 29, 2015 20:37

An Issue with Combobox Focus...

Post by datwill310 »

Hi all.

I've run into a little problem in one of my programs.

In my window, I have a combobox and a button. The combobox has several items, and the program selects an item for you at the very beginning of the program. I, the user, select the edit control within the combobox to select all the text.

When the button is clicked, I would like the selected text within the combobox to be copied.

Trouble is, once the user has clicked off of the combobox and clicked the button, the edit control loses focus and therefore no text is copied as no text is selected because of this.

Do any of you guys know how to keep combobox/edit focus in this case?

I was reading this blog, and even though the blogger said he would post the answer to a very similar problem, he hadn't: at least not for the next several blogs in the linked series. It helped part of the way, but not all the way, if you see what I mean.

Thanks for your time.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Re: An Issue with Combobox Focus...

Post by D.J.Peters »

pseudo code:

Code: Select all

var last_item = -1
if combo_clicked then last_item = combo_with_focus_get_active_item
if copy_button_clicked and (last_item <> -1) then copy(combo_get_item(last_item))
datwill310
Posts: 355
Joined: May 29, 2015 20:37

Re: An Issue with Combobox Focus...

Post by datwill310 »

D.J.Peters wrote:pseudo code:

Code: Select all

var last_item = -1
if combo_clicked then last_item = combo_with_focus_get_active_item
if copy_button_clicked and (last_item <> -1) then copy(combo_get_item(last_item))
Thanks!
Pierre Bellisle
Posts: 56
Joined: Dec 11, 2016 17:22

Re: An Issue with Combobox Focus...

Post by Pierre Bellisle »

Hey,

For the fun of it, one may also subclass the edit part of the combobox to get access to usefull messages.

Pierre

Image

Code: Select all

#Include Once "windows.bi"
#Include Once "win/commctrl.bi"
#Include Once "win/shellapi.bi"

Dim Shared pComboboxEdit As WndProc
Dim Shared hStatic       As HWnd
Dim Shared SelStart      As wParam 
Dim Shared SelEnd        As lParam
Dim Shared hInstance     As HINSTANCE : hInstance = GetModuleHandle(NULL)

Function GetEditSelText(ByVal hWnd As HWND) As String
 'Get the selected text of the edit part of the combobox control
 Dim TextLen As Long 
 Dim sBuffer As String

 SendMessage(hWnd, EM_GETSEL, Cast(wParam, @SelStart), Cast(lParam, @SelEnd)) 'Get selection
 TextLen = SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0) + 1
 sBuffer = String(TextLen, 0)
 SendMessage(hWnd, WM_GETTEXT, TextLen, Cast(lParam, StrPtr(sBuffer)))
 Function = Mid(sBuffer, SelStart + 1, SelEnd - SelStart)

End Function

Function ComboboxEditProc(ByVal hWnd As HWND, ByVal uMsg As UINT, ByVal wParam As WPARAM, ByVal lParam As LPARAM) As Integer
 'Subclassed edit part of the combobox control

 Select Case uMsg

   Case WM_SETFOCUS 
     SetWindowText(hStatic, "SetFocus: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

   Case WM_KILLFOCUS
     SetWindowText(hStatic, "KillFocus: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

   Case EM_SETSEL
     wParam = SelStart 'Memorise SelStart
     lParam = SelEnd   'Memorise SelEnd

   Case WM_KEYUP, WM_LBUTTONUP
     SetWindowText(hStatic, "KeyUp/LButtonUp: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

 End Select

 Function = CallWindowProc(pComboboxEdit, hWnd, uMsg, wParam, lParam)

End Function

Function WndProc(ByVal hWnd As HWND, ByVal uMsg As UINT, ByVal wParam As WPARAM, ByVal lParam As LPARAM) As Integer
 Static ComboInfo  As COMBOBOXINFO
 Static hCombo     As HWND
 Static hButton    As HWND
 Dim    sEdit      As String
 Dim    EditLen    As DWORD
 Static StaticId   As Word = 101
 Static ComboBoxId As Word = 201
 Static ButtonId   As Word = 301

 Function = 0

 Select Case (uMsg)
   
   Case WM_CREATE
     Dim As HFONT hFont = GetStockObject(DEFAULT_GUI_FONT) 'Get a default font handle for the following controls

     hStatic = CreateWindowEx(WS_EX_STATICEDGE, WC_STATIC, "", _'Create a static label to show info
                              WS_CHILD Or WS_VISIBLE Or SS_CENTER Or _
                              SS_CENTERIMAGE Or SS_NOTIFY,_
                              50, 15, 260, 20,_
                              hWnd, Cast(HMENU, Cast(LONG_PTR, StaticId)), hInstance, 0)
     SendMessage(hStatic, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set static label font

     hCombo = CreateWindowEx(WS_EX_CLIENTEDGE, WC_COMBOBOX, "", _'Create a combobox
                             WS_CHILD Or WS_VISIBLE Or WS_BORDER Or WS_TABSTOP Or _
                             CBS_DROPDOWN Or CBS_NOINTEGRALHEIGHT Or CBS_SORT,_
                             100, 60, 160, 140,_
                             hWnd, Cast(HMENU, Cast(LONG_PTR, ComboBoxId)), hInstance, 0)
     SendMessage(hCombo, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set ComboBox font

     Dim As ZString * 64 szItem
     SendMessage(hCombo, WM_SETREDRAW, FALSE, 0) 'Stop redraw to save time
     szItem = "Grenada"     : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Trinidad"    : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "St. Vincent" : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "St. Lucia"   : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Antigua"     : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Dominica"    : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     SendMessage(hCombo, CB_SETCURSEL, 2, 0) 'Choose third item
     SendMessage(hCombo, WM_SETREDRAW, TRUE, 0) 'Permit to redraw

     ComboInfo.cbSize = SizeOf(COMBOBOXINFO) 'Preset ComboInfo version
     SendMessage(hCombo, CB_GETCOMBOBOXINFO, 0, Cast(lParam, VarPtr(ComboInfo))) 'Get ComboBox info to have the edit handle

     'Subclass the edit part of the ComboBox     
     pComboboxEdit = Cast(WndProc, SetWindowLongPtr(ComboInfo.hwndItem, GWLP_WNDPROC, Cast(LONG_PTR, ProcPtr(ComboboxEditProc)))) 

     hButton = CreateWindowEx(NULL, "Button", "OK", _ 'Create a button
                              WS_CHILD Or WS_VISIBLE Or WS_TABSTOP Or _
                              BS_PUSHBUTTON Or BS_CENTER Or BS_VCENTER, _
                              140, 110, 80, 25, _
                              hWnd, Cast(HMENU, Cast(LONG_PTR, ButtonId)), hInstance, 0) 
     SendMessage(hButton, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set Button font

     SelStart = 2 'Preset a selection
     SelEnd   = 5 'Preset a selection
     SendMessage(hCombo, CB_SETEDITSEL, 0, MAKELONG(SelStart, SelEnd)) 'Select a portion of the combobox's edit part
     SetFocus(hCombo)

  Case WM_COMMAND
    Dim Code As Word = HiWord(wParam) 'Notification code
    Select Case LoWord(wParam) 'Control id

      Case ComboBoxId 'The comboBox
        Select Case Code
          Case CBN_SETFOCUS
          Case CBN_KILLFOCUS
          Case CBN_CLOSEUP 
          Case CBN_EDITUPDATE
          Case CBN_EDITCHANGE
          Case CBN_DROPDOWN
          Case CBN_SELCHANGE 
          Case CBN_SELENDOK  
            SelStart = 0  'Select from the start
            SelEnd   = -1 'Select to the end
            PostMessage(hWnd, WM_APP, CBN_SELENDOK, 0) 'PostMessage to let ComboBox complete its stuff  
        End Select

      Case ButtonId 'The buttom
        If Code = BN_CLICKED Or Code = 1 Then
          'Do nothing
        End If

    End Select

  Case WM_APP 'Custom message
    SetWindowText(hStatic, "WM_APP: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(ComboInfo.hwndItem) & """")

  Case WM_KEYDOWN
    If(LoByte(wParam) = 27) Then 'Escape key
      PostMessage(hWnd, WM_CLOSE, 0, 0)
    End If

  Case WM_DESTROY
    'UnSubclass the edit part of the ComboBox
    If pComboboxEdit Then SetWindowLongPtr(ComboInfo.hwndItem, GWLP_WndProc, Cast(LONG_PTR, pComboboxEdit))   
    PostQuitMessage(0) 'Die gracefully
    Exit Function

 End Select

 Function = DefWindowProc(hWnd, uMsg, wParam, lParam)

End Function

 Dim wMsg       As MSG
 Dim wcls       As WNDCLASS
 Dim WindowSize As SIZEL
 Dim hWnd       As HWND
 Dim hIcon      As HICON

 hIcon = ExtractIcon(GetModuleHandle(""), "Shell32.dll", 112)
 WindowSize.cx = 360
 WindowSize.cy = 195

 Dim appName As String => "ComboBoxEditSel"

 wcls.style         = CS_HREDRAW Or CS_VREDRAW
 wcls.lpfnWndProc   = @WndProc
 wcls.cbClsExtra    = 0
 wcls.cbWndExtra    = 0
 wcls.hInstance     = hInstance
 wcls.hIcon         = hIcon 
 wcls.hCursor       = LoadCursor(NULL, IDC_ARROW)
 wcls.hbrBackground = Cast(HGDIOBJ, COLOR_BTNFACE + 1) 
 wcls.lpszMenuName  = NULL
 wcls.lpszClassName = StrPtr(appName)

 If RegisterClass(@wcls) Then 'RegisterClass success
   hWnd = CreateWindowEx(WS_EX_WINDOWEDGE, AppName, AppName, _
                         WS_OVERLAPPED Or WS_BORDER Or WS_DLGFRAME Or WS_CAPTION Or _
                         WS_SYSMENU Or WS_MINIMIZEBOX Or WS_CLIPSIBLINGS Or WS_VISIBLE, _
                         (GetSystemMetrics(SM_CXSCREEN) - WindowSize.cx) / 2, _
                         (GetSystemMetrics(SM_CYSCREEN) - WindowSize.cy) / 2, _
                         WindowSize.cx, WindowSize.cy, _
                         NULL, NULL, hInstance, NULL)

   'Build a messages pump
   Do Until(GetMessage(@wMsg, NULL, 0, 0) = FALSE)
     If IsDialogMessage(hWnd,  @wMsg ) = 0 Then
       TranslateMessage(@wMsg)
       DispatchMessage(@wMsg)
     End If
   Loop
 End If
 DestroyIcon(hIcon) 'Clean up
 End
Last edited by Pierre Bellisle on Jan 15, 2017 19:03, edited 1 time in total.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: An Issue with Combobox Focus...

Post by Josep Roca »

Hi Pierre,

To make it to work in both 32 and 64 bit, change GWL_WndProc to GWLP_WndProc in this line:

Code: Select all

If pComboboxEdit Then SetWindowLongPtr(ComboInfo.hwndItem, GWL_WndProc, Cast(LONG_PTR, pComboboxEdit))   
BTW to cast the control id, instead of the define you can also use:

Code: Select all

Cast(HMENU, cast(LONG_PTR, ComboBoxId))
Keep your nice examples coming.
datwill310
Posts: 355
Joined: May 29, 2015 20:37

Re: An Issue with Combobox Focus...

Post by datwill310 »

Pierre Bellisle wrote:Hey,

For the fun of it, one may also subclass the edit part of the combobox to get access to usefull messages.

Pierre

Image

Code: Select all

#Include Once "windows.bi"
#Include Once "win/commctrl.bi"
#Include Once "win/shellapi.bi"

Dim Shared pComboboxEdit As WndProc
Dim Shared hStatic       As HWnd
Dim Shared SelStart      As wParam 
Dim Shared SelEnd        As lParam
Dim Shared hInstance     As HINSTANCE : hInstance = GetModuleHandle(NULL)

'Used To Cast a Word control id in a 32 Or 64 Bit variable
#Define MakeUInteger(HighWord, LowWord) CUInt(LowWord Shl 16 Or HighWord) 

Function GetEditSelText(ByVal hWnd As HWND) As String
 'Get the selected text of the edit part of the combobox control
 Dim TextLen As Long 
 Dim sBuffer As String

 SendMessage(hWnd, EM_GETSEL, Cast(wParam, @SelStart), Cast(lParam, @SelEnd)) 'Get selection
 TextLen = SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0) + 1
 sBuffer = String(TextLen, 0)
 SendMessage(hWnd, WM_GETTEXT, TextLen, Cast(lParam, StrPtr(sBuffer)))
 Function = Mid(sBuffer, SelStart + 1, SelEnd - SelStart)

End Function

Function ComboboxEditProc(ByVal hWnd As HWND, ByVal uMsg As UINT, ByVal wParam As WPARAM, ByVal lParam As LPARAM) As Integer
 'Subclassed edit part of the combobox control

 Select Case uMsg

   Case WM_SETFOCUS 
     SetWindowText(hStatic, "SetFocus: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

   Case WM_KILLFOCUS
     SetWindowText(hStatic, "KillFocus: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

   Case EM_SETSEL
     wParam = SelStart 'Memorise SelStart
     lParam = SelEnd   'Memorise SelEnd

   Case WM_KEYUP, WM_LBUTTONUP
     SetWindowText(hStatic, "KeyUp/LButtonUp: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(hWnd) & """")

 End Select

 Function = CallWindowProc(pComboboxEdit, hWnd, uMsg, wParam, lParam)

End Function

Function WndProc(ByVal hWnd As HWND, ByVal uMsg As UINT, ByVal wParam As WPARAM, ByVal lParam As LPARAM) As Integer
 Static ComboInfo  As COMBOBOXINFO
 Static hCombo     As HWND
 Static hButton    As HWND
 Dim    sEdit      As String
 Dim    EditLen    As DWORD
 Static StaticId   As Word = 101
 Static ComboBoxId As Word = 201
 Static ButtonId   As Word = 301

 Function = 0

 Select Case (uMsg)
   
   Case WM_CREATE
     Dim As HFONT hFont = GetStockObject(DEFAULT_GUI_FONT) 'Get a default font handle for the following controls

     hStatic = CreateWindowEx(WS_EX_STATICEDGE, WC_STATIC, "", _'Create a static label to show info
                              WS_CHILD Or WS_VISIBLE Or SS_CENTER Or _
                              SS_CENTERIMAGE Or SS_NOTIFY,_
                              50, 15, 260, 20,_
                              hWnd, Cast(HMENU, MakeUInteger(StaticId, 0)), hInstance, 0)
     SendMessage(hStatic, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set static label font

     hCombo = CreateWindowEx(WS_EX_CLIENTEDGE, WC_COMBOBOX, "", _'Create a combobox
                             WS_CHILD Or WS_VISIBLE Or WS_BORDER Or WS_TABSTOP Or _
                             CBS_DROPDOWN Or CBS_NOINTEGRALHEIGHT Or CBS_SORT,_
                             100, 60, 160, 140,_
                             hWnd, Cast(HMENU, MakeUInteger(ComboBoxId, 0)), hInstance, 0)
     SendMessage(hCombo, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set ComboBox font

     Dim As ZString * 64 szItem
     SendMessage(hCombo, WM_SETREDRAW, FALSE, 0) 'Stop redraw to save time
     szItem = "Grenada"     : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Trinidad"    : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "St. Vincent" : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "St. Lucia"   : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Antigua"     : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     szItem = "Dominica"    : SendMessage(hCombo, CB_ADDSTRING, 0, Cast(lParam, VarPtr(szItem))) 'Add combobox item
     SendMessage(hCombo, CB_SETCURSEL, 2, 0) 'Choose third item
     SendMessage(hCombo, WM_SETREDRAW, TRUE, 0) 'Permit to redraw

     ComboInfo.cbSize = SizeOf(COMBOBOXINFO) 'Preset ComboInfo version
     SendMessage(hCombo, CB_GETCOMBOBOXINFO, 0, Cast(lParam, VarPtr(ComboInfo))) 'Get ComboBox info to have the edit handle

     'Subclass the edit part of the ComboBox     
     pComboboxEdit = Cast(WndProc, SetWindowLongPtr(ComboInfo.hwndItem, GWLP_WNDPROC, Cast(LONG_PTR, ProcPtr(ComboboxEditProc)))) 

     hButton = CreateWindowEx(NULL, "Button", "OK", _ 'Create a button
                              WS_CHILD Or WS_VISIBLE Or WS_TABSTOP Or _
                              BS_PUSHBUTTON Or BS_CENTER Or BS_VCENTER, _
                              140, 110, 80, 25, _
                              hWnd, Cast(HMENU, MakeUInteger(ButtonId, 0)), hInstance, 0)
     SendMessage(hButton, WM_SETFONT, Cast(wParam, hFont), TRUE) 'Set Button font

     SelStart = 2 'Preset a selection
     SelEnd   = 5 'Preset a selection
     SendMessage(hCombo, CB_SETEDITSEL, 0, MAKELONG(SelStart, SelEnd)) 'Select a portion of the combobox's edit part
     SetFocus(hCombo)

  Case WM_COMMAND
    Dim Code As Word = HiWord(wParam) 'Notification code
    Select Case LoWord(wParam) 'Control id

      Case ComboBoxId 'The comboBox
        Select Case Code
          Case CBN_SETFOCUS
          Case CBN_KILLFOCUS
          Case CBN_CLOSEUP 
          Case CBN_EDITUPDATE
          Case CBN_EDITCHANGE
          Case CBN_DROPDOWN
          Case CBN_SELCHANGE 
          Case CBN_SELENDOK  
            SelStart = 0  'Select from the start
            SelEnd   = -1 'Select to the end
            PostMessage(hWnd, WM_APP, CBN_SELENDOK, 0) 'PostMessage to let ComboBox complete its stuff  
        End Select

      Case ButtonId 'The buttom
        If Code = BN_CLICKED Or Code = 1 Then
          'Do nothing
        End If

    End Select

  Case WM_APP 'Custom message
    SetWindowText(hStatic, "WM_APP: Selection is " & SelStart & " to " & SelEnd & " """ & GetEditSelText(ComboInfo.hwndItem) & """")

  Case WM_KEYDOWN
    If(LoByte(wParam) = 27) Then 'Escape key
      PostMessage(hWnd, WM_CLOSE, 0, 0)
    End If

  Case WM_DESTROY
    'UnSubclass the edit part of the ComboBox
    If pComboboxEdit Then SetWindowLongPtr(ComboInfo.hwndItem, GWL_WndProc, Cast(LONG_PTR, pComboboxEdit))   
    PostQuitMessage(0) 'Die gracefully
    Exit Function

 End Select

 Function = DefWindowProc(hWnd, uMsg, wParam, lParam)

End Function

 Dim wMsg       As MSG
 Dim wcls       As WNDCLASS
 Dim WindowSize As SIZEL
 Dim hWnd       As HWND
 Dim hIcon      As HICON

 hIcon = ExtractIcon(GetModuleHandle(""), "Shell32.dll", 112)
 WindowSize.cx = 360
 WindowSize.cy = 195

 Dim appName As String => "ComboBoxEditSel"

 wcls.style         = CS_HREDRAW Or CS_VREDRAW
 wcls.lpfnWndProc   = @WndProc
 wcls.cbClsExtra    = 0
 wcls.cbWndExtra    = 0
 wcls.hInstance     = hInstance
 wcls.hIcon         = hIcon 
 wcls.hCursor       = LoadCursor(NULL, IDC_ARROW)
 wcls.hbrBackground = Cast(HGDIOBJ, COLOR_BTNFACE + 1) 
 wcls.lpszMenuName  = NULL
 wcls.lpszClassName = StrPtr(appName)

 If RegisterClass(@wcls) Then 'RegisterClass success
   hWnd = CreateWindowEx(WS_EX_WINDOWEDGE, AppName, AppName, _
                         WS_OVERLAPPED Or WS_BORDER Or WS_DLGFRAME Or WS_CAPTION Or _
                         WS_SYSMENU Or WS_MINIMIZEBOX Or WS_CLIPSIBLINGS Or WS_VISIBLE, _
                         (GetSystemMetrics(SM_CXSCREEN) - WindowSize.cx) / 2, _
                         (GetSystemMetrics(SM_CYSCREEN) - WindowSize.cy) / 2, _
                         WindowSize.cx, WindowSize.cy, _
                         NULL, NULL, hInstance, NULL)

   'Build a messages pump
   Do Until(GetMessage(@wMsg, NULL, 0, 0) = FALSE)
     If IsDialogMessage(hWnd,  @wMsg ) = 0 Then
       TranslateMessage(@wMsg)
       DispatchMessage(@wMsg)
     End If
   Loop
 End If
 DestroyIcon(hIcon) 'Clean up
 End
Thanks also for your time and the great example!
Pierre Bellisle
Posts: 56
Joined: Dec 11, 2016 17:22

Re: An Issue with Combobox Focus...

Post by Pierre Bellisle »

Hi datwil310,

It was a pleasure, have a great day. :-)
Pierre Bellisle
Posts: 56
Joined: Dec 11, 2016 17:22

Re: An Issue with Combobox Focus...

Post by Pierre Bellisle »

Hey Josep,

Always good to ear from you,
Yes, you are right, GWLP_WndProc should have been used.
And Cast(HMENU, Cast(LONG_PTR, SomeWordContolId)) is a cleaner approach.
I did update the code above.

Thank. :-)
Post Reply