'working, please wait' screen with thread? - delphi

Perhaps, it is very easy for you, but I am hard working on a project (for educational purposes) that is querying adsi with TADSISearch component, for several days. I'm trying to show a 'Working, Please wait..' splash screen with a man worker animated gif on Form2 while TADSISearch is searching the Active Directory. Although i tried every possibilities according to me, but i couldn't succeed. I tried to use TADSISearch in a thread, but thread is terminating before ADSIsearch finishes. I think TADSISearch is not thread safe. What do you think? Also, another way that I created Form2 and used a thread for updating it but the animated gif is stopping while main form gone adsi searching. What can you say about these? How can i make a please wait screen while ADSISearch is working and keep main form responding. Application.ProcessMessages or timer is not a way too. Thanks a lot for reading and answers.

The graphical user interface should be updated by the main thread. You should put your search code into a separate thread, and while the searcher thread is working, your main thread can show the animation along with "Please wait" message.
Your searcher thread can notify the main thread when search is done by any of the available synchronization techniques. The simplest one is to define a method in your thread class which stops the animation in user interface, and pass that method to Synchronize at the end of Execute method of your searcher thread.
Your searcher thread code will be something like this:
type
TMyThread = class(TThread)
private
procedure NotifyEndOfThread;
protected
procedure Execute; override;
end;
implementation
uses MainFormUnit;
procedure TMyThread.NotifyEndOfThread;
begin
MainForm.ShowAnimation := False;
end;
procedure TMyThread.Execute;
begin
try
{Add your search code here}
finally
Synchronize(NotifyEndOfThread);
end;
end;
And your main thread's code will be like this:
TMainForm = class(TForm)
...
private
FShowAnimation : Boolean;
procedure SetShowAnimation(Value: Boolean);
public
property ShowAnimation : Boolean read FShowAnimation write SetShowAnimation;
end;
procedure TMainForm.SetShowAnimation(Value: Boolean);
begin
FShowAnimation := Value;
if FShowAnimation then
{Add animation code here}
else
{Stop animation}
end;

Maybe you can try this:
Threaded Splashscreen for Delphi
http://cc.embarcadero.com/Item/20139
I use this on a touchscreen/terminal application (thin client, Wifi, RemObjects, etc) and it works nice!
Also got an animated gif working.

How can the thread terminate before the search is finished? If the search is executed in the thread and you have only one instance of the thread it should work.

Can you not just do a
f := TMyWaitForm.Create(self);
try
f.Show();
...start the TADSISearch...
finally
FreeAndNil(f);
end;
Putting an animated GIF on the TMyWaitForm (which displays itself) ?
I have a progress form when building websites in my web creation program, and this works like a charm.
You even may consider showing some state information on the wait form (if the TADSISearch component/software has a call back function or event which can be assigned).
Displaying a running clock showing the amount of time the process is taking, is also a nice touch.

Related

How to prevent application from freezing in a long running process?

The app seems to freeze sometimes when we try to pass a lot of imported files at once, which is done a each call of the function below for each file so the proposed solution is to add a sleep, but I can't seem to find proper documentation or explaining on how to handle it, or if I can even pass it as a parameter in a function.
This is the call for the proc
OpenQuery(FOrderToImportQuery.Database,FOrderToImportQuery);
My suggested idea if I can pass Sleep as Param
OpenQuery(FOrderToImportQuery.Database,FOrderToImportQuery, Sleep(200));
This is the function itself minus the sleep
procedure OpenQuery(aDatabase : TIBDatabase; aQuery : TIBQuery);
begin
if aDatabase.Connected = false then
databaseConnect(aDatabase);
if aDatabase.connected then
begin
try
aQuery.Open;
except
//try
aDatabase.ForceClose;
aDatabase.Open;
aQuery.Open;
{
except
on e: exception do
begin
Log('Error opening query : '+e.Message);
end;
end;
}
end;
end;
end;
The idea is I want the call to wait so it can complete properly before being called again. Would it be just fine to put Sleep at the end of the function itself?(Before the last END)
Or would passing it as a parameter in the call of the function be best? And if is so, how is this achieved... I can't find any doc on this particular circumstance.
The idea is I want the call to wait so it can complete properly before being called again.
Then the idea of using Sleep() is completely misconceived.
If, in a single thread, you call procedures A, B and C, as in
A;
B;
C;
then execution in the thread will only ever proceed to B after the call to A returns. Adding a Sleep() in either of them or in between them will only delay things: if there is a "log-jam" in A, adding a call to Sleep() in or after it will make no difference whatsoever. The fact that A, B and C all call your OpenQuery makes no difference either.
This is true even if A runs an asynchronous query, because the whole point of a call to an asynchronous query is that the call returns before the query completes - an asynchronous query spawns its own background thread in which the query actually executes, then typically passes the results back to the VCL thread via a call to Synchronize().
You have had comments suggesting that you put your query in a separate worker thread (separate from the VCL thread, that is). That's fine for stopping the VCL thread seizing up while waiting for the query(s) to complete, but including calls to Sleep() in the worker thread won't help there either.
So, the real answer to your q is for you to investigate and solve why a single call to OpenQuery causes the program to hang. But that's not what you've asked ...
First of all, let me say that I'm assuming your code is as optimized as it can be, and the time it takes to complete is inherently long. If you believe this might not be the case, you should open a new question with the details of your queries so we can help you on this.
Sleeping your main thread is definitely not the answer
The Sleep function will actually suspend the main thread for the amount of milliseconds specified. So, you will actually just be freezing your gui even more than now.
Worker thread
Creating a worker thread to handle the long-running work is probably your best bet to keep your program responsive while it's doing all the dirty work.
You'll have to take some precautions, though, because you probably don't want the user to be using the program while it's running the worker thread. For example, you don't want the user to click the start button again; or close the application, etc. But if these precautions are something like freezing the main thread, then you better just freeze it with the long-running work, anyway.
Maybe you will want a cancel button somewhere, if this is a process that can be interrupted in the middle (proper control of database transactions could provide this option safely).
Your worker thread could be something along these lines:
type
TWorkerThread = class(TThread)
private
{ Private declarations }
FDatabase: TIBDatabase;
FListQueries: TStringList;
protected
procedure Execute; override;
public
constructor Create(aDatabase: TIBDatabase; ListQueries: TStringList; CreateSuspended: Boolean);
destructor Destroy; override;
end;
implementation
{ TWorkerThread }
constructor TWorkerThread.Create(aDatabase: TIBDatabase; ListQueries: TStringList; CreateSuspended: Boolean);
begin
FListQueries.Create;
FListQueries.Assign(ListQueries);
FDatabase := aDatabase;
inherited Create(CreateSuspended);
end;
destructor TWorkerThread.Destroy;
begin
FListQueries.Free;
inherited;
end;
procedure TWorkerThread.Execute;
var i: Integer;
ibQuery: TIBQuery;
begin
{ Place thread code here }
ibQuery := TIBQuery.Create(aDatabase);
try
for i := 0 to FListQueries.Count - 1 do begin
if Terminated then
Exit;
ibQuery.SQL.Clear;
ibQuery.SQL.Add(FListQueries[i]);
OpenQuery(FDatabase, ibQuery);
end;
finally
ibQuery.Free;
end;
end;
PS: I'm sorry if there are compilation errors or if code for TIBDatabase/TIBQuery is wrong, I don't use any of these.
PPS: There is probably a problem with this code, though: I believe that the TIBConnection is very likely to not be thread-safe (I believe the client library itself is not). So you actually should create one connection just for use within the worker thread, rather than just use the same from main thread. I'll leave this correction for you, though. ;)

why does tmediaplayer delay a caption changing on a tpanel?

I am a novice programmer, so apologies if this sounds very basic to you all.
I have a procedure which looks (essentially) like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
panel1.caption:='This is a sentence';
with MediaPlayer1 do
begin
filename:='f:\untitled.wma';
open;
wait:=true;
play;
close;
end;
end;
The problem is that the caption on panel1 doesn't change until the mediaplayer has played the sound file; I need the caption to change, and the player to start playing, simultaneously. How can I ensure this?
I thought that the procedure would execute each line of code sequentially, meaning that the caption of panel1 changes, followed by the mediaplayer springing into action. Where have I gone wrong?
Explanation:
VCL components (like TPanel) usually have an internal method called Invalidate() that is called when a property (like Caption) changes and that change requires repainting part of the control (eg. to draw the new caption text).
This method only sets a flag inside the window control, but does not invoke the repaint method itself. The reason for that is to avoid calling the Repaint() method multiple times, if many properties are changed at once (sequentially, in short time).
The Repaint() method is actually called when the component receives a message to repaint via the main message loop (processed from the main thread of the application - the GUI thread).
The way you start playing the media player is blocking, because you set the Wait property to True, which makes the player block the calling thread (again the main thread) until the file has been played.
This does not give a chance to the main thread to process it's message queue and initiate the repaint.
Quick fix:
A quick fix to the problem is either the one suggested by becsystems, or this one:
panel1.Caption := 'This is a sentence';
Application.ProcessMessages();
Calling ProcessMessages() will give the main thread the opportunity to process the message queue and perform the update, just before starting to play the file.
This is a quick fix, as the main thread will still be blocked after starting to play, which will prevent other portions of the window to repaint (eg. Try moving the window around or minimizing and maximizing it while playing).
The code suggested by becsystems is similar, but instead of processing the message queue, just forces the control to repaint.
Proper fix:
To properly fix the problem you should not use the Wait property and instead handle the OnNotify event of the media player.
Here is an example, adapted from Swiss Delphi Center (not tested as I do not have Delphi installed at the moment):
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
panel1.Caption := 'This is a sentence';
with MediaPlayer1 do
begin
Notify := True;
OnNotify := NotifyProc;
Filename := 'f:\untitled.wma';
Open;
Play;
end;
end;
procedure TForm1.NotifyProc(Sender: TObject);
begin
with Sender as TMediaPlayer do
begin
case Mode of
mpStopped: {do something here};
end;
// Set to true to enable next-time notification
Notify := True;
end;
end;
Side notes:
There is a short explanation of the VCL message loop (part of Delphi Developer's Guide) published here:
Anatomy of a Message System: VCL
Also, not related to the problem, but take a look at Delphi Coding Style Guide. It's just nice when code posted is formatted.
Add a Refresh call after setting the caption, i.e.:
panel1.caption:='This is a sentence';
Refresh;

OnShown event for TForm?

At program start, in the OnActivate event handler, I need to do something which blocks the program for a few seconds. During this time the form's client area is still not completely painted, which looks ugly for the user. (During this blocked time I don't need the program to respond to clicks or other user actions, so there is no need to put the blocking operation into a thread - I just need the form to be completely painted). So I use TForm.Update and Application-ProcessMessages to update the form before the blocking operation which works very well:
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Update;
Application.ProcessMessages;
Sleep(7000);
end;
However, I wonder whether there is not another more elegant solution for this problem. This could be for example a OnShown event implemented in a descendant of TForm which will be fired AFTER the form has been completely painted. How could such an event be implemented?
Your real problem is that you are blocking the UI thread. Simply put, you must never do that. Move the long running task onto a different thread and thus allow the UI to remain responsive.
If you are looking for event which is fired when application finishes loading/repainting you should use TApplication.OnIdle event
http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Forms.TApplication.OnIdle
This event is fired once application is read to recieve users input. NOTE this event will be fired every time application becomes idle so you need to implement some controll variable which will tel you when OnIdle even was fired for the first time.
But as David already pointed out it is not good to block your UI (main thread). Why? When you block your main thread the application can't normally process its messages. This could lead to OS recognizing your application as being "Hanged". And aou definitly wanna avoid this becouse it could cause the users to go and forcefully kill your application whihc would probably lead to data loss. Also if you ever wanna design your application for any other platforms than Windows your application might fail the certification proces becouse of that.
In the past a simple PostMessage did the trick.
Essentially you fire it during DoShow of the base form:
procedure TBaseForm.DoShow;
begin
inherited;
PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;
then catch the msg and create an AfterShow event for all forms inherited from this base form.
But that no longer works, well not if you are skinning and have a good number of VCL controls.
My next trick was to spawn a simple thread in DoShow and check for IsWindowVisible(Handle) and IsWindowEnabled(Handle). That really sped things up it cut 250ms from load time since db opening and other stuff was already in the AfterShow event.
Then finally I thought of madHooks, easy enough to hook the API ShowWindow for my application and fire APP_AFTERSHOW from that.
function ShowWindowCB(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;
begin
Result := ShowWindowNext(hWnd, nCmdShow);
PostMessage(hWnd, APP_AFTERSHOW, 0, 0);
end;
procedure TBaseForm.Loaded;
begin
inherited;
if not Assigned(Application.MainForm) then // Must be Mainform it gets assigned after creation completes
HookAPI(user32, 'ShowWindow', #ShowWindowCB, #ShowWindowNext);
end;
To get the whole thing to completely paint before AfterShow it still needed a ProcessPaintMessages call
procedure TBaseForm.APPAFTERSHOW(var AMessage: TMessage);
begin
ProcessPaintMessages;
AfterShow;
end;
procedure ProcessPaintMessages; // << not tested, pulled out of code
var
msg: TMsg;
begin
while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(msg);
end;
My final test was to add a Sleep to the AfterShow event and see the Form fully painted with empty db containers since the AfterShow events had not yet completed.
procedure TMainForm.AfterShow;
begin
inherited;
Sleep(8*1000);
......

Which is the best place to initialize code? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs
Which is the best place to initialize code such as loading INI file? I want first to show the form on screen so the user know that the app is loading and ONLY after that I want to call lengthy functions such as LoadIniFile or IsConnectedToInternet (the last one is REALLY slow).
The OnCreate is not good because the form is not yet ready and it will not show up on screen.
I do this I DPR but not working always:
program Test;
begin
Application.Initialize;
Application.Title := 'Test app';
Application.CreateForm(TfrmTest, frmTest);
frmTest.Show; <---------------------- won't show
LateInitialize;
Application.Run;
end.
The form will not show until LateInitialize (4-5 seconds) is executed.
procedure LateInitialize;
begin
CursorBussy;
TRY
// all this won't work also. the form won't show
frmTest.Visible:= TRUE;
Application.ProcessMessages;
frmTest.Show;
Application.ProcessMessages;
frmTest.BringToFront;
frmTest.Update;
Application.ProcessMessages;
DoSomethingLengthy; {4-5 seconds}
FINALLY
CursorNotBussy;
END;
end; <--------- Now the form shows.
And yes, frmTest it is my only form (the main form).
After calling frmTest.Show, you can call frmTest.Update to let it render onscreen, before then calling LateInitialize. But until Application.Run is called, the main message loop will not be running, so the form will not be able to do anything else until then.
Another option is to use the form's OnShow event to post a custom window message back to the form via PostMessage(), then have the form call LateInitialize when it receives that message at a later time. That will allow the form to process painting messages normally until LateInitialize is called.
Anything that blocks the main thread for more than a few milliseconds/seconds really should be moved into a separate worker thread instead (especially things like IsConnectedToInternet). The main thread should be used for running the UI.
An easy way to do this, is to send a message to yourself.
I do this all the time
const
MSG_AFTERCREATE = WM_APP + 4711;
...
procedure OnCreate(Sender: TObject);
procedure AfterCreate(var message: TMessage); message MSG_AFTERCREATE;
...
Implementation
procedure OnCreate(Sender: TObject);
begin
PostMessage(Self.Handle, MSG_AFTERCREATE, 0, 0);
end;
procedure AfterCreate(var message: TMessage);
begin
//Do initializing here... the form is done creating, and are actually visible now...
end;
Variant 1: Use TTimer with a 1 second delay, run it from main form's OnShow
In TTimer do the initialisation
This will give time for most components to initialize and draw themselves
Variant 1.1: use message method in function and call Win API PostMessage (but not SendMessage aka Perform) from OnShow. This is seemilar but more cheap and fast. However that message "do init now" sometimes may be received before some complex component on the form would fully draw itself.
Variant 2: use threads (OmniThreadsLib or even plain TThread)
Launch it from MainForm OnCreate and let it prepare all data in background, then enable all needed buttons, menus, etc
That is truly the best way if you have long and blocking functions, liek you described IsConnectedToInternet.
Variant 3: use SplashScreen before showing main form.
That is good because users see that application not read yet.
That is bad for that very reason - people start feeling your program is slow. Google Chrome was told to draw their main form as picture in 1st moments just to make look "we are already started" even the actual control would be ready a bit later.
A long time ago in another forum far far away, someone posted the following to document the life cycle of a form. I have found it useful, so am sharing it here.
Create OnCreate
Show OnShow
Paint OnPaint
Activate OnActivate
ReSize OnResize
Paint OnPaint
Close query OnCloseQuery
Close OnClose
Deactivate OnDeactivate
Hide OnHide
Destroy OnDestroy
Try the OnActivate event.

Display progress from time consuming process

Sorry for my bad English...
Using Delphi 7 I want to create a dialog window to show that something is happening in my application when i have to run slow processes.
My idea was to do something that i can use like:
with TMyDialog.Create do
begin
//call the time consuming method here
Free;
end;
When i create the dialog, a window with an animation or something will show and will disappear after the time consuming method ends (on the free method) - it would be nice if I could manually update the progress from that dialog, in cases when the process give me such information:
with TMyDialog.Create do
begin
while time_consuming_method do
begin
UpdateStatusOnMyDyalog();
end;
Free;
end;
but normally it would only be a animation to show that something is happening.
Has someone did something like that, knows a component or have any suggestions on whats the best way to do it in the most clean and simple way?
The bad but easy way to do this is to call Application.ProcessMessages or UpdateWindow(Handle) (to update the form) and increment a progressbar during your time_consuming_method. A slightly better method would be to wrap your time_consuming_method up into a class with an OnProgress event. Finally as other people have suggested you could use a separate thread for your time_consuming_method - which is the most powerful technique, but has the worst learning curve.
You will need to run your time consuming process in a separate thread, and have that thread report its' progress to your main UI thread using synchronization.
Here is an example that shows you how to start a new thread and have that thread do the synchronized progress reporting.
--jeroen
It's quite common to report progress in this way (using, for instance, a progress bar).
Your "time consuming process" needs to receive either a callback function that will be called every time it has some progress to report or, if you are willing to bind it more tightly with your user interface design, a reference to a component of some kind that it will know how to update. This can be a progress bar which it will step, a listbox or memo field that will receive a new line with status updates, a label control the caption of which will get updated, and so on.
Displaying a progress during long operations depend on several factors (limitations) :
Defined/undefined progress (you know,
may calculate, how many steps does
the operation take)
Interruptibility/segmentation (you
will be able, or have to, interrupt
the operation to refresh the progress
to the user)
The operation is thread-able (you may
put the operation a thread)
For defined progress it's common to display a segmented progress bar, and for undefined an animation or progress bar with the style "Marquee".
The main consideration is whether the operation is segmented/interruptible or not. Because if it's not, and you don't take care of it, your application will freeze until the operation finishes.
Searching for files is one example of segmented operation. Each found file is one segment , and it gives you the ability to display the progress to the user, and refresh the display.
Example:
TFrmUndefinedProgress = class(TForm)
private
FCallbackProc : TNotifyEvent;
protected
procedure WndProc(var Message:TMessage); override;
public
constructor Create(aCallbackProc: TNotifyEvent);
procedure UpdateProgress(const aStr : string; aPercent : integer);
...
constructor TFrmUndefinedProgress.Create(aCallbackProc: TNotifyEvent);
begin
inherited Create(nil);
FCallbackProc := aCallbackProc;
end;
...
procedure TFrmUndefinedProgress.FormShow(Sender: TObject);
begin
Update;
PostMessage(Handle, WM_START_UNDEFPROG, 0, 0);
end;
Send message to window procedure on your form's OnShow, to make sure that it will be rendered first.
procedure TFrmUndefinedProgress.WndProc(var Message: TMessage);
begin
if (Message.Msg = WM_START_UNDEFPROG) then begin
if Assigned(FCallbackProc) then
FCallbackProc(Self); --> Call your callback procedure
PostMessage(Handle, WM_CLOSE, 0, 0); --> close when finished
end
else
inherited;
end;
And if you make a regular procedure in your form's unit...
procedure ShowUndefinedProgress(aCallbackProc : TNotifyEvent);
var
FrmUndefinedProgress : TFrmUndefinedProgress;
begin
FrmUndefinedProgress := nil;
try
FrmUndefinedProgress := TFrmUndefinedProgress.Create(aCallbackProc);
FrmUndefinedProgress.ShowModal;
finally
FreeAndNil(FrmUndefinedProgress);
end;
end;
You then may call progress form like this:
ShowUndefinedProgress(HandleSomeOperation);
where you pass your aCallbackProc.
Inside you put your operation:
procedure TForm1.HandleSomeOperation(Sender: TForm);
var
aProgress : TFrmUndefinedProgress;
begin
--> Do something
aProgress := TFrmUndefinedProgress(Sender);
aProgress .UpdateProgress(SomeMessage, Percent);
Update the display for each found file ...
If you have operation that takes long time, but you have no way of interrupting it, then you should put it in a thread.
Create a descendant of the TThread object.
Override it's Execute method
Do your thing inside Execute
And use it:
Create a form
Start some animation on it's OnShow
Then run your thread
Close when thread finishes.

Resources