Saving ListBoxes

Hello,
I have an app with two ListBoxes and a TextField (which is used to add data in the first LB).
Which is the best method to implement saving and opening for such an app?

Thank you

I wouldn’t call it the best, but you can get and set the entire Listbox with CellValueAt(-1, -1). Couple that with the example code from the docs for TextOutputStream and TextInputStream and you’ve got a quick 'n dirty Listbox save and load.

What do you want to do ?

a. Save a ListBox contents
Check first if the ListBox have Rows, then follow Tim’s advice.

b. Store the current window contents before the application left ?
In App.CancelClose (or Window’s CancelCLose Event: do as above for both ListBox, then append the TextField contents if not empty.
So:
Check if ListBox1 is not empty, if so: create the text file and save the ListBox contents
Same for ListBox2, and append its contents to the file,
Check if the TextField is not empty and, if so, append its contents to the file, then close it.

To read back correctly, you have to set some delimiter to separate the data from the two ListBoxes and the TextField.

Eventually, you can use BinaryStream to make the writing/reading using the text width before the text, so at read time, you could read that amount of text, attribute it to the ListBoxes / TextField.

Things do get harder if you have assigned any CellTags, RowTags, or ColumnTags as they would have to be handled separately if you needed to save them also.

For this task, I suggest to use the BinaryStream class and loop thru all cells in the listbox.

I’d create a listbox subclass with these methods in it:

[code]Public Sub SaveLB(bs As BinaryStream)
dim i,x,y As Integer

if bs=nil then Return

bs.WriteInt32 17 'Tag to validate the file on read
bs.WriteInt32 1 'Version: if later Xojo adds something to the listbox, increment that number. On read, read what the version knew.

bs.WriteInt32 me.ColumnCount 'In case the listbox can have more or less columns than “default”

for x=0 to me.ColumnCount-1
bs.WriteInt32 me.ColumnAt(0).WidthActual
SaveType bs,me.ColumnTypeAt(x)
SaveTag bs,me.ColumnTagAt(x)
next
bs.WriteInt32 54 'Tag between “headers” and content
bs.WriteInt32 me.RowCount
for y=0 to me.RowCount-1
SaveTag bs,me.RowTagAt(y)
for x=0 to me.ColumnCount-1
SaveType bs,me.CellTypeAt(y,x)

  if i=2 then bs.WriteBoolean me.CellCheckBoxValueAt(y,x)
  if i=3 or i=4 then SaveString bs,me.CellValueAt(y,x)
  SaveString bs,me.CellTooltipAt(y,x)
  bs.WriteBoolean me.CellBold(y,x)
  bs.WriteBoolean me.CellItalic(y,x)
  bs.WriteBoolean me.CellUnderline(y,x)
  SaveTag bs,me.CellTagAt(y,x)
next
bs.WriteBoolean me.Selected(y)

next
bs.WriteInt32 35 'Another tag; separator for content and properties to set after loading data
bs.WriteInt32 me.SortingColumn
Select case me.ColumnSortDirectionAt(me.SortingColumn)
case Listbox.SortDirections.Ascending
i=1
case Listbox.SortDirections.Descending
i=-1
case Listbox.SortDirections.None
i=0
End Select
bs.WriteInt32 i
bs.WriteInt32 me.ScrollPosition
bs.WriteInt32 me.ScrollPositionX
bs.WriteInt32 11 'End tag
End Sub

Public Sub SaveType(bs As BinaryStream, Type As Listbox.CellTypes)
dim i As Integer
if bs=nil then Return

Select case Type
case Listbox.CellTypes.CheckBox
i=2
case Listbox.CellTypes.Normal
i=1
case Listbox.CellTypes.TextArea
i=4
case Listbox.CellTypes.TextField
i=3
Else 'Default (or “unknown”…)
i=0
End Select
bs.WriteInt32 i
End Sub

Public Sub SaveString(bs As BinaryStream, Data As String)
if bs=nil then Return

bs.WriteInt32 Data.Length
bs.Write Data
End Sub

Public Sub SaveTag(bs As BinaryStream, Tag As Variant)
if bs=nil then Return
dim vt As Integer

vt=VarType(Tag)
bs.WriteInt32 vt

if vt>=4096 then Return 'No support for array variants yet

Select case vt
case 0 'Nil
//Nothing
case 2
bs.WriteInt32 Tag
case 3
bs.WriteInt64 Tag
case 4
bs.WriteSingle Tag
case 5
bs.WriteDouble Tag
case 6
bs.WriteCurrency Tag
case 8
SaveString bs,Tag
case 11
bs.WriteBoolean Tag
case 16
dim clr As Color

clr=Tag.ColorValue
bs.WriteInt32 clr.Red
bs.WriteInt32 clr.Green
bs.WriteInt32 clr.Blue

case 9 'Object
Select case True
case Tag IsA PushButton
dim pb As PushButton=PushButton(Tag)

  bs.WriteInt32 1
  SaveString bs,pb.Caption
  bs.WriteBoolean pb.Default
  bs.WriteBoolean pb.Cancel
case Tag IsA DateTime
  dim dt As DateTime=DateTime(Tag)
  
  bs.WriteInt32 2
  bs.WriteDouble dt.SecondsFrom1970
else 'Tag contains a class we haven't expected; either ignore saving it or implement how to save it (add a case here)
  bs.WriteInt32 -2 'Not saved
End Select

End Select
End Sub

Public Function LoadLB(bs As BinaryStream) as Integer
dim i,j,Version,x,y As Integer

if bs=nil then Return -50 'BinaryStream not set

if bs.ReadInt32<>17 then Return -39 'Invalid format/corrupted file
Version=bs.ReadInt32

me.RemoveAllRows 'Start with a fresh listbox
me.ColumnCount=bs.ReadInt32
for x=0 to me.ColumnCount-1
me.ColumnAt(0).WidthActual=bs.ReadInt32
me.ColumnTypeAt(x)=LoadType(bs)
me.ColumnTagAt(x)=LoadTag(bs)
next
if bs.ReadInt32<>54 then Return -39 'Invalid format/corrupted file
j=bs.ReadInt32-1 'Row count-1
for y=0 to j
me.AddRow “” 'Just add an empty row for now
me.RowTagAt(y)=LoadTag(bs)
for x=0 to me.ColumnCount-1
me.CellTypeAt(y,x)=LoadType(bs)

  Select case me.CellTypeAt(y,x)
  case Listbox.CellTypes.CheckBox
    me.CellCheckBoxValueAt(y,x)=bs.ReadBoolean
  case Listbox.CellTypes.TextArea,Listbox.CellTypes.TextField
    me.CellValueAt(y,x)=LoadString(bs)
  End Select
  me.CellTooltipAt(y,x)=LoadString(bs)
  me.CellBold(y,x)=bs.ReadBoolean
  me.CellItalic(y,x)=bs.ReadBoolean
  me.CellUnderline(y,x)=bs.ReadBoolean
  me.CellTagAt(y,x)=LoadTag(bs)
next
me.Selected(y)=bs.ReadBoolean

next
if bs.ReadInt32<>35 then Return -39 'Invalid format/corrupted file

me.SortingColumn=bs.ReadInt32
dim sd As Listbox.SortDirections

sd=Listbox.SortDirections.None 'Default value
Select case bs.ReadInt32
case -1
sd=Listbox.SortDirections.Descending
case 1
sd=Listbox.SortDirections.Ascending
End Select
me.ColumnSortDirectionAt(me.SortingColumn)=sd

me.ScrollPosition=bs.ReadInt32
me.ScrollPositionX=bs.ReadInt32
if bs.ReadInt32<>11 then Return -39 'Invalid format/corrupted file
End Function

Public Function LoadString(bs As BinaryStream) as String
if bs=nil then Return “”

Return bs.Read(bs.ReadInt32,Encodings.UTF8)
End Function

Public Function LoadTag(bs As BinaryStream) as Variant
if bs=nil then Return nil
dim vt As Integer

vt=bs.ReadInt32
if vt>=4096 then Return nil 'No support for array variants yet

Select case vt
case 0 'Nil
Return nil
case 2
Return bs.ReadInt32
case 3
Return bs.ReadInt64
case 4
Return bs.ReadSingle
case 5
Return bs.ReadDouble
case 6
Return bs.ReadCurrency
case 8
Return LoadString(bs)
case 11
Return bs.ReadBoolean
case 16
Return RGB(bs.ReadInt32,bs.ReadInt32,bs.ReadInt32)
case 9 'Object
Select case bs.ReadInt32 'What kind of object was saved
case 1
dim pb As PushButton=PB1

  pb.Caption=LoadString(bs)
  pb.Default=bs.ReadBoolean
  pb.Cancel=bs.ReadBoolean
case 2
  Return new DateTime(bs.ReadDouble)
case -2 'Not saved
  System.DebugLog "Something wasn't saved last time."
else 'Something was saved, but we don't know what (either something from a newer version or a forgotten class from earlier versions)
  MessageBox "Warning: some data are invalid."
End Select

End Select
End Function

Public Function LoadType(bs As BinaryStream) as Listbox.CellTypes
if bs=nil then Return Listbox.CellTypes.Default

Select case bs.ReadInt32
case 2
Return Listbox.CellTypes.CheckBox
case 1
Return Listbox.CellTypes.Normal
case 4
Return Listbox.CellTypes.TextArea
case 3
Return Listbox.CellTypes.TextField
Else 'Default (or “unknown”…)
Return Listbox.CellTypes.Default
End Select
End Function
[/code]
(the PushButton part represents any class you may have in the project; define how to save your class)

That’s just an example.

What’s wrong with a simple SQLite database? Isn’t this exactly the type of thing for which it is intended?

For plain text only, perhaps. To store styles and various other related things, I’d think it’s less appropriate.

Seems to me that a Listbox is a display article.
Not a data store.

The actual data should be in a database or a class, which can be saved/serialised.
The listbox should just display what the data store contains.

Kristin, nothing wrong with an SQLite DB. I was looking for something to stay out of it until I start delving into SQLite (I’m not into it yet)

[quote=496899:@Jeff Tullin]Seems to me that a Listbox is a display article.
Not a data store.

The actual data should be in a database or a class, which can be saved/serialised.
The listbox should just display what the data store contains.[/quote]
It may depend whether you allow the user to edit the cells and validate the changes at the end, e.g. when the window closes.
There are several possible ways; I’m not saying mine is better, it was just an example.
And, for one, I’ll never rely on a database, but that’s just a matter of opinions.

Get in there! :wink: