delphitstringlisttstream

TStream as an object inside StringList


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?


Solution

  • 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;