Hopefully a simple one. I am using an OTL Parallel.For loop to process lots of data. The amount of data can change and if there is a lot (that takes over 2 seconds) Windows flickers the application form and gives a temporary "not responding" status in the title bar.
To get around this I thought I could put the procedure with the Parallel.For loop inside an OTL Async call, like
done:=false;
Async(ProcedureThatDoesParallelFor).Await(
procedure begin
done:=true;
end);
repeat application.processmessages until done=true;
This works (or seems to work) but can lead to the program just aborting/exiting without any error messages. It only seems to cause the silent abort problem when the Parallel.For loop is very quick to run.
If I remark the above code and take the call to ProcedureThatDoesParallelFor outside of it the app runs fine without unexpected quitting, so I am assuming it must be the Async call causing the problem. Or a combination of Parallel.For within Async?
Is using Async the best way to run another procedure and wait for it to finish? Is there a better OTL way of doing this?
Thanks for any ideas or solutions.
Here is the simplest example to show the crashing error. Single form with a memo and button. Click the button and the program will hang around iteration 300.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,OtlParallel,OtlTaskControl;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure AsyncParallelFor;
var iterations:integer;
blah:integer;
begin
iterations:=10;
//for iter:=0 to limit-1 do
Parallel.For(0,iterations-1).Execute(procedure(iter:integer)
var x,y:integer;
begin
for y:=0 to 50 do
begin
for x:=0 to 50 do
begin
blah:=x+y;
end;
end;
end);
end;
procedure AsyncProcedure;
var done:boolean;
begin
done:=false;
Parallel.Async(
procedure
begin
//executed in background thread
AsyncParallelFor;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
//executed in main thread after the async has finished
done:=true;
end
)
);
//this point is reached immediately after the call to Async
//the repeat loop waits until the Async is finished being signalled via done variable
repeat
application.processmessages;
until done=true;
end;
procedure TForm1.Button1Click(Sender: TObject);
var iters:integer;
begin
iters:=0;
repeat
memo1.lines.add('Iteration '+inttostr(iters)+'...');
memo1.lines.add('Before Async');
application.processmessages;
AsyncProcedure;
memo1.lines.add('After Async');
application.processmessages;
inc(iters);
until 1>2;
end;
end.
AsyncParallelFor shows the basic nested loops. Just a simple addition in there to demo the issue.
AsyncProcedure does the OTL Async call and waits for the return.
I have a lot of non parallel code before and after the call to AsyncProcedure that need to wait for the parallel.for loop to finish.
If I change the button click to call AsynParallelFor directly without the Async then there is no hang.
In your AsyncProcedure, there is no need to repeatedly wait for the async call to finish. This defeats the event driven model that the OS is built on. Specially calling Application.ProcessMessages can lead to unexpected things to happen.
Use the OnTerminate event to signal that the async call is done and there take actions what to do next. In the example provided in this answer, a callback method is used to handle that.
A button click method is supposed to do only a short task, not an eternal loop with the dreaded calls to Application.ProcessMessages.
Instead, use a flag to indicate whether a new call to the async procedure should be done.
Below is an example how to modify your test with a callback method and an event driven model (I did not try the OTL calls, but I would be surprised if the library is the cause of your problems):
type
TForm1 = class(TForm)
BtnStart: TButton;
BtnStop: TButton;
Memo1: TMemo;
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
private
{ Private declarations }
fDoRepeat : Boolean;
fIterations : Integer;
procedure MyCallbackMethod(Sender : TObject);
public
{ Public declarations }
end;
procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
Parallel.Async(
procedure
begin
//executed in background thread
AsyncParallelFor;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
//executed in main thread after the async has finished
MyCallbackMethod(Nil);
end)
);
end;
procedure TForm1.MyCallbackMethod(Sender : TObject);
begin
if (Sender = nil) then // Callback from AsyncProcedure
memo1.lines.add('After Async');
if fDoRepeat then begin
Inc(fIterations);
memo1.lines.add('Iteration '+inttostr(fIterations)+'...');
memo1.lines.add('Before Async');
AsyncProcedure(MyCallbackMethod);
end;
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
fDoRepeat := true;
fIterations := 0;
BtnStart.Enabled := false;
MyCallbackMethod(Sender); // Start iteration event looping
end;
procedure TForm1.BtnStopClick(Sender: TObject);
begin
fDoRepeat := false; // Stop iteration loop
BtnStart.Enabled := true;
end;
Update
Running the above test in debug mode gave:
Out of memory
after 387 iterations in an OTL unit allocating memory for a buffer (and it is running slow).
Testing the OTL Parallel.For() with some other examples from Updating a Progress Bar From a Parallel For Loop (Plus Two Bonuses) did not improve the outcome. Program hangs at 400 iterations.
Using the bug ridden Delphi PPL did in fact work, though.
Uses
Threading;
procedure AsyncParallelFor;
var
iterations:integer;
blah:integer;
begin
iterations := 10;
TParallel.For(0,iterations-1,
procedure(iter : integer)
var x,y:integer;
begin
for y := 0 to 50 do
begin
for x := 0 to 50 do
begin
blah := x+y;
end;
end;
end);
end;
procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
TTask.Run(
procedure
begin
AsyncParallelFor;
//executed in main thread after the async has finished
TThread.Queue(nil,
procedure
begin
MyCallbackMethod(Nil);
end
);
end);
end;
To update the GUI within a parallel for loop, just use this code within the loop:
TThread.Queue(nil,
procedure
begin
// Some code that updates the GUI or calls a method to do so.
end
);
Related
The game i'm trying to make is snake, in the console application. I can get the snake to move along the screen however I am not sure how I can read the user inputing the keys WASD, code segment shown below.
write (StoredTrail); //This would be writing the snake, each segment is '[]'
repeat
clearScreen; // This is calling a clear screen procedure, if there is a simple way to make the snake disappear from the console that avoids such a lengthy procedure that would be great to know.
delete (StoredTrail ,0,2);
StoredTrail:= A+StoredTrail; //This makes the trail move along(A is ' ')
write(StoredTrail);
Xcord:= Xcord + 1;
sleep(150);
until 1=2;
I am also aware the sleep is very inefficient so if anyone had a better way to delay the movement of the snake that would also be welcomed. Coding for increasing the snakes length is also not implemented yet.
Many thanks to anyone able to help.
I give an example for a event driven console application, which update the screen iterativelly.
It would be too long to write here the user event handler routines and you can find it on a lot of places on the net. This is a fine example, which handle keyboard and mouse events as well:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils
, Vcl.ExtCtrls
;
type
TSnakeApp = class
private
fTimer : TTimer;
fExit : boolean;
protected
function createTimer : TTimer; virtual;
procedure releaseTimer; virtual;
procedure drawSnake( timer_ : TObject ); virtual;
procedure handleKeyBoardEvents; virtual;
public
constructor create;
destructor destroy; override;
procedure run;
end;
var
app : TSnakeApp;
function TSnakeApp.createTimer : TTimer;
begin
result := TTimer.Create( NIL );
end;
procedure TSnakeApp.releaseTimer;
begin
fTimer.Free;
end;
procedure TSnakeApp.drawSnake( timer_ : TObject );
begin
// if it takes too long time (>= times.interval), then disable+enable the timer
fTimer.enabled := FALSE;
try
finally
fTimer.enabled := TRUE;
end;
end;
procedure TSnakeApp.handleKeyBoardEvents;
begin
// It would be too long to write here, but you can find a very nice keyboard/mouse event handler for console applications here:
// https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
// case ( keyPressed ) of
// VK_ESC:
// fExit := TRUE;
// ...
end;
constructor TSnakeApp.create;
begin
inherited create;
fTimer := createTimer;
fTimer.Interval := 20;
fTimer.OnTimer := drawSnake;
end;
destructor TSnakeApp.destroy;
begin
releaseTimer;
inherited destroy;
end;
procedure TSnakeApp.run;
begin
fTimer.enabled := TRUE;
while ( not fExit ) do
begin
handleKeyBoardEvents;
end;
fTimer.enabled := FALSE;
end;
begin
try
try
app := TSnakeApp.create;
app.run;
finally
app.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
In the days of Turbo Pascal an ancient predecessor of Delphi there was a CRT unit that provided some useful functions for console applications. Two such functions that would be of interest to you for keyboard input are KeyPressed() which returns true if a key has been pressed and GetKey() which returns the key pressed. For Delphi itself there are a few sources of libraries that offer compatible functions. One is Rudy's Velthuis.Console unit.
I would like to write a loop that checks the value of a variable has changed. There's no event that fires to tell me the value has changed.
The application doesn't support multi threading.
How to achieve this without causing app to freeze ?
The aim is this:
Application starts
...
loop
Check variable value
If changed then
exit
if timedOut then
exit
While loop causes application to freeze.
Thank you.
* Edit *
This is what I'm after (this code is written by Remy Lebeau):
const
APPWM_COM_EVENT_DONE = WM_APP + 1;
APPWM_COM_EVENT_TIMEOUT = WM_APP + 2;
type
MyClass = class
private
MsgWnd: HWND;
procedure COMEventHandler(parameters);
procedure WndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure DoIt;
end;
constructor MyClass.Create;
begin
inherited;
MsgWnd := AllocateHWnd(WndProc);
end
destructor MyClass.Destroy;
begin
KillTimer(MsgWnd, 1);
DeallocateHWnd(MsgWnd);
inherited;
end;
procedure MyClass.COMEventHandler(parameters);
begin
KillTimer(MsgWnd, 1);
PostMessage(MsgWnd, APPWM_COM_EVENT_DONE, 0, 0);
end;
procedure MyTimer(hWnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
begin
KillTimer(hWnd, idEvent);
PostMessage(hWnd, APPWM_COM_EVENT_TIMEOUT, 0, 0);
end;
procedure MyClass.WndProc(var Message: TMessage);
begin
case Message.Msg of
APPWM_COM_EVENT_DONE:
begin
// Event fired, all good
end;
APPWM_COM_EVENT_TIMEOUT:
begin
// Event timed out
end;
else
begin
Message.Result := DefWindowProc(MsgWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
end;
procedure MyClass.DoIt;
begin
SetTimer(MsgWnd, 1, 1000 * 1000, #MyTimer);
// invoke COM function that will eventually trigger the COM event...
end;
How to call DoIt and wait for either Event to fire or timeout without causing the application to freeze ?
Tried using while do loop but that prevented WndProc from running.
Thank you
Answer depends on your application demands. There are 2 easy solutions with prons and cons each:
1. Put Timer to application and check value by timeout. Dignity - it is the most easy way for GUI application (Windows messages loop already exists), drawback on other side - there will be delta time of detecting value have been changed.
2. Handle Application.OnIdle event. Disadvantage of this approach - yor checking procedure will be runned if nobody click on GUI elements.
Professional way to solve your solution - wrap your variable by complex object, for example:
Trigger = class
private
FOnChanged: TNotifyEvent;
public
procedure Emit;
property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
end;
procedure Trigger.Emit;
if Assined(FOnChanged) then
FOnChanged(Self)
end;
Cause of your application has not threads we can implement Trigger without mutexes/critical sections, on another side you can handle changing as soon as event producer will raise Emit
Good approach if you don't want use multithreading is split your ligic on multiple state machines based on coroutines.
Example based on AIO framework https://github.com/Purik/AIO
AIO framework create itself events loop, scheduling multiple state machines in parallel without threads:
program TriggerExample;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
SyncObjs,
Gevent,
Greenlets;
const
WAIT_TMEOUT_MSEC = 1000;
var
ChangedEvent: TGevent;
Value: Boolean = False;
// Part of application that raise change events randomly
procedure EventsProducer;
begin
while True do
begin
Greenlets.GreenSleep(100+Random(10000));
Value := True;
ChangedEvent.SetEvent;
end;
end;
begin
ChangedEvent := TGevent.Create(False, False);
// run fake event producer inside other state machine
TSymmetric.Spawn(EventsProducer);
// Loop
while True do
begin
if ChangedEvent.WaitFor(WAIT_TMEOUT_MSEC) = wrSignaled then
begin
WriteLn('Value was changed');
Value := False
end
else
begin
WriteLn('Exit by timeout');
end;
end;
end.
I am just playing around with the OmniThreadLibrary after reading the docs, but I am still facing some simple/early problems on constructing a ParallelTask.
After the construction of a ParallelTask with cancellationToken and terminationHandler, terminationHandler.OnTerminated and OnStop are not being executed after the async execution is done and I was not able to find out why :-(
I hope some of the OTL pros can help me out on this one.
What I want to achieve:
execute an asynchronous operation
stop execution when cancelled (cancellationtoken)
execute some code in the mainthread (checking for exceptions) when the async operation is done
What I did so far:
After reading the docs I created a ParallelTask, setting up cancellationToken and terminationHandler via TaskConfig and executed the operation.
The executed operation itself checks for the cancellationToken being signalled and does its work (here a Sleep of 1s). The HandleOnTerminated method checks for
errors and sets the fIsDone and fHasError flags, getting read by someone from mainthread.
unit OTLSetup.Async;
interface
uses
OtlParallel, OtlSync, OtlTaskControl, OtlTask;
type
IAsyncOperation = interface
['{6B10AB46-DEB6-48F5-AC36-E9327AA54C82}']
procedure Execute;
procedure Cancel;
function IsDone: boolean;
end;
TAsyncOperation = class(TInterfacedObject, IAsyncOperation)
protected
fParallelTask: IOmniParallelTask;
fCancellationToken: IOmniCancellationToken;
fIsDone: boolean;
procedure HandleOnTerminated(const task: IOmniTaskControl);
procedure HandleOnStop;
procedure AsyncOperation(const task: IOmniTask);
public
procedure Execute;
procedure Cancel;
function IsDone: boolean;
end;
implementation
uses
Winapi.Windows;
{ TAsyncOperation }
procedure TAsyncOperation.Cancel;
begin
fCancellationToken.Signal;
end;
procedure TAsyncOperation.Execute;
begin
if Assigned(fParallelTask) then
Exit;
fIsDone := false;
fCancellationToken := CreateOmniCancellationToken;
fParallelTask := Parallel.ParallelTask;
fParallelTask.NoWait.NumTasks(1);
fParallelTask.TaskConfig(Parallel.TaskConfig.CancelWith(fCancellationToken).OnTerminated(HandleOnTerminated));
fParallelTask.OnStop(HandleOnStop);
fParallelTask.Execute(AsyncOperation);
end;
procedure TAsyncOperation.AsyncOperation(const task: IOmniTask);
var
I: Integer;
begin
for I := 0 to 5 do
if task.CancellationToken.IsSignalled then
Exit
else
Winapi.Windows.Sleep(1000);
end;
procedure TAsyncOperation.HandleOnStop;
begin
fParallelTask := nil;
fIsDone := true;
end;
procedure TAsyncOperation.HandleOnTerminated(const task: IOmniTaskControl);
begin
fParallelTask := NIL;
fIsDone := true;
end;
function TAsyncOperation.IsDone: boolean;
begin
result := fIsDone;
end;
end.
With this peace of Code, fIsDone is never set, because HandleOnTerminate and HandleOnStopare never called. So with the exmaple from above
the following ConsoleApplication seems to never end:
program OTLSetup;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
OTLSetup.Async in 'OTLSetup.Async.pas';
var
LAsync: IAsyncOperation;
begin
LAsync := TAsyncOperation.Create;
try
LAsync.Execute;
while not LAsync.IsDone do
Writeln('Async task still running');
Writeln('Async task finished');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
As posted in the comments, the issue I was facing has been caused by the consoleapplication itself, because it does not contain a messageloop (in my case a DUnitX project).
Because the OTL communication seems to be based on windowsmessages, OnTerminated and OnStop are not triggered in absence of a working messageloop.
I am trying to write a separate unit for my main form to call, all of my other units are working except for one that uses TTimer.
Basically what the function is supposed to be doing is that the main form uDataReceived calls BlinkRect(Gateway) which is processed in rRectControl unit and the according Rectangle will blink in the main form.
Here are the codes:
unit uRectControl;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, System.IOUtils, FMX.Graphics, FMX.Types, FMX.Objects;
var
Blinks: array [0 .. 2] of record Rectangle: TRectangle;
Timer: TTimer;
end;
type
TMyClass = Class(TObject)
private
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
public
procedure BlinkRect(Gateway: integer);
end;
procedure AssignRectangles;
implementation
uses uDataReceived;
// Error shows "Cannot resolve unit name 'uDataReceived'
{ TMyClass }
procedure AssignRectangles;
var
i: integer;
begin
Blinks[0].Rectangle := TC_Theft_Detection.rect1;
// Error shows Undeclared Identifier TC_Theft_Detection (which is the name of the main form)
Blinks[0].Timer := nil;
Blinks[1].Rectangle := TC_Theft_Detection.rect2;
Blinks[1].Timer := nil;
Blinks[2].Rectangle := TC_Theft_Detection.rect3;
Blinks[2].Timer := nil;
for i := 0 to 2 do
Blinks[i].Rectangle.Fill.Color := TAlphacolors.blue;
end;
procedure TMyClass.BlinkRect(Gateway: integer);
begin
Blinks[Gateway].Rectangle.Fill.Color := TAlphacolors.Red;
Blinks[Gateway].Rectangle.Fill.Kind := TBrushKind.Solid;
Blinks[Gateway].Rectangle.Stroke.Thickness := 0.3;
Blinks[Gateway].Rectangle.Stroke.Color := TAlphacolors.Black;
if Blinks[Gateway].Timer = nil then
begin
Blinks[Gateway].Timer := TTimer.Create(nil);
Blinks[Gateway].Timer.OnTimer := Timer1Timer;
Blinks[Gateway].Timer.Interval := 500;
Blinks[Gateway].Timer.Tag := Gateway;
Blinks[Gateway].Timer.Enabled := True;
end;
end;
procedure TMyClass.Timer1Timer(Sender: TObject);
var
Timer: TTimer;
begin
Timer := TTimer(Sender);
Blinks[Timer.Tag].Rectangle.Visible := not Blinks[Timer.Tag]
.Rectangle.Visible;
end;
end.
I know there must be something wrong with the unit shown above, and my question is:
How to work with TTimer in a separate unit and how to call the procedure BlinkRect(Gateway) on the main form.
Thanks a lot!!
Your code in uRectControl works provided AssignRectangles is called before you attempt to call BlinkRect. However there are a number of issues to be addressed.
1) Cross dependency of units
The form (uDataReceived) apparently uses uRectControl and that is fine. The way uRectControl is written it needs to use (uses uDataReceived in the implementation) the form and this is not good.
This error is simple to correct, because the AssignRectangles procedure is the only place where the form is referred to. AssignRectangles could just as well be in the form, since the Blinks[] array is global (in the interface of uRectControl) and can therefore be accessed by the form.
2) Global variables
Global variables should be avoided as much as possible. You have defined both the Blinks[] array and the Timer to be global, so you might by mistake access and modify them from anywhere in your program just by adding uRectControl to a uses clause. In future development you might add new forms that have indicators you want to blink and add TRectangles to the Blinks[] array possibly overwriting value that are already there and you end up in a mess. I will address this issue in my suggestion below.
3) Hardcoded entities
In Proof Of Concept code it is acceptable (or not) to hardcode constants, sizes of arrays etc. but not in production code. Just think about all changes you need to do just to add one more blinking rectangle to the form. Dynamical arrays or better TList and its derivatives etc. comes to rescue here. You have also limited yourself to only TRectangles. What if you would like to have circular indicators in your form?
4) Unsyncronized blinking
It may look cool (not really) when indicators are blinking all over the place, but actually it is just distracting. I guess you tried to change this with the timer in TMyClass, but you still left the individual timers in the Blinks records. I will address this also in my suggestion below.
Here is a suggestion
unit ShapeBlinker;
interface
uses
System.SysUtils, System.UITypes, System.Classes, System.Generics.Collections,
FMX.Graphics, FMX.Types, FMX.Objects;
type
TBlinkState = (bsOff, bsBlinking, bsSteady);
I have a background in Fire Alarm Systems, and it is common to have three states; off, blinking and steady lit. TBlinkState represents these.
Then comes a class that represent indicators in the UI. An indicator can be any TShape derivative like TRectangle, TCircle, TPath etc. Each state can have its own color.
type
[...]
TBlinkingShape = class
private
FShape: TShape;
FState: TBlinkState;
FOffColor: TAlphaColor;
FBlinkColor: TAlphaColor;
FSteadyColor: TAlphaColor;
public
constructor Create(AShape: TShape);
procedure SetBlinkState(NewState: TBlinkState);
end;
The field FShape holds a reference to a TShape derivative. Through this reference we have access to the actual component on the UI form and can change its color. We will see later how the TShape is passed to the constructor.
Then the second class which manages a collection of TBlinkingShape, timing and actual color changes of the indicators on the form.
type
[...]
TShapeBlinker = class
private
FBlinkingShapes: TObjectList<TBlinkingShape>;
FBlinkPhase: integer;
FTimer: TTimer;
public
constructor Create;
destructor Destroy; override;
procedure RegisterShape(Shape: TShape; OffColor, BlinkColor, SteadyColor: TAlphaColor);
procedure UnRegisterShape(Shape: TShape);
procedure BlinkTimer(Sender: TObject);
procedure SetBlinkState(Shape: TShape; NewState: TBlinkState);
function GetBlinkState(Shape: TShape): TBlinkState;
end;
FBlinkingShapes is the object list that holds instances of TBlinkingShapes.
FBlinkPhase syncronizes blinking of the indicators so that all blinking indicators change to the BlinkColor simultaneously. FTimer is common for all indicators.
Procedure RegisterShape is called by the UI when it wants to add an indicator to the list. UnRegister is called when an indicator is to be removed from the list. SetBlinkState is used to change state and GetBlinkState to retrieve the state of an indicator.
The unit is designed to be usable by any number of forms, synchronizing blinking for all of them. This requires that the TShapeBlinker is a singleton. It is therefore created in the initialization section of the unit, and freed in the finalization.
The instance is held by a var in the implementation, thus inaccessible directly from any other unit. Access is provided by a function declared as the last item in the interface of the unit:
function ShapeBlinker: TShapeBlinker;
This effectively prevents a mistake to accidentally call ShapeBlinker.Create.
Instead of commenting on each method I just copy the implementation here:
implementation
var
SShapeBlinker: TShapeBlinker;
function ShapeBlinker: TShapeBlinker;
begin
result := SShapeBlinker;
end;
{ TBlinkingShape }
constructor TBlinkingShape.Create(AShape: TShape);
begin
FShape := AShape;
FState := bsOff;
end;
procedure TBlinkingShape.SetBlinkState(NewState: TBlinkState);
begin
FState := NewState;
case NewState of
bsOff: begin
FShape.Fill.Color := FOffColor;
end;
bsBlinking: begin
FShape.Fill.Color := FBlinkColor;
end;
bsSteady: begin
FShape.Fill.Color := FSteadyColor;
end;
end;
end;
{ TShapeBlinker }
constructor TShapeBlinker.Create;
begin
FBlinkingShapes := TObjectList<TBlinkingShape>.Create;
FTimer := TTimer.Create(nil);
FTimer.OnTimer := BlinkTimer;
FTimer.Interval := 500;
FTimer.Enabled := False;
end;
destructor TShapeBlinker.Destroy;
begin
FTimer.Enabled := False;
FTimer.Free;
FBlinkingShapes.Free;
inherited;
end;
function TShapeBlinker.GetBlinkState(Shape: TShape): TBlinkState;
var
RegShape: TBlinkingShape;
begin
result := bsOff;
for RegShape in FBlinkingShapes do
if Shape = RegShape.FShape then result := RegShape.FState;
end;
procedure TShapeBlinker.SetBlinkState(Shape: TShape; NewState: TBlinkState);
var
RegShape: TBlinkingShape;
begin
for RegShape in FBlinkingShapes do
if Shape = RegShape.FShape then RegShape.SetBlinkState(NewState);
self.FTimer.Enabled := True;
end;
procedure TShapeBlinker.BlinkTimer(Sender: TObject);
var
i: integer;
begin
FTimer.Enabled := False;
FBlinkPhase := (FBlinkPhase + 1) mod 2;
for i := 0 to FBlinkingShapes.Count-1 do
with FBlinkingShapes[i] do
begin
case FState of
bsOff: begin
FShape.Fill.Color := FOffColor;
end;
bsBlinking: begin
if FBlinkPhase = 1 then
FShape.Fill.Color := FOffColor // alt. FSteadyColor
else
FShape.Fill.Color := FBlinkColor;
FTimer.Enabled := True;
end;
bsSteady: begin
FShape.Fill.Color := FSteadyColor;
end;
end;
end;
end;
procedure TShapeBlinker.RegisterShape(Shape: TShape; OffColor, BlinkColor, SteadyColor: TAlphaColor);
begin
with FBlinkingShapes[FBlinkingShapes.Add(TBlinkingShape.Create(Shape))] do
begin
FOffColor := OffColor; //TAlphaColors.Silver;
FBlinkColor := BlinkColor; //TAlphaColors.Red;
FSteadyColor := SteadyColor; //TAlphaColors.Yellow;
end;
end;
procedure TShapeBlinker.UnRegisterShape(Shape: TShape);
var
i: integer;
begin
for i := FBlinkingShapes.Count-1 downto 0 do
if FBlinkingShapes[i].FShape = Shape then
FBlinkingShapes.Delete(i);
end;
initialization
SShapeBlinker := TShapeBlinker.Create;
finalization
SShapeBlinker.Free;
end.
Finally a few words about usage. Consider a form, say TAlarmView, with 2 TRectangle and 1 TCircle.
In FormCreate you might register these for blinking as follows
procedure TAlarmView.FormCreate(Sender: TObject);
begin
ShapeBlinker.RegisterShape(Rect1, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
ShapeBlinker.RegisterShape(Circle1, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
ShapeBlinker.RegisterShape(Rect3, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
end;
and then test them with button clicks like
procedure TAlarmView.Button1Click(Sender: TObject);
begin
case ShapeBlinker.GetBlinkState(Rect1) of
bsOff: ShapeBlinker.SetBlinkState(Rect1, bsBlinking);
bsBlinking: ShapeBlinker.SetBlinkState(Rect1, bsSteady);
else ShapeBlinker.SetBlinkState(Rect1, bsOff);
end;
end;
As you see I just go through the different states for each click.
I have ID of the process. This process is an application which have a main window.
I am trying to close this application by sending WM_CLOSE to its main window.
I am searching its main window by using EnumWindows.
The problem is, that this application which I try to close, does not close always.
It is multithreaded application. Notepad and Calc are always closing when I use the same method which is presented below. But I am not sure if it is working properly cause it returns me many handles to the same window, even for Calc.exe.
Is it possible that thread is taking a handle to window and then this handle somehow become corrupted? Or maybe I should not use GetWindowThreadProcessId(hHwnd,pPid) but some other function in the callback?
I am out of ideas, would be grateful for any help. Thanks.
Code snippet:
unit Unit22;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm22 = class(TForm)
edtprocID: TEdit;
lblEnterProcessID: TLabel;
btnCloseProcessWindow: TButton;
lblStatus: TLabel;
procedure btnCloseProcessWindowClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
THandleAndHWND = record
ProcID: THandle;
WindowHandle: HWND;
end;
var
Form22: TForm22;
var
HandleAndHWNDArray: array of THandleAndHWND;
HandeIndex, lp: Integer;
implementation
{$R *.dfm}
function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall;
var
pPid : DWORD;
begin
//if the returned value in null the
//callback has failed, so set to false and exit.
if (hHwnd=0) then
begin
result := false;
end else
begin
GetWindowThreadProcessId(hHwnd,pPid);
Inc(HandeIndex);
HandleAndHWNDArray[HandeIndex].ProcID := pPid;
HandleAndHWNDArray[HandeIndex].WindowHandle := hHwnd;
Result := true;
end;
end;
procedure TForm22.btnCloseProcessWindowClick(Sender: TObject);
var
ProcID: Cardinal;
i, LastError: Integer;
begin
HandeIndex := -1;
ProcID := StrToInt(edtprocID.Text);
SetLength(HandleAndHWNDArray, 3000);
EnumWindows(#EnumProcess,lp);
for i := 0 to HandeIndex do //After EnumWindows HandleIndex is above 500 despite the fact that I have like 10 openned windows max
begin //That means that EnumWindows was called 500 times?
if HandleAndHWNDArray[i].ProcID = ProcID then //search for process equal to procces ID given by the user
begin
//if we have a processID then we have a handle to its main window
if PostMessage(HandleAndHWNDArray[i].WindowHandle, WM_CLOSE, 0, 0) then
begin
lblStatus.Caption := 'message posted!';
end else
begin
LastError := GetLastError;
lblStatus.Caption := Format('Error: [%d] ' + SysErrorMessage(LastError), [LastError]);
end;
Exit;
end;
end;
end;
end.
Have a look in this Knowledge Base Article here on how to close another application as cleanly as possible. You are doing it right so far. The Article suggests that you
first post WM_CLOSE to all windows of the application (since you cannot know for sure which one is the main).
wait with a timeout and if the timeout elapses
kill the application using TerminateProcess
I agree.