Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TListBox sync with TStringList

Tags:

delphi

I've got a background thread sending messages to the main thread which, in turn, adds the messages to a TListBox like a log.

Thing is, this background thread is really fast and I dont really need to update the log that fast. I'd like to add the messages to a TStringList and set a timer to update the TListBox every second or so.

I've tried using:

listBox1.Items := StringList1;

or

listBox1.Items.Assign(StringList1);

in the OnTimer event and it works. Thing is, it never let's the user really scroll or click the listbox, because it refreshes every second.

I'm using Delphi XE4

Is there a more elegant way to sync the contents of the listbox with this background StringList (or any other list if necessary)? Thank you in advance!

like image 614
IgorMF Avatar asked Sep 12 '13 15:09

IgorMF


1 Answers

Use a virtual approach

Set the Style property of the ListBox to lbVirtual, and assign the OnData event to let it request the strings needed for painting the control, rather then owning the strings which resets the whole control on every single update. Illustrative code:

unit Unit1;

interface

uses
  Windows, Messages, Classes, Controls, Forms, AppEvnts, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    ListBox1: TListBox;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Data(Control: TWinControl; Index: Integer;
      var Data: String);
    procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
    procedure Timer1Timer(Sender: TObject);
  private
    FStrings: TStringList;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FStrings := TStringList.Create;
  FStrings.CommaText := 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z';
  ListBox1.Count := FStrings.Count;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FStrings.Free;
end;

procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
  var Data: String);
begin
  Data := FStrings[Index];
end;

procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  FStrings[Random(FStrings.Count)] := Chr(65 + Random(26));
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ListBox1.Invalidate;
end;

end.

In this example I use the OnIdle event of a TApplicationEvents component to simulate your threaded updates of the StringList. Note that you now can scroll and select items in the ListBox, despite of the 1 second update interval of the Timer.

Syncronizing the item count

Changes in item count of the StringList also needs to be reflected in the ListBox. This needs be done by ListBox1.Count := FStrings.Count, but then the ListBox appearance will be reset again. Thus a workaround is needed by temporarily preventing it from redrawing/updating alltogether:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if Random(2) = 0 then
  begin
    FStrings.Add('A');
    SyncListCounts;
  end
  else
    ListBox1.Invalidate;
end;

procedure TForm1.SyncListCounts;
var
  SaveItemIndex: Integer;
  SaveTopIndex: Integer;
begin
  ListBox1.Items.BeginUpdate;
  try
    SaveItemIndex := ListBox1.ItemIndex;
    SaveTopIndex := ListBox1.TopIndex;
    ListBox1.Count := FStrings.Count;
    ListBox1.ItemIndex := SaveItemIndex;
    ListBox1.TopIndex := SaveTopIndex;
  finally
    ListBox1.Items.EndUpdate;
  end;
end;
like image 126
NGLN Avatar answered Nov 18 '22 17:11

NGLN