I made one for my own gui editor, its a bit ugly but it does the job. here you go =)
Note!.. the key handling doesnt work properly :/
proplist.bmx
SuperStrict
Const PROP_TEXT:Int = 0 ' text input
Const PROP_NUM:Int = 1 ' number input
Const PROP_LIST:Int = 2 ' list
Const PROP_ELIST:Int = 3 ' editable list
Const PROP_ILIST:Int = 4 ' icon list
Type TPropertyField
Field Class:Int
Field Caption:String
Field Text:String
Field Index:Int
Field Items:String[]
Field Icons:Int[]
Field IconStrip:TIconStrip
Field Owner:TPropertyList
Method StartEdit()
'Local xx:Int = (GadgetWidth(Owner.gadget) / 2) - 2
Local xx:Int = (TPropertyList.PROPERTY_WIDTH * (FontSize(Owner.font) - 1)) - FontSize(Owner.font)
SetGadgetShape( Owner.controls[Class], xx - 3, Index * TPropertyList.PROPERTY_HEIGHT, xx - 1,TPropertyList.PROPERTYFIELD_HEIGHT)
Select Class
Case PROP_LIST, PROP_ELIST
ClearGadgetItems Owner.controls[Class]
For Local i:Int = 0 Until Items.Length
AddGadgetItem( Owner.controls[Class], Items[i])
If Text = Items[i] Then SelectGadgetItem Owner.controls[Class], i
Next
If SelectedGadgetItem( Owner.controls[Class]) < 0 Then
SelectGadgetItem Owner.controls[Class], 0
EndIf
Case PROP_ILIST
ClearGadgetItems Owner.controls[Class]
SetGadgetIconStrip Owner.controls[Class], IconStrip
For Local i:Int = 0 Until Items.Length
AddGadgetItem( Owner.controls[Class], Items[i], 0, Icons[i])
If Text = Items[i] Then SelectGadgetItem Owner.controls[Class], i
Next
If SelectedGadgetItem( Owner.controls[Class]) < 0 Then
SelectGadgetItem Owner.controls[Class], 0
EndIf
Case PROP_NUM
If Text.Length <= 0 Then Text = "0"
EndSelect
Owner.controls[Class].SetText( Text)
ShowGadget Owner.controls[Class]
Select Class
Case PROP_TEXT, PROP_NUM
SelectTextAreaText( Owner.controls[Class])
EndSelect
ActivateGadget Owner.controls[Class]
EndMethod
Method EndEdit( store:Int = True)
Local t:String
Select Class
Case PROP_TEXT, PROP_NUM
t = TextAreaText( Owner.controls[Class])
Case PROP_LIST, PROP_ELIST, PROP_ILIST
t = Owner.controls[Class].GetText()
EndSelect
If (t <> Text) And store Then
Text = t
Owner.UpdateGadget()
EndIf
DeselectGadgetItem( Owner.Gadget, Index)
HideGadget Owner.controls[Class]
EndMethod
EndType
Type TPropertyList
Const PROPERTY_HEIGHT:Int = 14
Const PROPERTY_WIDTH:Int = 20
Const PROPERTYFIELD_HEIGHT:Int = 17
Field props:TList = New TList
Field Gadget:TGadget
Field controls:TGadget[5]
Field font:TGUIFont
Field lastprop:TPropertyField
Function Create:TPropertyList( x:Int,y:Int,w:Int,h:Int, group:TGadget)
Local list:TPropertyList = New TPropertyList
list.Init( x,y, w,h, group)
Return list
EndFunction
Function FilterText:Int( event:TEvent, context:Object)
If event.ID = EVENT_KEYDOWN Then
Local props:TPropertyList = TPropertyList( context)
If Not props Then Return True
Select event.Data
Case KEY_ENTER
' complete edit
props.EndEdit()
Return False
Case KEY_ESCAPE
' cancel edit
props.CancelEdit()
Return False
Case KEY_UP
' complete edit and goto previous property
If props.lastprop Then
Local index:Int = props.lastprop.Index - 1
If index >= 0 Then
props.lastprop.EndEdit()
ActivateGadget( props.Gadget)
SelectGadgetItem( props.Gadget, index)
'props.lastprop = TPropertyField( props.Props.ValueAtIndex( index))
'If props.lastprop Then props.lastprop.StartEdit()
EndIf
EndIf
Case KEY_DOWN
' complete edit and goto next property
If props.lastprop Then
Local index:Int = props.lastprop.Index + 1
If index < props.lastprop.Owner.Props.Count() Then
props.lastprop.EndEdit()
ActivateGadget( props.Gadget)
SelectGadgetItem( props.Gadget, index)
'props.lastprop = TPropertyField( props.Props.ValueAtIndex( index))
'If props.lastprop Then props.lastprop.StartEdit()
EndIf
EndIf
EndSelect
EndIf
Return True
EndFunction
Function FilterNum:Int( event:TEvent, context:Object)
If FilterText( event, context) Then
Return (event.Data >= Asc("0")) And (event.Data <= Asc("9"))
EndIf
Return False
EndFunction
Method CancelEdit()
If lastprop Then
lastprop.EndEdit( False)
lastprop = Null
EndIf
EndMethod
Method EndEdit()
If lastprop Then
lastprop.EndEdit( True)
lastprop = Null
EndIf
EndMethod
Method Init( x:Int,y:Int, w:Int,h:Int, group:TGadget)
Gadget = CreateListBox( x,y, w,h, group)
font = LoadGuiFont( "Lucida Console", 10)
SetGadgetFont( Gadget, font)
controls[PROP_TEXT] = CreateTextArea( 0,-64, 32,PROPERTYFIELD_HEIGHT, Gadget)
SetGadgetFilter controls[PROP_TEXT], FilterText, Self
controls[PROP_NUM] = CreateTextArea( 0,-64, 32,PROPERTYFIELD_HEIGHT, Gadget)
SetGadgetFilter controls[PROP_NUM], FilterNum, Self
controls[PROP_LIST] = CreateComboBox( 0,-64, 32,PROPERTYFIELD_HEIGHT, Gadget)
controls[PROP_ELIST] = CreateComboBox( 0,-64, 32,PROPERTYFIELD_HEIGHT, Gadget, COMBOBOX_EDITABLE)
controls[PROP_ILIST] = CreateComboBox( 0,-64, 32,PROPERTYFIELD_HEIGHT, Gadget)
SetGadgetBorder( controls[PROP_TEXT], BORDER_THIN)
SetGadgetBorder( controls[PROP_NUM], BORDER_THIN)
HideGadget controls[PROP_TEXT]
HideGadget controls[PROP_NUM]
HideGadget controls[PROP_LIST]
HideGadget controls[PROP_ELIST]
HideGadget controls[PROP_ILIST]
EndMethod
Method AddTextProperty:TPropertyField( caption:String, text:String = Null)
Local prop:TPropertyField = New TPropertyField
prop.Class = PROP_TEXT
prop.Caption = caption
prop.Text = text
prop.Owner = Self
prop.Index = props.Count()
props.AddLast( prop)
Return prop
EndMethod
Method AddNumberProperty:TPropertyField( caption:String, text:String = "0")
Local prop:TPropertyField = New TPropertyField
prop.Class = PROP_NUM
prop.Caption = caption
prop.Text = text
prop.Owner = Self
prop.Index = props.Count()
props.AddLast( prop)
Return prop
EndMethod
Method AddListProperty:TPropertyField( caption:String, items:String[], index:Int = 0)
Local prop:TPropertyField = New TPropertyField
prop.Class = PROP_LIST
prop.Caption = caption
prop.Items = items
If index < 0 Then
prop.Text = items[0]
ElseIf index >= items.Length Then
prop.Text = items[items.Length-1]
Else
prop.Text = items[index]
EndIf
prop.Index = props.Count()
prop.Owner = Self
props.AddLast( prop)
Return prop
EndMethod
Method AddEditListProperty:TPropertyField( caption:String, items:String[], index:Int = 0)
Local prop:TPropertyField = New TPropertyField
prop.Class = PROP_ELIST
prop.Caption = caption
prop.Items = items
If index < 0 Then
prop.Text = items[0]
ElseIf index >= items.Length Then
prop.Text = items[items.Length-1]
Else
prop.Text = items[index]
EndIf
prop.Index = props.Count()
prop.Owner = Self
props.AddLast( prop)
Return prop
EndMethod
Method AddIconListProperty:TPropertyField( caption:String, items:String[], iconstrip:TIconStrip, icons:Int[] = Null, index:Int = 0)
Local prop:TPropertyField = New TPropertyField
prop.Class = PROP_ILIST
prop.Caption = caption
prop.Items = items
prop.IconStrip = iconstrip
prop.Icons = icons
If index < 0 Then
prop.Text = items[0]
ElseIf index >= items.Length Then
prop.Text = items[items.Length-1]
Else
prop.Text = items[index]
EndIf
prop.Index = props.Count()
prop.Owner = Self
props.AddLast( prop)
Return prop
EndMethod
Method UpdateGadget()
Local index:Int = SelectedGadgetItem( Gadget)
ClearGadgetItems( Gadget)
For Local prop:TPropertyField = EachIn props
Local text:String = LSet( prop.Caption,PROPERTY_WIDTH) + "|"
If prop.Text.Length > 0 Then
If prop.Text.Length > PROPERTY_WIDTH Then
' downsize string
AddGadgetItem( Gadget, text + prop.Text[..PROPERTY_WIDTH-3] + "...", 0,-1,"", prop)
Else
AddGadgetItem( Gadget, text + RSet( prop.Text, PROPERTY_WIDTH), 0,-1,"", prop)
EndIf
Else
AddGadgetItem( Gadget, text + RSet( "", PROPERTY_WIDTH), 0,-1,"", prop)
EndIf
Next
SelectGadgetItem( Gadget, index)
EndMethod
Method ProcessEvent:Int( event:TEvent)
Select event.ID
Case EVENT_GADGETSELECT
Select event.Source
Case Gadget
EndEdit()
Local prop:TPropertyField = TPropertyField( EventExtra())
If prop Then
prop.StartEdit()
lastprop = prop
EndIf
Return True
EndSelect
Case EVENT_GADGETACTION
Select event.Source
Case Gadget
EndEdit()
Return True
Case Controls[PROP_LIST], Controls[PROP_ELIST]
EndEdit()
Return True
EndSelect
EndSelect
Return False
EndMethod
EndType
It uses this to set the border on some of the gadgets
WIN32 only (just comment out any
SetGadgetBorder function calls if you dont want it)
GadgetBorder.bmx
SuperStrict
?MacOS
DebugLog "GadgetBorder.bmx doesnt support MacOS"
?Linux
DebugLog "GadgetBorder.bmx doesnt support Linux"
?
Private
?Win32
Const SWP_DRAWFRAME:Int = 32
Const SWP_FRAMECHANGED:Int = 32
Const SWP_HIDEWINDOW:Int = 128
Const SWP_NOACTIVATE:Int = 16
Const SWP_NOCOPYBITS:Int = 256
Const SWP_NOMOVE:Int = 2
Const SWP_NOSIZE:Int = 1
Const SWP_NOREDRAW:Int = 8
Const SWP_NOZORDER:Int = 4
Const SWP_SHOWWINDOW:Int = 64
Const SWP_NOOWNERZORDER:Int = 512
Const SWP_NOREPOSITION:Int = 512
Const SWP_NOSENDCHANGING:Int = 1024
Const SWP_DEFERERASE:Int = 8192
Const SWP_ASYNCWINDOWPOS:Int = 16384
Const RDW_ERASE:Int = 4
Const RDW_FRAME:Int = 1024
Const RDW_INTERNALPAINT:Int = 2
Const RDW_INVALIDATE:Int = 1
Const RDW_NOERASE:Int = 32
Const RDW_NOFRAME:Int = 2048
Const RDW_NOINTERNALPAINT:Int = 16
Const RDW_VALIDATE:Int = 8
Const RDW_ERASENOW:Int = 512
Const RDW_UPDATENOW:Int = 256
Const RDW_ALLCHILDREN:Int = 128
Const RDW_NOCHILDREN:Int = 64
Const WS_BORDER:Int = $800000 ' thin border
Const WS_EX_CLIENTEDGE:Int = 512 ' 3d border
Extern "Win32"
Function GetWindowLong:Int( hwnd:Int, index:Int) = "GetWindowLongA@8"
Function SetWindowLong:Int( hwnd:Int, index:Int, value:Int) = "SetWindowLongA@12"
Function SetWindowPos:Int( hwnd:Int, hwndafter:Int, x:Int,y:Int, cx:Int,cy:Int, flags:Int)
Function RedrawWindow:Int( hwnd:Int, rect:Byte Ptr, region:Int, flags:Int)
EndExtern
?
Public
Const BORDER_NONE:Int = 0
Const BORDER_3D:Int = 1
Const BORDER_THIN:Int = 2
Function SetGadgetBorder( gadget:TGadget, flags:Int)
?Win32
Local hwnd:Int = gadget.Query( QUERY_HWND)
Local style:Int = GetWindowLong( hwnd, GWL_STYLE)
Local exstyle:Int = GetWindowLong( hwnd, GWL_EXSTYLE)
Select flags
Case BORDER_NONE
' remove both border types
If style & WS_BORDER Then SetWindowLong( hwnd, GWL_STYLE, style & ~WS_BORDER)
If exstyle & WS_EX_CLIENTEDGE Then SetWindowLong( hwnd, GWL_EXSTYLE, exstyle & ~WS_EX_CLIENTEDGE)
Case BORDER_THIN
' add thin border & remove 3d border
If Not (style & WS_BORDER) Then SetWindowLong( hwnd, GWL_STYLE, style | WS_BORDER)
If exstyle & WS_EX_CLIENTEDGE Then SetWindowLong( hwnd, GWL_EXSTYLE, exstyle & ~WS_EX_CLIENTEDGE)
Case BORDER_3D
' remove thin border & add 3d border
If style & WS_BORDER Then SetWindowLong( hwnd, GWL_STYLE, style & ~WS_BORDER)
If Not (exstyle & WS_EX_CLIENTEDGE) Then SetWindowLong( hwnd, GWL_EXSTYLE, exstyle | WS_EX_CLIENTEDGE)
EndSelect
' update window frame
SetWindowPos( hwnd,0, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED)
RedrawWindow( hwnd, Null,0, RDW_INVALIDATE | RDW_NOCHILDREN | RDW_UPDATENOW | RDW_FRAME)
?
EndFunction
And here is the test source:
test.bmx
SuperStrict
Import "proplist.bmx"
Import "GadgetBorder.bmx"
Const WINDOW_FLAGS:Int = WINDOW_TITLEBAR | WINDOW_RESIZABLE '| WINDOW_MENU
' main window
Global window:TGadget = CreateWindow( "Properties", 64,64, 340,480, Null, WINDOW_FLAGS | WINDOW_CLIENTCOORDS)
Global props:TPropertyList = TPropertyList.Create( 0,0, 340,ClientHeight(window), window)
SetGadgetLayout props.Gadget, 1,1,1,1
Global StandardIconStrip:TIconStrip = LoadIconStrip( "icons.png")
Local LayoutItems:String[] = [ "CENTERED", "ALIGNED", "RELATIVE" ]
Local BooleanItems:String[] = [ "FALSE", "TRUE" ]
Local IconStripItems:String[] = [ "NONE", "STANDARD" ]
Local StandardIconItems:String[] = [ "NONE", "0", "1", "2", "3", "4", "5", "6", "7"]
Local StandardIconIcons:Int[] = [ -1, 0, 1, 2, 3, 4, 5, 6, 7 ]
Local prop:TPropertyField
props.AddNumberProperty( "ID")
props.AddTextProperty( "Caption")
props.AddIconListProperty( "Icon", StandardIconItems, StandardIconStrip, StandardIconIcons)
props.AddListProperty( "IconStrip", IconStripItems)
props.AddTextProperty( "Description")
props.AddListProperty( "Layout.Right", LayoutItems)
props.AddListProperty( "Layout.Left", LayoutItems)
props.AddListProperty( "Layout.Top", LayoutItems)
props.AddListProperty( "Layout.Bottom", LayoutItems)
props.UpdateGadget()
While WaitEvent()
If Not props.ProcessEvent( CurrentEvent) Then
Select EventID()
Case EVENT_WINDOWCLOSE
Select EventSource()
Case window Exit
EndSelect
EndSelect
EndIf
Wend
End
EDIT: oh,, and heres the icon file for the test ;)