I'm using a ComboBox
to display a list of many items (don't worry, the differences between the items allow a quick selection by AutoComplete
:-).
This list is created during the creation of the form (OnCreate
event), but so that the form does not freeze while filling, I added a TThread
that performs the filling to TStringList
and then assigns it to ComboBox.Items
.
It works, but when the form is displayed it is not drawn properly until the thread has finished and the content in the Combo is already displayed. Shouldn't using thread save this?
Here is my code: (it is not currently written in an IDE, so there may be typos...)
type
TLoadList = class(TThread)
public
constructor Create;
destructor Destroy; override;
private
SL: TStrings;
procedure UpdateCombo;
procedure Execute; override;
end;
implementation
uses uMain, HebNumFunc; //The main form unit
constructor TLoadList.Create;
begin
FreeOnTerminate := True;
SL := TStringList.Create;
inherited Create(True);
end;
procedure TLoadList.UpdateCombo;
begin
MainFrm.YListCombo.Items := SL;
end;
procedure TLoadList.Execute;
begin
for var I: Integer := 1 to 6000 do SL.Add(HebNumber(I)); //HebNumber This is a function that converts the value of the number to a Hebrew alphabetic value
Synchronize(UpdateCombo);
end;
destructor TLoadList.Destroy;
begin
SL.Free;
inherited;
end;
The HebNumber function is declared in the HebNumFunc unit in this way:
function HebNumber(const Number: Integer): string;
const
Letters1: Array of String = ['','א','ב','ג','ד','ה','ו','ז','ח','ט'];
Letters10: Array of String = ['','י','כ','ל','מ','נ','ס','ע','פ','צ'];
Letters100: Array of String = ['','ק','ר','ש','ת','תק','תר','תש','תת','תתק'];
function _ThousandsConv(const Value: Integer): string;
var
Input, Hundreds, Tens, Some: Integer;
begin
if Value <= 0 then Exit('');
if Value = 1 then Exit(Format('%s'' ', [Letters1[Value]]));
if Value in [2..9] then Exit(Format('%s"א ', [Letters1[Value]]));
if Value >= 10 then
begin
Input := Value;
Hundreds := Input div 100;
Input := Input mod 100;
Tens := Input div 10;
Some := Input mod 10;
Result := Format('%s%s%s"א ', [Letters100[Hundreds], Letters10[Tens], Letters1[Some]]);
end;
end;
var
Input, Thousands, Hundreds, Tens, Some: Integer;
begin
Input := Number;
Thousands := Input div 1000;
if Thousands > 999 then Exit('חריגה');
Input := Input mod 1000;
Hundreds := Input div 100;
Input := Input mod 100;
Tens := Input div 10;
Some := Input mod 10;
if (Thousands > 0) and (Hundreds Tens Some = 0) then
Exit(Format('%sתתר', [_ThousandsConv(Thousands - 1)]));
Result := Format('%s%s%s%s', [_ThousandsConv(Thousands),
Letters100[Hundreds], Letters10[Tens], Letters1[Some]]);
if Result.Contains('יה') then Exit(Result.Replace('יה', 'טו'));
if Result.Contains('יו') then Result := Result.Replace('יו', 'טז');
end;
I call it in the OnCreate event simply like this:
var
LoadList: TLoadList;
begin
LoadList := TLoadList.Create;
LoadList.Start;
{...}
end;
There is nothing else in the OnCreate
event that causes a delay in coloring the form, other than the call to create and run the Thread. There is also nothing extra an additional operation is performed in it.
CodePudding user response:
There is nothing wrong with your code. Adding 6000 items to the ComboBox is plenty slow and it may interfere with initial painting of the Form, since adding items to the ComboBox also runs in the main (GUI) thread.
You cannot move that assignment in the background thread, as working with GUI controls must be done in the main thread.
The problem you are seeing occurs because the code inside the thread runs pretty fast and will synchronize with the main thread before the Form has the chance to properly paint itself.
There are few possible solutions:
Because code in the background thread runs fast, remove the thread and just fill the ComboBox directly within the
OnCreate
event handler.Move the code from
OnCreate
to theOnShow
event handler, to run the code closer to the time when the Form will be shown. However, this event can be triggered multiple times, so you need additional boolean field to fill the ComboBox only once.
But, this will still not be enough to prevent the glitch. To do that, add a Sleep()
within the thread to slow down its execution a bit and give some time to the Form to complete its initial painting. Then you can call Synchronize()
to fill the ComboBox.
You may need to experiment with the sleep period, as slower computers will need more time to complete painting the Form, but don't sleep for too long as the user will be able to access the empty ComboBox in that case.
procedure MainFrm.FormShow(Sender: TObject);
begin
if not FFilled then
begin
FFilled := True;
TThread.CreateAnonymousThread(
procedure
var
sl: TStringList;
begin
sl := TStringList.Create;
try
for var I: Integer := 1 to 6000 do
sl.Add(HebNumber(I));
// Sleep for some small amount of time before synchronizing
// with the main thread to allow form to fully paint itself
Sleep(100);
TThread.Synchronize(nil,
procedure
begin
YListCombo.Items := sl;
end);
finally
sl.Free;
end;
end).Start;
end;
end;
Note: I used an anonymous thread to fill up the ComboBox, as this approach does not require creating an additional class, and has simpler code. But, you don't have to change that part of your code if you don't want to.