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!
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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With