Page 1 of 2

[SOLVED] Multi-column ComboBoxGadget and use of separating delimiter

Posted: Sun Nov 10, 2024 7:24 pm
by PBJim
A delimiting character #LF$ appears to work here, as a means of separating the code from an associated description within the combobox, when we later write it to file.

Is there a better way however, for Windows only?

We have no control over the users' insertion of a space into coded values, nor the length of their codes, otherwise I would obviously just split the value on the space.

Incidentally, I see that multiple-column comboboxes are a frequent requirement and a few other platforms seem to have coded solutions — https://ibb.co/djfd8T4 Perhaps there is something available in PureBasic that I'm not aware of. If there is, I'd be very grateful to know, thanks.

Code: Select all

EnableExplicit

; **
; **  Two-column ComboBoxGadget, see line 37
; **
Define  counter
Define  item, value
Define  Event

NewList CodeDesc.s()

AddElement(CodeDesc()) : CodeDesc() = "PINEAPPLE"
AddElement(CodeDesc()) : CodeDesc() = "BANANA"
AddElement(CodeDesc()) : CodeDesc() = "DRAGON FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "STAR FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "MANGO"
AddElement(CodeDesc()) : CodeDesc() = "RAMBUTAN"
AddElement(CodeDesc()) : CodeDesc() = "WATERMELON"
AddElement(CodeDesc()) : CodeDesc() = "POMEGRANATE"
AddElement(CodeDesc()) : CodeDesc() = "LYCHEE"
AddElement(CodeDesc()) : CodeDesc() = "MANGOSTEEN"
AddElement(CodeDesc()) : CodeDesc() = "TAMARIND"
AddElement(CodeDesc()) : CodeDesc() = "PASSION FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "PAPAYA"

If OpenWindow(0, 0, 0, 270, 180, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

  ComboBoxGadget(2, 10, 20, 250, 21)
  
  ForEach(CodeDesc())
    counter + 1
    
    ; **
    ; **  Insert delimiter #LF$ to enable ComboBoxGadget value to be extracted without its
    ; **  associated description
    ; **
    AddGadgetItem(2, -1, "STK" + RSet(Str(counter), 3, "0") + #LF$ + "   " + CodeDesc())
  Next

  SetGadgetState(2, 1)

  Repeat
    Event = WaitWindowEvent()
    If Event = #PB_Event_Gadget
      item = GetGadgetState(2)
      
      Select EventGadget()
        Case 2
          If item <> -1
            MessageRequester("Stock code", StringField(GetGadgetItemText(2, item), 1, #LF$))
          EndIf
          
      EndSelect
    EndIf
  Until Event = #PB_Event_CloseWindow

EndIf

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Mon Nov 11, 2024 12:58 pm
by spikey
PBJim wrote: Sun Nov 10, 2024 7:24 pm We have no control over the users' insertion of a space into coded values, nor the length of their codes, otherwise I would obviously just split the value on the space.
I don't know why you think you don't have any control over the values, you do. The data in the example you give looks pretty uniform to me, perhaps some more real world problems detail is needed.

I guess you're gathering the data via a user interface or importing the data from an external source. So build clean up code into the user interface or the import procedure. The trick is to make the incoming data conform to an expectation you can handle as you import/gather it and then add correctly formatted values to the gadget when you prepare it.

Examples:
1. You can restrict a StringGadget to numeric, lowercase or uppercase entry when you create it.
2. You can fix its length via SetGadgetAttribute.
3. Or you can do the same with LCase() and UCase() if you're importing.
4. Something like x$ = ReplaceString(x$, " ", " ") will remove double spaces.
5. Left() will truncate strings to a specific length.
6. If you have more complex requirements have a look at the RegEx library, you can do all sorts of manipulations with that.

In this case, if I were constructing this interface I'd be storing the ListIndex value in the SetGadgetItemData() as I add the values to the combo. Then I can retrieve this from the selected item using GetGadgetItemData() when a selection is made. This points me back to the source data in the list. I can add anything to the list I please to make things happen in the background.

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Mon Nov 11, 2024 1:07 pm
by Axolotl
Hi,
most flexible way is ownerdraw the content.

Code: Select all

; ComboBox OwnerDraw in PureBasic 
EnableExplicit 

Enumeration EWindow 1 
  #WND_Main 
EndEnumeration 

Enumeration EGadget 1 
  #GDT_strInfo 
  #GDT_cbbValues 
EndEnumeration 

#COMBOBOX_COLUMN_1  =  40   ; Pixels 
#COMBOBOX_COLUMN_2  = 160   ; Pixels 

#DI_NORMAL = $0003  ; Icon 

Global NewList CodeDesc.s()

AddElement(CodeDesc()) : CodeDesc() = "PINEAPPLE"
AddElement(CodeDesc()) : CodeDesc() = "BANANA"
AddElement(CodeDesc()) : CodeDesc() = "DRAGON FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "STAR FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "MANGO"
AddElement(CodeDesc()) : CodeDesc() = "RAMBUTAN"
AddElement(CodeDesc()) : CodeDesc() = "WATERMELON"
AddElement(CodeDesc()) : CodeDesc() = "POMEGRANATE"
AddElement(CodeDesc()) : CodeDesc() = "LYCHEE"
AddElement(CodeDesc()) : CodeDesc() = "MANGOSTEEN"
AddElement(CodeDesc()) : CodeDesc() = "TAMARIND"
AddElement(CodeDesc()) : CodeDesc() = "PASSION FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "PAPAYA"


Procedure WindowCallback(hWnd, Msg, WParam, LParam) 
  Protected result, hBrush, *lpdis.DRAWITEMSTRUCT, rc.RECT 
  Protected text$, t$, tlen, dtFlags  

  Select Msg 
    Case #WM_DRAWITEM 
      *lpdis = lParam  ; typecast to DRAWITEMSTRUCT 
      
      If *lpdis\CtlType = #ODT_COMBOBOX 
        SetBkMode_(*lpdis\hDC, #TRANSPARENT)    ; Text is rendered transparent 
        
        If *lpdis\ItemState & #ODS_FOCUS 
          hBrush = GetSysColorBrush_(#COLOR_HIGHLIGHT)  ; returns a cached brush instead of allocating a new one 
          FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
          
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_HIGHLIGHTTEXT)) 
        Else 
          FillRect_(*lpdis\hDC, *lpdis\rcItem, GetSysColorBrush_(#COLOR_WINDOW)) 
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_WINDOWTEXT)) 
        EndIf 

        If *lpdis\itemID <> -1 
          tlen = SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXTLEN, *lpdis\itemID, 0) ; lParam .. not used 
          If tlen > 0 
            text$ = Space(tlen) 
            SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXT, *lpdis\itemID, @text$) 
          EndIf 

          DrawIconEx_(*lpdis\hDC, *lpdis\rcItem\left+2   , *lpdis\rcItem\top+1, LoadIcon_(0, #IDI_ASTERISK), 16, 16, 0, 0, #DI_NORMAL) 

          dtFlags = #DT_LEFT | #DT_VCENTER 
          If Asc(text$) 
            ; column 1 
            t$ = StringField(text$, 1, #LF$) 
            If Asc(t$) 
              *lpdis\rcItem\left + #COMBOBOX_COLUMN_1 
              DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
            EndIf 

            ; column 2 
            t$ = StringField(text$, 2, #LF$) 
            If Asc(t$) 
              *lpdis\rcItem\left + #COMBOBOX_COLUMN_2 
              DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
            EndIf 
          EndIf 
        EndIf 
      
      EndIf 
    ; ProcedureReturn result 
  
  EndSelect 
  
  ; default means handling by PB internally 
  ;
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 


Procedure OpenMainWindow(WndW=400, WndH=100) 
  If OpenWindow(#WND_Main, 0, 0, WndW, WndH, "ComboBox OwnerDraw", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget | #PB_Window_TitleBar ) 
    StringGadget(#GDT_strInfo, 8, 8, WndW-16, 20, "Selected Combobox item is .... ")  

    ComboBoxGadget(#GDT_cbbValues, 8, 40, WndW-16, 21, #CBS_HASSTRINGS | #CBS_OWNERDRAWFIXED) 

    SetWindowCallback(@WindowCallback(), #WND_Main) 

    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False
EndProcedure 

Procedure Main() 
  Protected index, text$  

  If OpenMainWindow() 

    ForEach(CodeDesc())
      index + 1
      ; **
      ; **  Insert delimiter #LF$ to enable ComboBoxGadget value to be extracted without its
      ; **  associated description
      ; **
      AddGadgetItem(#GDT_cbbValues, -1, "STK" + RSet(Str(index), 3, "0") + #LF$ + CodeDesc()) 
    Next  

  Repeat 
    Select WaitWindowEvent() 
      Case #PB_Event_CloseWindow 
        Break ; bye 
      Case #PB_Event_Gadget 
        Select EventGadget() 
          Case #GDT_cbbValues 
            text$ = GetGadgetText(#GDT_cbbValues) 
            SetGadgetText(#GDT_strInfo, "Stock code == " + StringField(text$, 1, #LF$)) 
        EndSelect 
    EndSelect 
  ForEver 

  EndIf 
  ProcedureReturn 0 
EndProcedure 

End Main() 

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Mon Nov 11, 2024 2:18 pm
by Axolotl
Hi,
maybe I did not understand your problem correctly. But this looks almost like your picture.... (IMHO) .

Code: Select all

EnableExplicit 

Enumeration EWindow 1 
  #WND_Main 
  #WND_FlyOver 
EndEnumeration 

Enumeration EGadget 1 
  #GDT_strInfo 
  #GDT_strValues
  #GDT_btnValues

  ; #WND_FlyOver 
  #GDT_lbFlyOver
EndEnumeration 


Global NewList CodeDesc.s()

AddElement(CodeDesc()) : CodeDesc() = "PINEAPPLE"
AddElement(CodeDesc()) : CodeDesc() = "BANANA"
AddElement(CodeDesc()) : CodeDesc() = "DRAGON FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "STAR FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "MANGO"
AddElement(CodeDesc()) : CodeDesc() = "RAMBUTAN"
AddElement(CodeDesc()) : CodeDesc() = "WATERMELON"
AddElement(CodeDesc()) : CodeDesc() = "POMEGRANATE"
AddElement(CodeDesc()) : CodeDesc() = "LYCHEE"
AddElement(CodeDesc()) : CodeDesc() = "MANGOSTEEN"
AddElement(CodeDesc()) : CodeDesc() = "TAMARIND"
AddElement(CodeDesc()) : CodeDesc() = "PASSION FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "PAPAYA"


Procedure SetListIconGadgetNoHeader(Gadget) 
  Protected hGad = GadgetID(Gadget) 
  ProcedureReturn SetWindowLongPtr_(hGad, #GWL_STYLE, GetWindowLongPtr_(hGad, #GWL_STYLE) | #LVS_NOCOLUMNHEADER)  
EndProcedure 

;-
;-{--== Window FlyOver ==----------------------------------------------------------------------------------------------
;-

Procedure WindowFlyOver_PerformCallback(hWnd, uMsg, wParam, lParam) 
  Select uMsg 
    Case #WM_ACTIVATE 
      ; user clicked outside the window / listbox 
      ; 
      If wParam = #WA_INACTIVE  
        HideWindow(#WND_FlyOver, 1) 
      EndIf 
  EndSelect 

  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 

; --- 

Procedure WindowFlyOver_CreateWindow() 
  Protected OldGadgetList 

  If OpenWindow(#WND_FlyOver, 0, 0, 400, 200, "", #PB_Window_BorderLess | #PB_Window_Invisible | #PB_Window_NoActivate) 
    StickyWindow(#WND_FlyOver, 1) ; because main window is also sticky 
    OldGadgetList = UseGadgetList(WindowID(#WND_FlyOver))  ; Create GadgetList and store old GadgetList
    ListIconGadget(#GDT_lbFlyOver, 0, 0, 320, 200, "", 80, #PB_ListIcon_AlwaysShowSelection|#PB_ListIcon_FullRowSelect|#PB_ListIcon_GridLines) 
      AddGadgetColumn(#GDT_lbFlyOver, 1, "", 120) 
      AddGadgetColumn(#GDT_lbFlyOver, 2, "", 80) 
    SetListIconGadgetNoHeader(#GDT_lbFlyOver) 
    SetWindowCallback(@WindowFlyOver_PerformCallback(), #WND_FlyOver) 
    UseGadgetList(oldGadgetList)             ; return to previous GadgetList 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 

; ---

Procedure WindowFlyOver_ShowWindow(DockToGadget, FlyOverWidth=#PB_Ignore) 
  Protected hGad, width, rct.RECT, pt.POINT 

  If IsGadget(DockToGadget) 
    hGad = GadgetID(DockToGadget) 
    GetClientRect_(hGad, @rct) 
    pt\x = rct\left - 1 
    pt\y = rct\bottom 
    ClientToScreen_(hGad, @pt) 

    If FlyOverWidth <> #PB_Ignore 
      width = FlyOverWidth 
    Else 
      width = rct\right - rct\left  ; new width of the window / gadget 
    EndIf 
    ;
    ResizeWindow(#WND_FlyOver, pt\x, pt\y, width, #PB_Ignore) 
    ResizeGadget(#GDT_lbFlyOver, #PB_Ignore, #PB_Ignore, width, #PB_Ignore) 
    ; 
    HideWindow(#WND_FlyOver, 0)  ; show now 
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 

; ---

Procedure WindowFlyOver_FillList(List Input$(), SelIndex, bKeepContent=#False) 
  Protected index 

  If IsGadget(#GDT_lbFlyOver) 
    If bKeepContent <> #False 
      ClearGadgetItems(#GDT_lbFlyOver) 
    EndIf 

    ForEach Input$() 
      index + 1
      AddGadgetItem(#GDT_lbFlyOver, -1, "STK" + RSet(Str(index), 3, "0")  + #LF$ + Input$() + #LF$ + "comment")   ; <== adapt to your needs 
      If (index - 1) & 1 
        SetGadgetItemColor(#GDT_lbFlyOver, index - 1, #PB_Gadget_BackColor, $FFF8F0) ; AliceBlue 
      EndIf 
    Next Input$() 
    SetGadgetState(#GDT_lbFlyOver, SelIndex) 

    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False 
EndProcedure 

; --- 

Procedure.s WindowFlyOver_GetSelectedText() 
  Protected result$ 

  result$ = GetGadgetText(#GDT_lbFlyOver) 
  result$ = StringField(result$, 1, #LF$)     ; <== adapt to your needs 

  HideWindow(#WND_FlyOver, 1) 
  ProcedureReturn result$ 
EndProcedure 

; todo 
; Procedure WindowFlyOver_Events(Event) 
; EndProcedure 

;} Eod of Window_FlyOver 


Procedure OpenMainWindow(WndW=400, WndH=100) 
  If OpenWindow(#WND_Main, 0, 0, WndW, WndH, "ComboBox OwnerDraw", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget | #PB_Window_TitleBar ) 
    StringGadget(#GDT_strInfo, 8, 8, WndW-16, 20, "Selected Combobox item is .... ")  

    StringGadget(#GDT_strValues, 8, 40, 120, 21, "") 
    ButtonGadget(#GDT_btnValues, 128, 40, 32, 21, Chr($25BC)) ; "V") 
    
    WindowFlyOver_CreateWindow() 

    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False
EndProcedure 

Procedure Main() 
  Protected index, text$ 

  If OpenMainWindow() 

    WindowFlyOver_FillList(CodeDesc(), 1) 

    text$ = WindowFlyOver_GetSelectedText() 
    SetGadgetText(#GDT_strValues, text$) 

    Repeat 
      Select WaitWindowEvent() 
        Case #PB_Event_CloseWindow 
          Break ; bye 
        Case #PB_Event_Gadget 
          Select EventGadget() 
            Case #GDT_btnValues 
              WindowFlyOver_ShowWindow(#GDT_strValues, 320) 
            Case #GDT_lbFlyOver 
              Select EventType() 
                Case #PB_EventType_LeftClick
                  text$ = WindowFlyOver_GetSelectedText() 
                  SetGadgetText(#GDT_strValues, text$) 
                  SetGadgetText(#GDT_strInfo, "Stock code == " + text$) 
              EndSelect 
          EndSelect 
      EndSelect 
    ForEver 

  EndIf 
  ProcedureReturn 0 
EndProcedure 

End Main() 

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Mon Nov 11, 2024 3:17 pm
by RASHAD
Hi
Workaround for fun :D

Code: Select all

LoadFont(0,"Arial",11)

OpenWindow(0,0,0,400,250,"Window",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ComboBoxGadget(1,10,10,180,24,#PB_ComboBox_Editable)  
SetGadgetFont(1,FontID(0))
OpenWindow(10,GadgetX(1,#PB_Gadget_ScreenCoordinate),GadgetY(1,#PB_Gadget_ScreenCoordinate),180,24,"",#PB_Window_BorderLess,WindowID(0))
SetWindowLongPtr_(WindowID(10), #GWL_EXSTYLE, #WS_EX_LAYERED )
SetLayeredWindowAttributes_(WindowID(10),#Blue , 1, #LWA_ALPHA)
HideWindow(10,1)
OpenWindow(1,GadgetX(1,#PB_Gadget_ScreenCoordinate),GadgetY(1,#PB_Gadget_ScreenCoordinate)+GadgetHeight(1),300,200,"",#PB_Window_BorderLess,WindowID(0))
HideWindow(1,1)
UseGadgetList(WindowID(1))
ListIconGadget(2,0,0,300,200,"",20,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
SetGadgetColor(2,#PB_Gadget_BackColor,$D5F3FE)
SetGadgetColor(2,#PB_Gadget_LineColor,$0)
AddGadgetColumn(2,1,"",100)
AddGadgetColumn(2,2,"",180)
For i = 0 To 10
  AddGadgetItem(2,i,Str(i)+#LF$+"Text "+Str(i)+#LF$+"another text "+Str(i))
Next

UseGadgetList(WindowID(0))

Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Quit = 1
      
    Case #PB_Event_Menu
      Select GetActiveGadget()
        Case 1
          SetGadgetState(1,EventMenu()-1)
          SetActiveGadget(10)
          Debug GetGadgetText(1)     
      EndSelect
      
    Case #WM_LBUTTONDOWN
      act = GetActiveGadget()
      Select  act
        Case 1
          HideWindow(10,0)
          HideWindow(1,0)
          
        Case 2
          HideWindow(10,1)
          HideWindow(1,1)                
          text$ = GetGadgetItemText(2,GetGadgetState(2),1)
          SetGadgetText(1,text$)                
          
      EndSelect
      
    Case #WM_LBUTTONUP
      
  EndSelect 
  
Until Quit = 1
End

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Mon Nov 11, 2024 4:29 pm
by breeze4me
Based on Axolotl's code, I modified the code to look like the image shown.

Code: Select all

; ComboBox OwnerDraw in PureBasic 
EnableExplicit 

Enumeration EWindow 1 
  #WND_Main 
EndEnumeration 

Enumeration EGadget 1 
  #GDT_strInfo 
  #GDT_cbbValues 
EndEnumeration 


#CB_GETCOMBOBOXINFO = 356

Structure COMBOBOXINFO Align #PB_Structure_AlignC
  cbSize.l
  rcItem.RECT
  rcButton.RECT
  stateButton.l
  hwndCombo.i
  hwndItem.i
  hwndList.i
EndStructure

#COMBOBOX_COLUMN_0 = 60

Global NewList CodeDesc.s()

AddElement(CodeDesc()) : CodeDesc() = "PINEAPPLE"
AddElement(CodeDesc()) : CodeDesc() = "BANANA"
AddElement(CodeDesc()) : CodeDesc() = "DRAGON FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "STAR FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "MANGO"
AddElement(CodeDesc()) : CodeDesc() = "RAMBUTAN"
AddElement(CodeDesc()) : CodeDesc() = "WATERMELON"
AddElement(CodeDesc()) : CodeDesc() = "POMEGRANATE"
AddElement(CodeDesc()) : CodeDesc() = "LYCHEE"
AddElement(CodeDesc()) : CodeDesc() = "MANGOSTEEN"
AddElement(CodeDesc()) : CodeDesc() = "TAMARIND"
AddElement(CodeDesc()) : CodeDesc() = "PASSION FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "PAPAYA"


Procedure WindowCallback(hWnd, Msg, WParam, LParam) 
  Protected result, hBrush, *lpdis.DRAWITEMSTRUCT
  Protected text$, t$, tlen, dtFlags  
  
  Select Msg
    Case #WM_DRAWITEM
      *lpdis = lParam  ; typecast to DRAWITEMSTRUCT 
      If *lpdis\CtlType = #ODT_COMBOBOX 
        
        Protected cbinfo.COMBOBOXINFO, rt.RECT
        
        ; Change the listbox size of the combobox.
        If IsGadget(WParam) And GadgetType(WParam) = #PB_GadgetType_ComboBox
          cbinfo\cbSize = SizeOf(COMBOBOXINFO)
          SendMessage_(GadgetID(WParam), #CB_GETCOMBOBOXINFO, 0, @cbinfo)
          
          If cbinfo\hwndList
            GetWindowRect_(cbinfo\hwndList, rt)
            If rt\right - rt\left <> DesktopScaledX(200)
              MoveWindow_(cbinfo\hwndList, rt\left, rt\top, DesktopScaledX(200), rt\bottom - rt\top, 0)
            EndIf
          EndIf
        EndIf
        
        SetBkMode_(*lpdis\hDC, #TRANSPARENT)    ; Text is rendered transparent 
        
        If *lpdis\ItemState & #ODS_FOCUS 
          hBrush = GetSysColorBrush_(#COLOR_HIGHLIGHT)  ; returns a cached brush instead of allocating a new one 
          FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
          
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_HIGHLIGHTTEXT)) 
          DrawFocusRect_(*lpdis\hDC, *lpdis\rcItem)
        Else 
          ; Fill the background color differently for odd and even lines.
          If *lpdis\itemID % 2 And *lpdis\itemState & #ODS_COMBOBOXEDIT = 0
            hBrush = CreateSolidBrush_($DDDDDD)
          Else
            hBrush = GetSysColorBrush_(#COLOR_WINDOW)
          EndIf
          If hBrush
            FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
            DeleteObject_(hBrush)
          EndIf
          
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_WINDOWTEXT)) 
        EndIf 

        If *lpdis\itemID <> -1 
          tlen = SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXTLEN, *lpdis\itemID, 0) ; lParam .. not used 
          If tlen > 0 
            text$ = Space(tlen)
            SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXT, *lpdis\itemID, @text$) 
          EndIf 
          
          dtFlags = #DT_LEFT | #DT_VCENTER | #DT_SINGLELINE
          If Asc(text$) 
            ; column 0
            t$ = StringField(text$, 1, #LF$) 
            If Asc(t$) 
              *lpdis\rcItem\left + 5
              DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
            EndIf 

            ; column 1 
            t$ = StringField(text$, 2, #LF$) 
            If Asc(t$) 
              *lpdis\rcItem\left + DesktopScaledX(#COMBOBOX_COLUMN_0)
              
              If *lpdis\itemState & #ODS_COMBOBOXEDIT = 0
                Protected hPen, oldPen
                
                ; Draw a vertical divider for the column.
                hPen = CreatePen_(#PS_SOLID, 1, $BBBBBB)
                If hPen
                  oldPen = SelectObject_(*lpdis\hdc, hPen)
                  MoveToEx_(*lpdis\hdc, *lpdis\rcItem\left - 5, *lpdis\rcItem\top, 0)
                  LineTo_(*lpdis\hdc, *lpdis\rcItem\left - 5, *lpdis\rcItem\bottom)
                  SelectObject_(*lpdis\hdc, oldPen)
                  DeleteObject_(hPen)
                EndIf
                
                DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
              EndIf
            EndIf 
          EndIf 
        EndIf 
      
      EndIf 
    ; ProcedureReturn result 
  
  EndSelect 
  
  ; default means handling by PB internally 
  ;
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 


Procedure OpenMainWindow(WndW=400, WndH=100) 
  If OpenWindow(#WND_Main, 0, 0, WndW, WndH, "ComboBox OwnerDraw", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget | #PB_Window_TitleBar ) 
    StringGadget(#GDT_strInfo, 8, 8, WndW-16, 20, "Selected Combobox item is .... ")  
    
    ComboBoxGadget(#GDT_cbbValues, 8, 40, 100, 21, #CBS_HASSTRINGS | #CBS_OWNERDRAWFIXED) 
    SendMessage_(GadgetID(#GDT_cbbValues), #CB_SETITEMHEIGHT, 0, DesktopScaledY(GadgetHeight(#GDT_cbbValues) - 2))
    
    SetWindowCallback(@WindowCallback(), #WND_Main) 
    
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False
EndProcedure 

Procedure Main() 
  Protected index, text$  

  If OpenMainWindow() 

    ForEach(CodeDesc())
      index + 1
      ; **
      ; **  Insert delimiter #LF$ to enable ComboBoxGadget value to be extracted without its
      ; **  associated description
      ; **
      AddGadgetItem(#GDT_cbbValues, -1, "STK" + RSet(Str(index), 3, "0") + #LF$ + CodeDesc()) 
    Next  

  Repeat 
    Select WaitWindowEvent() 
      Case #PB_Event_CloseWindow 
        Break ; bye 
      Case #PB_Event_Gadget 
        Select EventGadget() 
          Case #GDT_cbbValues 
            text$ = GetGadgetText(#GDT_cbbValues) 
            SetGadgetText(#GDT_strInfo, "Stock code == " + StringField(text$, 1, #LF$)) 
        EndSelect 
    EndSelect 
  ForEver 

  EndIf 
  ProcedureReturn 0 
EndProcedure 

End Main() 

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 10:36 am
by PBJim
Thanks very much to all for the suggested answers to this post, I appreciate the code that's been put together on this.

I wish the solution was a bit easier. The only thing that I feel might be a downside with this, is that the callbacks are perhaps paired with each ComboBoxGadget, so where we have a window containing a lot of ComboBoxGadgets, the callbacks might become difficult for others to maintain in future. But, I haven't started to put that into use yet, so I may be overthinking.

With your example Rashad, you mention it's for fun, but actually it works well. We already have pop-up search windows for some input fields and I hadn't thought of positioning them so that they appear to be a combobox :D

A question aimed at PB's developers — are there plans to extend the abilities of GUI gadgets? I don't know, as I'm not familiar with the history of the GUI functions or whether they have been the subject of enhancements in the past.

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 11:00 am
by RASHAD
Hi PBJim :D
Next snippet is more simple and more close
Have fun

Code: Select all


LoadFont(0,"Arial",11)

OpenWindow(0,0,0,400,250,"Window",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ComboBoxGadget(1,10,10,180,24) 
dLh = FindWindow_("ComboLBox",0)   
SetGadgetFont(1,FontID(0))
ListIconGadget(2,10,GadgetY(1)+GadgetHeight(1),360,200,"",70,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
SetGadgetColor(2,#PB_Gadget_BackColor,$D5F3FE)
SetGadgetColor(2,#PB_Gadget_LineColor,$0)
AddGadgetColumn(2,1,"",100)
AddGadgetColumn(2,2,"",180)
For i = 0 To 10
  AddGadgetItem(1,i,"Item "+Str(i))
  AddGadgetItem(2,i,"Item "+Str(i)+#LF$+"Text "+Str(i)+#LF$+"another text "+Str(i))
Next
MoveWindow_(dlh,0,0,1,1,1)
HideGadget(2,1)
SetGadgetState(1,8)
Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Quit = 1      
      
    Case #WM_LBUTTONDOWN
      act = GetActiveGadget()
      Select  act
        Case 1
          ;ShowWindow_(dlh,#SW_HIDE	)
          HideGadget(2,0)
          SetActiveGadget(2)
          
        Case 2               
          text$ = GetGadgetItemText(1,GetGadgetState(2),0)
          SetGadgetText(1,text$) 
          HideGadget(2,1)               
          
      EndSelect
      
  EndSelect 
  
Until Quit = 1
End


Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 11:23 am
by RASHAD

Code: Select all


LoadFont(0,"Arial",11)

OpenWindow(0,0,0,400,250,"Window",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ComboBoxGadget(1,10,10,180,24) 
dLh = FindWindow_("ComboLBox",0)   
SetGadgetFont(1,FontID(0))
ListIconGadget(2,10,GadgetY(1)+GadgetHeight(1),360,200,"",70,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
SetGadgetColor(2,#PB_Gadget_BackColor,$D5F3FE)
SetGadgetColor(2,#PB_Gadget_LineColor,$0)
AddGadgetColumn(2,1,"",100)
AddGadgetColumn(2,2,"",180)
For i = 0 To 10
  AddGadgetItem(1,i,"Item "+Str(i))
  AddGadgetItem(2,i,"Item "+Str(i)+#LF$+"Text "+Str(i)+#LF$+"another text "+Str(i))
Next
SendMessage_(GadgetID(1), #CB_SETMINVISIBLE, 1, 0)
HideGadget(2,1)
SetGadgetState(1,8)
Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Quit = 1      
      
    Case #WM_LBUTTONDOWN,#WM_LBUTTONDBLCLK
      act = GetActiveGadget()
      Select  act
        Case 1
          HideGadget(2,0)
          SetActiveGadget(2)
          
        Case 2               
          text$ = GetGadgetItemText(1,GetGadgetState(2),0)
          SetGadgetText(1,text$) 
          HideGadget(2,1)               
          
      EndSelect
      
  EndSelect 
  
Until Quit = 1
End

EDIT : Bug fixed

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 11:30 am
by PBJim
RASHAD wrote: Tue Nov 12, 2024 11:00 am Hi PBJim :D
Next snippet is more simple and more close Have fun
Yes, that's easier, thanks.

It just needs to check or disable double-click on the ComboBoxGadget, as it opens its own drop-down list :

Image

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 11:47 am
by RASHAD
Previous post updated

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 1:38 pm
by ChrisR
Nice solution RASHAD :)
A small addition to close the Combobox by clicking on it again

Code: Select all

LoadFont(0,"Arial",11)

Define IsListIconVisible

OpenWindow(0,0,0,400,250,"Window",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ComboBoxGadget(1,10,10,180,24) 
;dLh = FindWindow_("ComboLBox",0)   
SetGadgetFont(1,FontID(0))
ListIconGadget(2,GadgetX(1),GadgetY(1)+GadgetHeight(1),360,200,"",70,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
SetGadgetColor(2,#PB_Gadget_BackColor,$D5F3FE)
SetGadgetColor(2,#PB_Gadget_LineColor,$0)
AddGadgetColumn(2,1,"",100)
AddGadgetColumn(2,2,"",180)
For i = 0 To 10
  AddGadgetItem(1,i,"Item "+Str(i))
  AddGadgetItem(2,i,"Item "+Str(i)+#LF$+"Text "+Str(i)+#LF$+"another text "+Str(i))
Next
SendMessage_(GadgetID(1), #CB_SETMINVISIBLE, 1, 0)

HideGadget(2,1)
SetGadgetState(1,8)
Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Quit = 1      
      
    Case #WM_LBUTTONDOWN,#WM_LBUTTONDBLCLK
      act = GetActiveGadget()
      Select  act
        Case 1
          If IsListIconVisible
            SetActiveGadget(2)
            HideGadget(2,1)
            IsListIconVisible = #False
          Else
            HideGadget(2,0)
            SetActiveGadget(2)
            IsListIconVisible = #True
          EndIf
          
        Case 2
          text$ = GetGadgetItemText(1,GetGadgetState(2),0)
          SetGadgetText(1,text$) 
          HideGadget(2,1)              
          IsListIconVisible = #False
          
      EndSelect
      
  EndSelect 
  
Until Quit = 1
End


Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 1:47 pm
by PBJim
RASHAD wrote: Tue Nov 12, 2024 11:47 am Previous post updated
Thanks Rashad, it works well.

Incidentally, was it important to use a specific font? I wasn't sure if it was done so that you could be certain other possible default Windows fonts didn't ruin the result.

Code: Select all

; LoadFont(0,"Arial",11)
; SetGadgetFont(1,FontID(0))
I'm not even sure where Windows' default font settings are defined these days, it's been so long since I've looked at them.

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Tue Nov 12, 2024 5:02 pm
by Axolotl
PBJim wrote: Tue Nov 12, 2024 10:36 am ..... I wish the solution was a bit easier. The only thing that I feel might be a downside with this, is that the callbacks are perhaps paired with each ComboBoxGadget, so where we have a window containing a lot of ComboBoxGadgets, the callbacks might become difficult for others to maintain in future. But, I haven't started to put that into use yet, so I may be overthinking. .....
You should not be so scared about the callback solution, because the callback function relates to the parent window.
What you have to do is, you need some configuration. I did that in a small structure and a map.
See the updated example based on breeze4me's update of my first example.
Thanks to breeze4me for his good addition.
BTW: I think this is very good solution, because it works with mouse and keyboard. (IMHO)

Code: Select all

;
; File : ComboBox_AdvancedExample.pb 
;
; ComboBox OwnerDraw in PureBasic 
; 
EnableExplicit 

Enumeration EWindow 1 
  #WND_Main 
EndEnumeration 

Enumeration EGadget 1 
  #GDT_strInfo 
  #GDT_cbbValues 
  #GDT_cbbZipCodes   ; of Florida ... why not!  
EndEnumeration 


#CB_GETCOMBOBOXINFO = 356

Structure COMBOBOXINFO Align #PB_Structure_AlignC
  cbSize.l
  rcItem.RECT
  rcButton.RECT
  stateButton.l
  hwndCombo.i
  hwndItem.i
  hwndList.i
EndStructure

#COMBOBOX_COLUMN_0 =  60  
#COMBOBOX_COLUMN_1 = 120  
#COMBOBOX_COLUMN_2 = 120 


; new user defined structure 
;
Structure TComboBoxData 
  Separator$          ; i.e.  #LF$, |, etc. 
  ListWndWidth.i      ; listbox of the combobox 
  Array Columns.i(0)  ; 
  ; tbc. 
EndStructure 


; --- 

Global NewMap AdvComboBoxData.TComboBoxData()   ; Key is Str(Gadget) 


Global NewList CodeDesc.s()

AddElement(CodeDesc()) : CodeDesc() = "PINEAPPLE"
AddElement(CodeDesc()) : CodeDesc() = "BANANA"
AddElement(CodeDesc()) : CodeDesc() = "DRAGON FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "STAR FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "MANGO"
AddElement(CodeDesc()) : CodeDesc() = "RAMBUTAN"
AddElement(CodeDesc()) : CodeDesc() = "WATERMELON"
AddElement(CodeDesc()) : CodeDesc() = "POMEGRANATE"
AddElement(CodeDesc()) : CodeDesc() = "LYCHEE"
AddElement(CodeDesc()) : CodeDesc() = "MANGOSTEEN"
AddElement(CodeDesc()) : CodeDesc() = "TAMARIND"
AddElement(CodeDesc()) : CodeDesc() = "PASSION FRUIT"
AddElement(CodeDesc()) : CodeDesc() = "PAPAYA"

; --- 
; --- 

Procedure WindowCallback(hWnd, Msg, WParam, LParam) 
  Protected result, hBrush, *lpdis.DRAWITEMSTRUCT
  Protected text$, t$, sep$, tlen, nn, width, dtFlags  
  Protected cbinfo.COMBOBOXINFO, rt.RECT  ; resize the list of the combobox 
  
  Select Msg
    Case #WM_DRAWITEM
      *lpdis = lParam  ; typecast to DRAWITEMSTRUCT 
      If *lpdis\CtlType = #ODT_COMBOBOX 
        
        ; Change the listbox size of the combobox.
        ;
        If IsGadget(WParam) And GadgetType(WParam) = #PB_GadgetType_ComboBox
          cbinfo\cbSize = SizeOf(COMBOBOXINFO)
          SendMessage_(GadgetID(WParam), #CB_GETCOMBOBOXINFO, 0, @cbinfo)
          width = AdvComboBoxData(Str(WParam))\ListWndWidth

          If cbinfo\hwndList
            GetWindowRect_(cbinfo\hwndList, rt)
            MoveWindow_(cbinfo\hwndList, rt\left, rt\top, DesktopScaledX(width), rt\bottom - rt\top, 0)
          EndIf
        EndIf
        
        SetBkMode_(*lpdis\hDC, #TRANSPARENT)    ; Text is rendered transparent 
        
        If *lpdis\ItemState & #ODS_FOCUS 
          hBrush = GetSysColorBrush_(#COLOR_HIGHLIGHT)  ; returns a cached brush instead of allocating a new one 
          FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
          
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_HIGHLIGHTTEXT)) 
;         DrawFocusRect_(*lpdis\hDC, *lpdis\rcItem)   ; <-- It's up to you. (I like it better this way) 
        Else 
          ; Fill the background color differently for odd and even lines.
          ;
          If *lpdis\itemID & 1 ; % 2 ; .. even or odd ?? 
            hBrush = GetSysColorBrush_(#COLOR_WINDOW) ; this is borrowed from system, destroying is not needed. 
            FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
          Else
            hBrush = CreateSolidBrush_($FFF8F0) ; AliceBlue <-- my fav color. 
            FillRect_(*lpdis\hDC, *lpdis\rcItem, hBrush) 
            DeleteObject_(hBrush) ; here we need to destroy the created brush 
          EndIf
          
          SetTextColor_(*lpdis\hDC, GetSysColor_(#COLOR_WINDOWTEXT)) 
        EndIf 

        If *lpdis\itemID <> -1 
          tlen = SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXTLEN, *lpdis\itemID, 0) ; lParam .. not used 
          If tlen > 0 
            text$ = Space(tlen)
            SendMessage_(*lpdis\hwndItem, #CB_GETLBTEXT, *lpdis\itemID, @text$) 
          EndIf 
          
          dtFlags = #DT_LEFT | #DT_VCENTER | #DT_END_ELLIPSIS 
          If Asc(text$) 
            sep$ = AdvComboBoxData(Str(WParam))\Separator$ 

            ; column 0
            ;
            t$ = StringField(text$, 1, sep$) ; #LF$) 
            If Asc(t$) 
              *lpdis\rcItem\left + 5
              DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
            EndIf 

            ; column 1 to N 
            ;
            For nn = 0 To ArraySize(AdvComboBoxData()\Columns()) - 1 
              t$ = StringField(text$, nn + 2, sep$) ; #LF$) 
              If Asc(t$) 
                *lpdis\rcItem\left + DesktopScaledX(AdvComboBoxData()\Columns(nn)) 
              
                If *lpdis\itemState & #ODS_COMBOBOXEDIT = 0 
                  Protected hPen, oldPen

                  ; Draw a vertical divider for the column.
                  ;
                  hPen = CreatePen_(#PS_SOLID, 1, $BBBBBB) 
                  If hPen
                    oldPen = SelectObject_(*lpdis\hdc, hPen)
                    MoveToEx_(*lpdis\hdc, *lpdis\rcItem\left - 5, *lpdis\rcItem\top, 0)
                    LineTo_(*lpdis\hdc, *lpdis\rcItem\left - 5, *lpdis\rcItem\bottom)
                    SelectObject_(*lpdis\hdc, oldPen)
                    DeleteObject_(hPen)
                  EndIf

                  DrawText_(*lpdis\hdc, t$, Len(t$), *lpdis\rcItem, dtFlags) 
                EndIf
              EndIf 
            Next nn 
          EndIf 
        EndIf 

      EndIf 
    ; ProcedureReturn result 
  
  EndSelect 
  
  ; default means handling by PB internally 
  ;
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 


Procedure OpenMainWindow(WndW=400, WndH=100) 
  If OpenWindow(#WND_Main, 0, 0, WndW, WndH, "ComboBox OwnerDraw", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget | #PB_Window_TitleBar ) 
    StringGadget(#GDT_strInfo, 8, 8, WndW-16, 20, "Selected Combobox item is .... ")  
    
    ComboBoxGadget(#GDT_cbbValues, 8, 40, 100, 21, #CBS_HASSTRINGS | #CBS_OWNERDRAWFIXED) 
    SendMessage_(GadgetID(#GDT_cbbValues), #CB_SETITEMHEIGHT, 0, DesktopScaledY(GadgetHeight(#GDT_cbbValues) - 2))
    AdvComboBoxData(Str(#GDT_cbbValues))\Separator$ = #LF$ 
    AdvComboBoxData()\ListWndWidth = 200  ; not scaled pixels 
    Dim AdvComboBoxData()\Columns(1) 
    AdvComboBoxData()\Columns(0) = #COMBOBOX_COLUMN_0 

    ; NEW ComboBox useing the same callback .....
    ;
    ComboBoxGadget(#GDT_cbbZipCodes, 8, 64, 100, 21, #CBS_HASSTRINGS | #CBS_OWNERDRAWFIXED) 
    SendMessage_(GadgetID(#GDT_cbbZipCodes), #CB_SETITEMHEIGHT, 0, DesktopScaledY(GadgetHeight(#GDT_cbbZipCodes) - 2))  ; ?? breezeme stuff! 
    AdvComboBoxData(Str(#GDT_cbbZipCodes))\Separator$ = #LF$ ;"|" 
    AdvComboBoxData()\ListWndWidth = 400  ; not scaled pixels 
    Dim AdvComboBoxData()\Columns(3) 
    AdvComboBoxData()\Columns(0) = #COMBOBOX_COLUMN_0 
    AdvComboBoxData()\Columns(1) = #COMBOBOX_COLUMN_1 
    AdvComboBoxData()\Columns(2) = #COMBOBOX_COLUMN_2 
    
    SetWindowCallback(@WindowCallback(), #WND_Main) 
    
    ProcedureReturn #True 
  EndIf 
  ProcedureReturn #False
EndProcedure 

Procedure Main() 
  Protected index, text$  

  If OpenMainWindow() 

    ForEach(CodeDesc())
      index + 1
      ; **
      ; **  Insert delimiter #LF$ to enable ComboBoxGadget value to be extracted without its
      ; **  associated description
      ; **
      AddGadgetItem(#GDT_cbbValues, -1, "STK" + RSet(Str(index), 3, "0") + #LF$ + CodeDesc()) 
    Next  

    ; Copied from https://www.zip-codes.com/state/fl.asp 
    ; ZIP Code LF City LF County LF Type 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32003" + #LF$ + "Fleming Island" + #LF$ + "Clay" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32004" + #LF$ + "Ponte Vedra Beach" + #LF$ + "Saint Johns" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32006" + #LF$ + "Fleming Island" + #LF$ + "Clay" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32007" + #LF$ + "Bostwick" + #LF$ + "Putnam" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32008" + #LF$ + "Branford" + #LF$ + "Suwannee" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32009" + #LF$ + "Bryceville" + #LF$ + "Nassau" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32011" + #LF$ + "Callahan" + #LF$ + "Nassau" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32013" + #LF$ + "Day" + #LF$ + "Lafayette" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32024" + #LF$ + "Lake City" + #LF$ + "Columbia" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32025" + #LF$ + "Lake City" + #LF$ + "Columbia" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32030" + #LF$ + "Doctors Inlet" + #LF$ + "Clay" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32033" + #LF$ + "Elkton" + #LF$ + "Saint Johns" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32034" + #LF$ + "Fernandina Beach" + #LF$ + "Nassau" + #LF$ + "Standard") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32035" + #LF$ + "Fernandina Beach" + #LF$ + "Nassau" + #LF$ + "P.O. Box") 
    AddGadgetItem(#GDT_cbbZipCodes, -1, "32038" + #LF$ + "Fort White" + #LF$ + "Columbia" + #LF$ + "Standard") 


    Repeat 
      Select WaitWindowEvent() 
        Case #PB_Event_CloseWindow 
          Break ; bye 
        Case #PB_Event_Gadget 
          Select EventGadget() 
            Case #GDT_cbbValues 
              text$ = GetGadgetText(#GDT_cbbValues) 
              SetGadgetText(#GDT_strInfo, "Stock code == " + StringField(text$, 1, #LF$)) 

            Case #GDT_cbbZipCodes 
              text$ = GetGadgetText(#GDT_cbbZipCodes) 
              Debug text$ 
            
          EndSelect 
      EndSelect 
    ForEver 
  EndIf 
  ProcedureReturn 0 
EndProcedure 

End Main() 

Re: Multi-column ComboBoxGadget and use of separating delimiter

Posted: Wed Nov 13, 2024 9:53 am
by PBJim
ChrisR wrote: Tue Nov 12, 2024 1:38 pm Nice solution RASHAD :)
A small addition to close the Combobox by clicking on it again
Thanks for that ChirisR, I'd noticed that also and put a toggle into it.

The difficulty with these workarounds, once we diverge from PB's built-in gadgets, is that there are often several unintended consequences, such as this one below :

Image

I realised that if the special combobox overlayed other fields, I could tab to them anyway. So we have to make sure to close the special combobox in several cases, tab, lost focus etc.

The thing is, for the solution we're working on, we could just resolve the requirement with SetGadgetItemData() and store the length of the key field in there, because it allows us to later extract the key — of otherwise unknown length and format — from the complete ComboBoxGadget line. Anyway, it's been interesting to see other options.