1.Is correct the way I am adding and removing (freeing) the TStream object inside the StringList?
Yes, because the TStrings.Objects[] property returns a TObject pointer and TStream derives from TObject, so you can call Free() on the object pointers.
Maybe I need to first free the Stream and then the Object??
You need to free the TStream objects before freeing the TStringList object. Just as you are already doing.
2.Is correct the way I am freeing all objects on FormDestroy event?
Yes. Though technically, you do not need to check the TStringList.Count property for > 0 before entering the loop, as the loop will handle that condition for you. And you do not need to nil the pointers before freeing the TStringList:
procedure TForm1.FormDestroy(Sender: TObject);
var
I: Integer;
begin
for I := 0 to Pred(List.Count) do
List.Objects[I].Free;
List.Free;
end;
One thing you are doing that is overkill, though, is Assign()ing the entire TStringList to the TListBox whenever you add/delete a single item from the TStringList. You should instead simply add/delete the associated item from the ListBox and preserve the remaining items as-is.
And add some extra error checking to btnAddClick() as well to avoid any memory leaks if something goes wrong.
Try this:
procedure TForm1.btnAddClick(Sender: TObject);
var
Strm: TStream;
Idx: Integer;
begin
Strm := TMemoryStream.Create;
try
Memo.Lines.SaveToStream(Strm);
Strm.Position := 0;
Idx := List.AddObject(IntToStr(List.Count), Strm);
except
Strm.Free;
raise;
end;
try
ListBox.Items.Add(List.Strings[Idx]);
except
List.Objects[Idx].Free;
List.Delete(Idx);
raise;
end;
Memo.Clear;
end;
procedure TForm1.btnDelFirstClick(Sender: TObject);
begin
if List.Count > 0 then
begin
List.Objects[0].Free;
List.Delete(0);
ListBox.Items.Delete(0);
end;
end;
3.When I try to load the stream back to Memo (Memo.Lines.LoadFromStream(TStream(List.Objects[Idx]))), it doesn't load, despite Stream.Size is higher than zero. What I am doing wrong?
You are not seeking the stream back to Position 0 before loading it into the Memo. SaveToStream() always leaves the stream positioned at the end of the stream, and LoadFromStream() leave the stream positioned wherever the load stopped reading from (if not at the end, in case of failure).
Now, with all of this said, I personally would not use TListBox in this manner. I would instead set its Style property to lbVirtual and then use its OnData event to display the strings from the TStringList. No need to copy them into the TListBox directly, or try to keep the two lists in sync at all times. It would be safer, and use less memory, to let the TListBox ask you for what it needs, and then you can provide it from the TStringList (which I would then change to a TList since you are not really storing meaningful names that can't be produced dynamically in the OnData event handler):
var
List: TList;
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TList.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
I: Integer;
begin
ListBox.Count := 0;
for I := 0 to Pred(List.Count) do
TStream(List[I]).Free;
List.Free;
end;
procedure TForm1.btnAddClick(Sender: TObject);
var
Strm: TStream;
Idx: Integer;
begin
Strm := TMemoryStream.Create;
try
Memo.Lines.SaveToStream(Strm);
Strm.Position := 0;
Idx := List.Add(Strm);
except
Strm.Free;
raise;
end;
try
ListBox.Count := List.Count;
except
TStream(List[Idx]).Free;
List.Delete(Idx);
raise;
end;
Memo.Clear;
end;
procedure TForm1.btnDelFirstClick(Sender: TObject);
begin
if List.Count > 0 then
begin
TStream(List[0]).Free;
List.Delete(0);
ListBox.Count := List.Count;
end;
end;
procedure TForm1.ListBoxDblClick(Sender: TObject);
var
Strm: TStream;
Idx: Integer;
begin
Memo.Clear;
Idx := ListBox.ItemIndex;
if Idx >= 0 then
begin
Strm := TStream(List[Idx]);
if Strm.Size > 0 then
begin
Strm.Position := 0;
Memo.Lines.LoadFromStream(Strm);
end;
end;
end;
procedure TForm1.ListBoxData(Control: TWinControl; Index: Integer; var Data: string);
begin
Data := IntToStr(Index);
end;