3
votes

I am using Delphi 7 and playing with a StringList, with TStream as object.

My test project has a ListBox, a Memo and 2 buttons (Add and Remove).

Here is what I got so far:

var
  List: TStringList;

procedure TForm1.FormCreate(Sender: TObject);
begin
  List := TStringList.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  I: Integer;
begin
  if (List.Count > 0) then
    for I := 0 to Pred(List.Count) do
    begin
      List.Objects[I].Free;
      List.Objects[I] := nil;
    end;
  FreeAndNil(List);
end;

procedure TForm1.btnAddClick(Sender: TObject);
var
  Strm: TStream;
begin
  Strm := TMemoryStream.Create;
  try
    Memo.Lines.SaveToStream(Strm);
    List.AddObject(IntToStr(List.Count), TObject(Strm));
    Memo.Clear;
    ListBox.Items.Assign(List);
  finally
//    Strm.Free; (line removed)
  end;
end;

procedure TForm1.btnDelFirstClick(Sender: TObject);
begin
  if (List.Count > 0) then
  begin
    List.Objects[0].Free;
    List.Objects[0] := nil;
    List.Delete(0);    
    ListBox.Items.Assign(List);
  end;
end;

When I double-click the ListBox I would like to load the selected item Stream object to Memo. Here is what I tried to do:

procedure TForm1.ListBoxDblClick(Sender: TObject);
var
  Idx: Integer;
begin
  Memo.Clear;
  Idx := ListBox.ItemIndex;
  if (Idx >= 0) and (TStream(List.Objects[Idx]).Size > 0) then
    Memo.Lines.LoadFromStream(TStream(List.Objects[Idx]));
end;

My questions are:

  1. Is correct the way I am adding and removing (freeing) the TStream object inside the StringList? Maybe I need to first free the Stream and then the Object??

  2. Is correct the way I am freeing all objects on FormDestroy event?

  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?

2
There is no need to cast Strm to TObject in the List.AddObject(IntToStr(List.Count), TObject(Strm)); line Strm IS a TObjectGerry Coll
@GerryColl, thank you for the tip!Guybrush
Your mistake is to try to force a a stream into the Objects[] property. Create a type that holds what you need.David Heffernan

2 Answers

3
votes

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;
3
votes
  1. I don't understand what you suggest about freeing the stream and then the object. As I understand it, the object you're talking about freeing is the stream. You can't destroy one before the other because there's only one object, which is a stream.

  2. Your methods of adding and removing stream objects in the string list are fine. They're not ideal, but I'll limit my comments here because Stack Overflow isn't Code Review.

  3. After you call SaveToStream, the stream's position is at the end of the stream. If you want to read from the stream, then you'll have to set the position back to the start again. Set Position := 0 for the stream prior to calling LoadFromStream.