A general purpose ListRequester() routine

Share your advanced PureBasic knowledge/code with the community.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

A general purpose ListRequester() routine

Post by kenmo »

Once again I was tired of rewriting basically the same code in many different projects, so I wrote this customizable, reusable code called ListRequester. It presents a set of options in either a ListIconGadget, ListViewGadget, or TreeGadget.

It operates similar to other PureBasic requesters. The main difference is that it accepts a linked list of options (text plus an optional image ID, tree level, or user-defined value) which must be pre-filled before calling.

I wrote up a series of simple examples, so I will let the code speak for itself.

Feedback and suggestions welcome.

v1.1 EDIT: Fixed ParentWin/ParentID with #PB_Window_WindowCentered, added #ListReq_Sticky, added example color constants

Code: Select all

; +---------------+
; | ListRequester |
; +---------------+
; |  9.08.2012 . Creation (TreeGadget)
; |   .10.     . Added MultiSelect and AllowNoSel flags
; |   .11.     . Added ListIcon, ListView, and EdgeButtons flags
; |   .12.     . Added Tree levels, Select All shortcut, split Value/ImageID
; |   .13.     + Version 1.0 (documented code, added more examples)
; |   .14.     + Version 1.1 (fixed ParentWin and WindowCenter, added Sticky flag)

;-
;
; Version 1.1 (Sept 2012)
; 
; This is a general purpose requester which presents a list of options to the user.
;   Each option includes a text string, plus an optional Image ID and user-defined integer.
;   The user may be required to choose EXACTLY one, AT LEAST one, or ANY NUMBER of options.
;   The Cancel action is always available, by design. You may modify this as desired.
;   The options may be presented in a ListIconGadget, ListViewGadget, or TreeGadget (each acts slightly differently).
;   The window size and text is fully customizable, otherwise it will try to reasonably size itself.
;   There are additional procedures to add, clear, and sort the options.
;
; This code may be used and modified however you want. Credit is appreciated.
;   Don't forget to disable or delete the Demo Program at the end!
; 
;
; Result.i = ListRequester( Title.s, Message.s, List Options.ListReq(), [ Flags.i, [ Selected.i, [ ParentWin.i, [ Width, Height ]]]] )
;
;   Title     : Brief text which appears in the requester's title bar (and ListIcon header if applicable)
;   Message   : Text prompt which appears at the top of the requester window
;   Options   : Linked list of options using the ListReq structure (see AddListReqOption for details)
;   Flags     : Bitwise-OR combination of the following constants:
;                 #ListReq_ListIcon    : Use a ListIconGadget for the Options (default, the column header will be hidden if possible)
;                 #ListReq_ListView    : Use a ListViewGadget for the Options (ImageID will be ignored)
;                 #ListReq_Tree        : Use a TreeGadget for the Options (tree levels may be specified by Value)
;                 #ListReq_ReturnVal   : The returned integer will be the Value associated with the selected Option (not its index)
;                 #ListReq_MultiSelect : Allows multiple Options to be selected, returns the total selected count
;                 #ListReq_AllowNoSel  : Used with MultiSelect, allows the user to accept without any Options selected
;                 #ListReq_TreeLevels  : Implies that the Value associated with each Option should be used as its "level" or depth
;                 #ListReq_EdgeButtons : Locates the OK and Cancel buttons at the side rather than the center
;                 #ListReq_Sticky      : Makes the requester "sticky" or always-on-top of other windows
;   Selected  : Specifies the initially selected option (default is -1, or no selection)
;   ParentWin : An optional PureBasic window (not its WindowID) which will be locked while the requester is open
;   Width     : Desired requester width (by default, auto-sizes to accomodate Title)
;   Height    : Desired requester height (by default, auto-sizes to accomodate Options)
;
;
;   Result    : The returned value means one of three things, depending on Flags:
;                 By default, this returns the index of the chosen option (0 or greater)
;                   The chosen option will also become the active list element, for immediate processing
;                 With #ListReq_ReturnVal, this returns the Value associated with the chosen Option (user-defined meaing)
;                 With #ListReq_MultiSelect, this returns the total number of options selected (0 or more)
;                   Note: each Option's Value is overwritten with #True or #False to indicate if it was selected or not
;                 #ListReq_Cancel is returned if the user cancels out
;                 #ListReq_Error is returned if the window cannot be created
;



;- Public Constants

; Requester Flags
#ListReq_ReturnVal    = $01
#ListReq_MultiSelect  = $04
#ListReq_AllowNoSel   = $08
#ListReq_TreeLevels   = $10
#ListReq_EdgeButtons  = $20
#ListReq_Sticky       = $40

; Gadget Flags
#ListReq_ListIcon     = $0000
#ListReq_ListView     = $0100
#ListReq_Tree         = $0200

; Sort Flags
#ListReq_Alphabetical = $00
#ListReq_ByValue      = $01
#ListReq_Ascending    = $00
#ListReq_Descending   = $02

; Return Codes
#ListReq_Empty  = -1
#ListReq_Cancel = -2
#ListReq_Error  = -3

;- Private Constants
#xListReq_Enter  = $01
#xListReq_Escape = $02
#xListReq_CtrlA  = $04

;- OS Constants
CompilerIf (#PB_Compiler_OS = #PB_OS_Windows)
  #LVS_NOCOLUMNHEADER = 16384
  #xListReq_Ctrl = #PB_Shortcut_Control
CompilerElse
  #LVS_NOCOLUMNHEADER = #Null
  CompilerIf (#PB_Compiler_OS = #PB_OS_MacOS)
    #xListReq_Ctrl = #PB_Shortcut_Command
  CompilerElse
    #xListReq_Ctrl = #PB_Shortcut_Control
  CompilerEndIf
CompilerEndIf




;-
;- Public Structures

; Define a single ListRequester option
Structure ListReq
  Text.s
  Value.i
  ImageID.i
  Invalid.i
EndStructure





;-
;- Public Procedures

; The main ListRequester function, see detailed description above
Procedure.i ListRequester(Title.s, Message.s, List Options.ListReq(), Flags.i = #Null, Selected.i = -1, ParentWin.i = #Null, Width.i = 0, Height.i = 0)
  Protected Result.i = #ListReq_Cancel
  Protected Window.i, ParentID.i
  Protected MessageGadget.i, ListGadget.i, OKGadget.i, CancelGadget.i
  Protected Event.i, ExitFlag.i, TryOption.i
  Protected Count.i, i.i, j.i, SelFlag.i
  
  ; Only process a non-empty list
  Count = ListSize(Options())
  If (Count > 0)
  
    ; Auto-fit window if dimensions aren't specified
    If (Width <= 0)
      Width = Len(Message) * 3 + 190
    EndIf
    If (Height <= 0)
      Height = Log(ListSize(Options())) * 60 + 100
    EndIf
    
    ; Find parent window handle, if applicable
    If (IsWindow(ParentWin))
      DisableWindow(ParentWin, #True)
      ParentID =  WindowID(ParentWin)
      i        = #PB_Window_WindowCentered
    Else
      ParentID = #Null
      i        = #PB_Window_ScreenCentered
    EndIf
    
    ; Open requester window
    Window = OpenWindow(#PB_Any, 0, 0, Width, Height, Title, i|#PB_Window_Invisible|#PB_Window_SystemMenu, ParentID)
    If (Window)
      If (Flags & #ListReq_Sticky)
        StickyWindow(Window, #True)
      EndIf
      
      ; Create text prompt and OK/Cancel buttons
      MessageGadget = TextGadget(#PB_Any, 0, 5, Width, 20, Message, #PB_Text_Center)
      If (Flags & #ListReq_EdgeButtons)
        i = 5
        j = Width   - 85
      Else
        i = Width/2 - 90
        j = Width/2 + 10
      EndIf
      OKGadget      = ButtonGadget(#PB_Any, i, Height - 35, 80, 25,     "OK", #PB_Button_Default)
      CancelGadget  = ButtonGadget(#PB_Any, j, Height - 35, 80, 25, "Cancel", #Null)
      
      ; Build window with a TreeGadget
      If (Flags & #ListReq_Tree)
        Flags & (~(#ListReq_ListView | #ListReq_ListIcon))
        SelFlag = #PB_Tree_Checked
        i = #PB_Tree_AlwaysShowSelection
        If (Not (Flags & #ListReq_TreeLevels))
          i | #PB_Tree_NoLines | #PB_Tree_NoButtons 
        EndIf
        If (Flags & #ListReq_MultiSelect)
          i | #PB_Tree_CheckBoxes
        EndIf
        ListGadget = TreeGadget(#PB_Any, 5, 25, Width - 10, Height - 70, i)
      
      ; Build window with a ListViewGadget (no images allowed)
      ElseIf (Flags & #ListReq_ListView)
        Flags & (~#ListReq_ListIcon)
        SelFlag = #True
        If (Flags & #ListReq_MultiSelect)
          i = #PB_ListView_ClickSelect
        Else
          i = #Null
        EndIf
        ListGadget = ListViewGadget(#PB_Any, 5, 25, Width - 10, Height - 70, i)
        
      ; Default, build window with a ListIconGadget
      Else
        SelFlag = #PB_ListIcon_Selected
        i = #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_FullRowSelect | #LVS_NOCOLUMNHEADER
        If (Flags & #ListReq_MultiSelect)
          i | #PB_ListIcon_MultiSelect
        EndIf
        ListGadget = ListIconGadget(#PB_Any, 5, 25, Width - 10, Height - 70, Title, Width - 20, i)
      EndIf
      
      ; Only the TreeGadget allows option levels
      If (Not (Flags & #ListReq_Tree))
        Flags & (~#ListReq_TreeLevels)
      EndIf
      
      ; Only MultiSelect allows "no selection" flag
      If (Not (Flags & #ListReq_MultiSelect))
        Flags & (~#ListReq_AllowNoSel)
      EndIf
      
      
      
      ; Add each option to the list
      ForEach (Options())
        ; 
        If (Flags & #ListReq_TreeLevels)
          j = Options()\Value
        Else
          j = #Null
        EndIf
        AddGadgetItem(ListGadget, ListIndex(Options()), Options()\Text, Options()\ImageID, j)
        
        ; Set pre-selected options
        If (Flags & #ListReq_MultiSelect)
          If (Options()\Value)
            SetGadgetItemState(ListGadget, ListIndex(Options()), SelFlag)
          Else
            SetGadgetItemState(ListGadget, ListIndex(Options()), #Null)
          EndIf
        EndIf
      Next
      
      ; Add standard keyboard shortcuts
      AddKeyboardShortcut(Window, #PB_Shortcut_Return, #xListReq_Enter )
      AddKeyboardShortcut(Window, #PB_Shortcut_Escape, #xListReq_Escape)
      AddKeyboardShortcut(Window, #PB_Shortcut_A|#xListReq_Ctrl, #xListReq_CtrlA)
      
      ; Remove ListIcon header, when possible
      CompilerIf (#PB_Compiler_OS = #PB_OS_Windows)
        If (Flags & #ListReq_ListIcon)
          SendMessage_(GadgetID(ListGadget), #LVM_SETCOLUMNWIDTH, 0, #LVSCW_AUTOSIZE | #LVSCW_AUTOSIZE_USEHEADER)
        EndIf
      CompilerEndIf
      
      ; Finalize and show window
      If (Not (Flags & #ListReq_MultiSelect))
        SetGadgetState(ListGadget, Selected)
      EndIf
      HideWindow(Window, #False)
      SetActiveWindow(Window)
      SetActiveGadget(ListGadget)
      
      ; Event loop
      Repeat
        Event = WaitWindowEvent()
        
        ; Handle gadget events
        If (Event = #PB_Event_Gadget)
          Select (EventGadget())
            Case OKGadget
              TryOption = #True
            Case CancelGadget
              Result   = #ListReq_Cancel
              ExitFlag = #True
            Case ListGadget
              If (EventType() = #PB_EventType_LeftDoubleClick)
                TryOption = #True
              EndIf
          EndSelect
        
        ; Handle menu events (shortcuts)
        ElseIf (Event = #PB_Event_Menu)
          Select (EventMenu())
            Case #xListReq_Enter
              TryOption = #True
            Case #xListReq_Escape
              Result   = #ListReq_Cancel
              ExitFlag = #True
            Case #xListReq_CtrlA
              If (Flags & #ListReq_MultiSelect)
                For i = Count - 1 To 0 Step -1
                  SetGadgetItemState(ListGadget, i, SelFlag)
                Next i
              EndIf
          EndSelect
        
        ; Handle other window events
        ElseIf (Event = #PB_Event_CloseWindow)
          Result   = #ListReq_Cancel
          ExitFlag = #True
        EndIf
        
        ; Verify user's selection
        If (TryOption)
          ExitFlag = #True
          
          ; Verify a multi-selection
          If (Flags & #ListReq_MultiSelect)
            Result = 0
            For i = 0 To Count - 1
              SelectElement(Options(), i)
              If ((Not Options()\Invalid) And (GetGadgetItemState(ListGadget, i) & SelFlag))
                Options()\Value = #True
                Result + 1
              Else
                Options()\Value = #False
              EndIf
            Next i
            If ((Result = 0) And (Not (Flags & #ListReq_AllowNoSel)))
              ExitFlag = #False
            EndIf
          
          ; Verify a single selection
          Else
            Selected = GetGadgetState(ListGadget)
            If (Selected >= 0)
              SelectElement(Options(), Selected)
              If (Not Options()\Invalid)
                Result = Selected
              Else
                ExitFlag = #False
              EndIf
            Else
              ExitFlag = #False
            EndIf
          EndIf
          TryOption = #False
        EndIf
        
      Until (ExitFlag)
      CloseWindow(Window)
      
      ; Select chosen list element, return its Value if desired
      If ((Result >= 0) And (Not (Flags & #ListReq_MultiSelect)))
        SelectElement(Options(), Result)
        If (Flags & #ListReq_ReturnVal)
          Result = Options()\Value
        EndIf
      EndIf
      
    Else
      Result = #ListReq_Error
    EndIf
    
    ; Free up parent window
    If (IsWindow(ParentWin))
      DisableWindow(ParentWin, #False)
    EndIf
  EndIf
  
  ProcedureReturn (Result)
EndProcedure















;
; *Result.ListReq = AddListReqOption(List Options.ListReq(), Text.s, [ Value.i, [ ImageID.i, [ Invalid.i ]]] )
; 
;   Text    : The actual Option text the user will see
;   Value   : With #ListReq_MultiSelect, set this #True to pre-select (will be set as #True if chosen by user)
;             With #ListReq_ReturnVal, this is the numeric Value that will be returned if the user selects this
;   ImageID : Valid image ID for this Option's icon (not valid with ListView)
;   Invalid : This option is visible but cannot be chosen (#True or #False)
;
Procedure.i AddListReqOption(List Options.ListReq(), Text.s, Value.i = #Null, ImageID.i = #Null, Invalid.i = #False)
  Protected *Result.ListReq = #Null
  
  *Result = AddElement(Options())
  *Result\Text    = Text
  *Result\Value   = Value
  *Result\ImageID = ImageID
  *Result\Invalid = Invalid
  
  ProcedureReturn (*Result)
EndProcedure



; Clear the provided ListReq options
Procedure ClearListReqOptions(List Options.ListReq())
  ClearList(Options())
EndProcedure



; Sorts the provided ListReq options, alphabetically or by value, ascending or descending
;
;   Flags : Bitwise-OR combination of:
;             #ListReq_Alphabetical
;             #ListReq_ByValue
;             #ListReq_Ascending
;             #ListReq_Descending
;
Procedure SortListReqOptions(List Options.ListReq(), Flags.i = #Null)
  Protected Order.i
  
  If (ListSize(Options()) > 0)
    If (Flags & #ListReq_Descending)
      Order = #PB_Sort_Descending
    Else
      Order = #PB_Sort_Ascending
    EndIf
    If (Flags & #ListReq_ByValue)
      SortStructuredList(Options(), Order, OffsetOf(ListReq\Value), #PB_Sort_Integer)
    Else
      SortStructuredList(Options(), Order | #PB_Sort_NoCase, OffsetOf(ListReq\Text), #PB_Sort_String)
    EndIf
  EndIf
EndProcedure











;-
;- Demo Program

; Change this to (0) or delete this section for inclusion in other code!
CompilerIf (1)

; Color Constants
#White = $FFFFFF
#Red   = $0000FF
#Green = $00FF00
#Blue  = $FF0000


; Simple procedure to create colored square images
Procedure.i xListReq_ColorIcon(Color.i)
  Protected img.i = CreateImage(#PB_Any, 16, 16)
  
  If (img)
    If (StartDrawing(ImageOutput(img)))
      Box(0, 0, ImageWidth(img),   ImageHeight(img),   #White)
      Box(1, 1, ImageWidth(img)-2, ImageHeight(img)-2,  Color)
      StopDrawing()
    EndIf
    img = ImageID(img)
  EndIf
  
  ProcedureReturn (img)
EndProcedure





;- Example 1
NewList LRO.ListReq()
  AddListReqOption(LRO(), "Choose this...")
  AddListReqOption(LRO(), "this...")
  AddListReqOption(LRO(), " ...", 0, 0, #True)
  AddListReqOption(LRO(), "or this.")

If (ListRequester("Example 1", "Default requester (ListIcon, choose exactly one)", LRO()) >= 0)
  MessageRequester("Example 1", "You chose: " + LRO()\Text)
EndIf


;- Example 2
ClearListReqOptions(LRO())
  AddListReqOption(LRO(), "Option A")
  AddListReqOption(LRO(), "Option B")
  AddListReqOption(LRO(), "Option C")

If (ListRequester("Example 2", "MultiSelect (choose AT LEAST one) and Sticky", LRO(), #ListReq_MultiSelect | #ListReq_Sticky)) > 0
  ForEach (LRO())
    If (LRO()\Value)
      MessageRequester("Example 2", "Selected: " + LRO()\Text)
    EndIf
  Next
EndIf

;- Example 3
NewList LRO.ListReq()
  AddListReqOption(LRO(), "LV-1")
  AddListReqOption(LRO(), "LV-2")
  AddListReqOption(LRO(), "LV-3")
  AddListReqOption(LRO(), "LV-4")
  AddListReqOption(LRO(), "LV-5")
  AddListReqOption(LRO(), "LV-6")

If (ListRequester("Example 3", "ListView, with 'edge' button placement", LRO(), #ListReq_ListView | #ListReq_EdgeButtons) >= 0)
  MessageRequester("Example 3", "You chose: " + LRO()\Text)
EndIf

;- Example 4
NewList LRO.ListReq()
  AddListReqOption(LRO(), "these")
  AddListReqOption(LRO(), "words")
  AddListReqOption(LRO(), "don't")
  AddListReqOption(LRO(), "really")
  AddListReqOption(LRO(), "matter")

SortListReqOptions(LRO(), #ListReq_Alphabetical)

Define Count.i =ListRequester("Example 4", "MultiSelect ListView, sorted, zero-selection allowed", LRO(), #ListReq_ListView | #ListReq_MultiSelect | #ListReq_AllowNoSel)
If (Count >= 0)
  MessageRequester("Example 4", "You chose " + Str(Count) + " of " + Str(ListSize(LRO())) + " options")
EndIf

;- Example 5
NewList LRO.ListReq()
  AddListReqOption(LRO(), "Audio", 0, 0, #True)
  AddListReqOption(LRO(), ".wav", 1)
  AddListReqOption(LRO(), ".mp3", 1)
  AddListReqOption(LRO(), "Video", 0, 0, #True)
  AddListReqOption(LRO(), ".avi", 1)
  AddListReqOption(LRO(), ".mpg", 1)
  AddListReqOption(LRO(), "Text", 0, 0, #True)
  AddListReqOption(LRO(), ".txt", 1)
  AddListReqOption(LRO(), ".doc", 1)

If (ListRequester("Example 5", "TreeGadget with variable levels, custom size", LRO(), #ListReq_Tree | #ListReq_TreeLevels, -1, 0, 360, 240) >= 0)
  MessageRequester("Example 5", "File type: " + LRO()\Text)
EndIf

;- Example 6
NewList LRO.ListReq()
  AddListReqOption(LRO(), "RED", #Red, xListReq_ColorIcon(#Red))
  AddListReqOption(LRO(), "GREEN", #Green, xListReq_ColorIcon(#Green))
  AddListReqOption(LRO(), "BLUE", #Blue, xListReq_ColorIcon(#Blue))

Define Count.i =ListRequester("Example 6", "Tree with images, returns value (not option index)", LRO(), #ListReq_Tree | #ListReq_ReturnVal)
If (Count >= 0)
  MessageRequester("Example 6", "Color " + Str(ListIndex(LRO())+1) + ": $" + RSet(Hex(Count), 6, "0"))
EndIf

;- Example 7
NewList LRO.ListReq()
  AddListReqOption(LRO(), "Yes 1", #True)
  AddListReqOption(LRO(), "No 2", #False)
  AddListReqOption(LRO(), "Yes 3", #True)
  AddListReqOption(LRO(), "Yes 4", #True)
  AddListReqOption(LRO(), "No 5", #False)

ListRequester("Example 7", "Tree with pre-selected options", LRO(), #ListReq_Tree | #ListReq_MultiSelect)

End
CompilerEndIf

;-
;  EOF
Last edited by kenmo on Fri Apr 17, 2015 4:43 pm, edited 3 times in total.
User avatar
STARGÅTE
Addict
Addict
Posts: 2235
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: A general purpose ListRequester() routine

Post by STARGÅTE »

useful for quick select options.

Feedback:

I see that you use the active window as a ParentID, then use #PB_Window_WindowCentered instead of #PB_Window_ScreenCentered.
You should also disable the parent window for inputs (like the PB-Requester).
It is not good, to make the requester sticky! a child window is always sticky in your application.
I don't want the requester overall my windows.

If you don't want to disable other windows in the current application, you have to check, if WindowEvent() is in your requester window

Code: Select all

If EventWindow() = Window
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: A general purpose ListRequester() routine

Post by BasicallyPure »

Hi,
I'm using PureBasic 4.61 (Linux version).
Your code needs this so it will run under Linux.

Code: Select all

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
   #White = $FFFFFF
   #Red   = $0000FF
   #Green = $00FF00
   #Blue  = $FF0000
CompilerEndIf
Example #5 will not continue until I choose 'Cancel'.
Or maybe I don't understand the purpose of example #5.

Thanks for posting your code.

B.P.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: A general purpose ListRequester() routine

Post by kenmo »

STARGÅTE wrote:I see that you use the active window as a ParentID, then use #PB_Window_WindowCentered instead of #PB_Window_ScreenCentered.
Good point, will change.
You should also disable the parent window for inputs (like the PB-Requester).
:shock: Oops! It used to do this. I must have deleted some code while reorganizing. Thanks for catching that so quickly.
It is not good, to make the requester sticky! a child window is always sticky in your application.
In some cases I prefer it. You can comment it out temporarily, but I will add a flag for this too.
If you don't want to disable other windows in the current application, you have to check, if WindowEvent() is in your requester window
Yes, will change... I was assuming that the requester should be the only non-disabled window, but that is not guaranteed.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: A general purpose ListRequester() routine

Post by kenmo »

Hello BP,
BasicallyPure wrote:Your code needs this so it will run under Linux.
I forget sometimes that the color constants aren't cross-platform... :?:
Example #5 will not continue until I choose 'Cancel'.
Or maybe I don't understand the purpose of example #5.
It should show a Tree with three categories (Audio / Video / Text) which are NOT selectable, but their sub-items are... I've only tested on Windows.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: A general purpose ListRequester() routine

Post by BasicallyPure »

kenmo wrote:It should show a Tree with three categories (Audio / Video / Text) which are NOT selectable, but their sub-items are... I've only tested on Windows.
Example #5 is working fine for me now that I understand how it should behave.

thanks,
B.P.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
jamirokwai
Enthusiast
Enthusiast
Posts: 798
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: A general purpose ListRequester() routine

Post by jamirokwai »

Very useful, thanks!
Regards,
JamiroKwai
Post Reply