Synchronize was there almost forever. I used it in Delphi 5. But that already was told above.
You can also use Windows API (namely PostMessage
) to do it, if you can afford the change happening "soon after", in other words if your thread does not have to stop and wait until GUI updated. For example if there is a long calculations, and you want to have a gauge "1234 of 5678 complete" - then there is little point to stop the calculation processes. Let the counter would be inaccurate, plus-minus a dozen. Why care ?
Now, how to implement it... For simplistic cases you can use direct Windows access.
For example, if we have a windowed text label - the one inherited from TWinControl
Then we can bypass VCL and all its goodies and change the caption with a standard Windows API
You would also have to make a proper calculation of total numbers and messages to display. Yet this is not related to GUI and VCL. But hopefully you know about synchronization primitives, for example
AS. Yes, there is AtomicIncrement for you, i just show the framework in details.
There below i assume TMyThread to be one of the pool of workers, processing some common set of work items. It might be the only worker thread or one of many, it just does not know it, nor care about.
TThreadsCoordinator = class
...
private
CounterCS; TCriticalSection;
public
property CompleteItemsCount: integer
read FCompleteItemsCount;
function IncCompleteItemsCount(const Delta: integer): integer;
end;
...
function TThreadsCoordinator.IncCompleteItemsCount;
begin
CounterCS.Acquire;
try
Inc(FCompleteItemsCount, Delta);
finally
CounterCS.Release;
end;
Result := FCompleteItemsCount;
end;
var GlobalCaptionBuffer: array[0..127] of char;
// should be filled through with #0 before threads are started
procedure TMyThread.WorkItemComplete;
var s: string;
begin
...
s := Format('Done: %d of %d',
[ Self.Coordinator.IncCompleteItemsCount( +1 ),
Self.Coordinator.TotalItemsCount ] );
StrCopy( @GlobalCaptionBuffer[0], PChar(s));
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
PostMessage( MyProgressForm1.StaticText1.Handle,
WM_SETTEXT, 0, Integer(@GlobalCaptionBuffer[0]));
...
end;
This implementation - while being straight-forward - may suffer from synchronization issues.
* you need some global buffer for the text, that would not be deleted before WM_SETTEXT would actually be received and executed.
* you need to think what would happen, when the text in that buffer would be in process of updating when the label would read it simultaneously
* David Heffernan below claims that in some conditions the Windows controls might be in process of destruction and re-creation (read: RecreateWnd method), thus unexpected consequences might happen. Personally - as long as that ProgressForm would only be controlled by WinGDI meeans avoiding all VCL goodies (such as changing combobox styles on the go, which does trigger RecreateWnd, but which is not possible by native WinGDI API) - i cannot see why that can happen. But David claims it can. Choose for yourself, if that suits you.
However for more complex tasks (or for using window-less text labels) you can resort to "message methods" - the foundation of VCL.
This implementation is more flexible (separating data generation and data visualiation) and arguable more reliable (see RecreateWnd remarks in comments). But it need more of a boilerplate code.
const WM_IncCounter = WM_USER + 10;
type TMyProgressForm = class(TForm)
private
FCompleteItemsCount: integer;
procedure IncCounter(var Msg: TMessage ); message WM_IncCounter;
...
end;
procedure TMyProgressForm.IncCounter(var Msg: TMessage );
var s: string;
begin
Inc(FCompleteItemsCount, Msg.WParam);
s := Format('Done: %d of %d',
[ FCompleteItemsCount, TotalItemsCount ] );
Label1.Caption := s;
ProgressBar1.Position := FCompleteItemsCount;
end;
procedure TMyThread.WorkItemComplete;
begin
Inc(Self.UncommittedTicks);
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
begin
PostMessage( MyProgressForm1.Handle, WM_IncCounter, Self.UncommittedTicks, 0);
Self.UncommittedTicks := 0;
end;
end;