Delphi: refer to control from thread in frame - delphi

There is a FRAME (not a form) and a thread. How to refer to Frame's control from the thread? For example I want to disable a button from a thread. But I don't have a pointer to the button, no global variables in the frame.
Thanks!

You should not in fact, call any method or modify any property of an VCL control at all, or anything visible to the user (the User interface of your application, which means VCL controls normally in Delphi, whether in a frame or not) directly from a background thread.
You can however send an event or notification to the main thread, either using PostMessage, or TThread.Synchronize or TThread.Queue.
Rather than having a reference to the frame or the control in your thread object, it might be better to just pass the handle of the form that contains your frame or other controls, to the thread, and use a user-message (WM_USER+10001) like this.
I prefer PostMessage to TTHread.Synchronize or Queue, because it's really simple and it works great. It's not exactly a cross-platform-friendly technique since it's tied to the Win32 API.
You should call synchronize like this:
TMyThread = class(TThread)
private
FFrame: TFrame;
...
public
constructor Create(AFrame: TFrame);
...
end;
constructor TMyThread.Create(AFrame: TFrame);
begin
FFrame := AFrame;
inherited Create;
end;
// do not call directly, only using Synchronize
procedure TMyThread.AMethodWithNoParameters;
begin
FFrame.Button1.Enabled := not FBusy;
end;
procedure TMyThread.DoWork; // called from Execute.
begin
FBusy := true;
Synchronize(AMethodWithNoParameters);
Sleep(100); //dummy;
FBusy := false;
Synchronize(AMethodWithNoParameters);
end;

As quite rightly pointed out, you cannot call any members of any visual component in a background thread.
To disable the button from inside the thread code you have to have a reference to the button OR a reference to an event which you can assign the thread object - you can then fire the thread inside the queued or synchronized procedure, like so :-
type
test=class(tthread)
ondisablebutton:tnotifyevent;
{...}
then, when in the procedure which you encapsulate with tthread.synchronize you can call the event, not forgetting to test if it is assigned....
procedure test.synchronisedprocedure;
begin
if assigned(ondisablebutton) then
ondisablebuttone(self);
end;
When you create the thread object you have designed, you then have to assign the ondisablebutton to a procedure of the form containing the button which looks like thus :-
procedure form1.threadwantstodisablebutton(sender:tobject);
begin
button1.enabled:=false;
end;
your thread creation then needs an extra line :-
mythread:=test.create;
test.ondisablebutton:=form1.threadwantstodisablebutton;
like so, obviously you have to have access to form1 (or the form containing the button) where you are defining and creating your thread, which is not necessarily good design but it works.

Related

How to check if the Application.MainForm is valid?

How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it (from a MadExcept ExceptionHandler).
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
I was thinking:
if Assigned(Application)
and (not Application.Terminated)
and Assigned(Application.MainForm)
and Application.MainForm.HandleAllocated then
begin
PostMessage(Application.MainForm.Handle, MyMessage, 0, 0);
end;
Is this correct?
How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it.
OK.
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
Uh oh.
....
Is this correct?
No it certainly is not. Your code can never be made threadsafe because it is not permitted to access VCL objects from outside the main thread.
In your particular case consider the following sequence of events:
You perform your tests in the if culminating in your evaluating Application.MainForm.HandleAllocated as True. Ignore for a moment the fact that you are doing this outside the main thread.
You then set about preparing the call to PostMessage. But at this very instance, the main form is destroyed.
By the time your thread gets round to accessing Application.MainForm, it has gone.
You are going to need to work a little harder here. You'll need to do something like this:
// interface section of some unit
procedure RegisterMainFormHandle(Wnd: HWND);
procedure UnregisterMainFormHandle;
procedure PostMessageToMainForm(...);
// implementation section
var
MainFormHandleLock: TCriticalSection;
MainFormHandle: HWND;
procedure RegisterMainFormHandle(Wnd: HWND);
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := Wnd;
finally
MainFormHandleLock.Release;
end;
end;
procedure UnregisterMainFormHandle;
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := 0;
finally
MainFormHandleLock.Release;
end;
end;
procedure PostMessageToMainForm(...);
begin
MainFormHandleLock.Acquire;
try
if MainFormHandle <> 0 then
PostMessage(MainFormHandle, ...)
finally
MainFormHandleLock.Release;
end;
end;
You also need to create and destroy the critical section, but I assume that you know how to do that.
In your main form you override CreateWnd and DestroyWnd and arrange that they call RegisterMainFormHandle and UnregisterMainFormHandle.
Then you can call PostMessageToMainForm from any thread at any time.
Of course, if the main form's window is recreated then you'll lose some messages. Which sounds like it could be a problem. Using AllocateHwnd to have a window whose lifetime you control is usually a better option than using the main form's window like this.
Make some global variable flag = false at the beginning.
Make your mainform turn it to true.
Check that flag to see if main form was already initialised or not yet.
You can make it from such places as mainform's OnActivate event or overridden TMainForm.Loaded method
Similarly when your application would be terminating and the mainform would get hidden (and later even destroyed) - you would reset the flag back to false

Delphi disable form while loading

In my application, I have a main form, with the ability to load some images in database.
While images is loading, I want to show a form, with the progress indicator (with bsNone border style).
But, if I show with form with ShowModal, execution of main form is stopped, so I can't to that.
If I call Show, user have access to all other form components, and it can be dangerous, while photo is not loaded completely.
I need to get the way, to disable everything on main form, while loading isn't completed.
Please, advice me, how it is possible.
Set the MainForm as the PopupParent for the progress form so that the MainForm can never appear on top of the progress form. Then simply set MainForm.Enabled := False while the progress form is open and set MainForm.Enabled := True when the progress form is closed.
procedure TMainForm.ShowProgressForm;
begin
with TProgressForm.Create(nil) do
begin
PopupParent := Self;
OnClose := ProgressFormClose;
Show;
end;
Enabled := False;
end;
procedure TMainForm.ProgressFormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
Enabled := True;
end;
This simulates howShowModal() behaves to the user (the MainForm is not user-interactive while the progress form is open), but without blocking the code.
First of all, the direct answer to your question.
I need to get the way, to disable everything on main form.
Set MainForm.Enabled to False to disable the window associated with the main form. And to re-enable set it to True instead.
Your fundamental problem however, is that you are executing long running tasks in the GUI thread. That's always a bad idea and the way out is to execute those long running tasks in a separate thread.
Once you move the long running tasks to a separate thread then you will find that ShowModal is exactly what you need to show your progress form.
As I explained in my other answer, putting the long running task into a thread other than the GUI thread is the ideal solution. The GUI thread should handle short running tasks so that it is always able to service the message queue in a timely fashion.
However, if you already have code that assumes that the long running task runs on the GUI thread, you may prefer to take a more expedient approach and postpone the re-factoring to threaded code. In which case, in my view, it is still better to use ShowModal to display your progress form.
But in order to make that work, you need to find a let the long running task execute inside the ShowModal call. You can do that as follows:
Before you call ShowModal, pass the task to the form. For example, pass a TProc to the constructor of the progress form.
Override the progress form's Activate method. This will get executed just before the ShowModal function starts its modal message loop. In the implementation of Activate, post a message to the form.
When the form handles that message, invoke the task that was passed to the constructor.
Obviously you'll need to call ProcessMessages in your long running task, in order to keep the main GUI thread message queue serviced. Clearly you must already be doing that.
Set the PopupParent of child form = ParentForm
procedure TParentForm.Button1Click(Sender: TObject);
begin
ParentForm.Enabled:=False;
with Tform1.create(nil) do show;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ParentForm.Enabled := true;
form1.free;
end;
You may want to use DisableTaskWindows and EnableTaskWindows functions.
If you use simple ParentForm.Enabled := False for the parent form, you can still access all other forms, like the main form if it differs from ParentForm. It is obviously still dangerous.
Here is short sample:
interface
uses
Vcl.Forms, Winapi.Windows, ...;
type
TPleaseWait = class(TObject)
private
fWindowList: TTaskWindowList;
fActiveWindow: HWND;
fDialog: TPleaseWaitForm;
public
constructor Create;
destructor Destroy; override;
end;
implementation
constructor TPleaseWait.Create;
begin
// Disable all displayed windows
fWindowList := DisableTaskWindows(0);
// Save the last active window
fActiveWindow := GetActiveWindow;
fDialog := TPleaseWaitForm.Create(nil);
fDialog.Show;
end;
destructor TPleaseWait.Destroy;
const
INVALID_HANDLE = 0;
begin
fDialog.Close;
fDialog.Free;
// All windows are enabled now
EnableTaskWindows(fWindowList);
// That helps by enabling the last one form
if (fActiveWindow <> INVALID_HANDLE) and IsWindow(fActiveWindow) then
SetActiveWindow(fActiveWindow);
inherited;
end;
end.

accessing components of frame1 with frame2, with frame2 being on frame1, in delphi, frames are created dynamically

Originally the question had to be how to access the components in the first place, however I somehow managed to figure it out. I am just learning Delphi so I am prone to dumb and obvious questions. I am also at a stage when I'm not actually writing anything useful, but just messing with random stuff to see how it works and maybe learn something.
Wall of text incoming, i want to explain what i am exploring at the moment...
Basically i have a form1 with a button1, pressing it creates a frame2, that frame2 has a button2, pressing button2 creates a frame3 within frame2 (it being frame3's parent and owner). Each frame has an another freeandnil-button. Upon pressing each button1/2/3, it gets disabled to prevent creating multiple instances. My original problem was that after using freeandnil-button, I couldnt access the button on the previous frame (it worked fine for forms, form1.button1.enabled:=true works fine from within frame2) that got disabled in order to re-enable it (frame2.button1.enabled:=true from within frame3 creates an access violation, I think).
Suppose I write something in the future that requires such communication? So I added an editbox to each frame, with a button on the other to change the editbox text, this is my current working solution:
procedure TFrame2.Button3Click(Sender: TObject);
var i,z:integer;
begin
for i := 0 to ComponentCount - 1 do
if components[i] is tframe3 then
for z := 0 to (components[i] as tframe3).ComponentCount - 1 do
if (components[i] as tframe3).Components[z] is TEdit then
((components[i] as tframe3).Components[z] as TEdit).Text:='ping';
end;
and
procedure TFrame3.Button3Click(Sender: TObject);
var i:integer;
begin
for i := 0 to parent.ComponentCount-1 do
if parent.components[i] is TEdit then
(parent.Components[i] as TEdit).Text:='pong';
end;
If I have a bunch of editboxes or whatever the hell, I suppose I could use Tag property to identify them, however, this much component counting and passing something AS something doesn't really look right or efficient enough to me.
My questions at the moment are: can it be done in a better way? and can someone provide the reasoning why cant I access "parent-frame" components from a "child-frame" in a dumb way (ie: frame2.button1.enabled:=true from within frame3)?
A couple of points:
Controls/Components are usually set to be owned by the form/frame that controls their lifetime and parented to the control that shows them. So when you create a TEdit to sit on a TPanel on a TForm, you would set TForm as the owner and TPanel as the parent of the TEdit. With frames you do the same: the frame is the owner of all controls on it, controls are parented to whatever container on the frame (can be the frame itself) is holding and and is thus reponsible for showing (painting) it.
When you iterate over the children of a control, iterating over Components uses the Owner relation; iterating over Controls uses the Parent relation. So your code above would already do a lot less if it were to iterate over the Controls.
Of course you can reference other controls in a "dumb" way, but you will have to provide for the access method. If you want others (not just child frames) you will at the very least have to declare a method to retrieve that control. Many ways to do this. One is to "ask" the form when you need it (using a method on the form), or have the form set a property on the frame when it is created...
Avoid accessing form's and frame's published properties. They are mainly there for Delphi's streaming system. When you tie your application together using your approach described above it can very soon become one incredibly tangled mess...
Example coming up (sometime this evening), it will take me a bit of time to also explain and I have work to do...
The following example deals only with the ping-pong game between the frames. Freeing any form or frame from one of its own event handlers is not such a good idea. Use the Release method for that as it prevents the form/frame from processing messages after is was freed. (Plenty of questions about this on SO by the way). Also, when you do release a frame from one of its own buttons, you will need to take care that the frame that created it has a chance to nil the references it may have held to that frame otherwise you are setting yourself up for some interesting to debug mayhem. Look into "Notification" and "NotifyControls" and the automatic notification by forms and frames that is sent to their owner/parent so these can remove the control from their components/controls collection. In your example, if you were to release Frame3 from its own "FreeAndNil" button's OnClick event handler, you would have to make sure that the Frame2 responds to the deletion (I think) notification message and nil's any references it holds to Frame3 (besides the ones that will already be cleared automatically in the components/controls collections).
Now, the ping pong game. There are a couple of ways to go about this.
Method 1
The first way is what you already tried with a loop over the components of the other frame. While it certainly is a way to avoid having to "use" the other frame, it is cumbersome and not very concise. Plus when you get more controls on your forms/frames, you will have to add a check on the name to know that you have the correct TEdit. And then you might just as well use the name directly, especially as one frame already has the other frame in its uses clause because it is creating it.
// PingFrame (your Frame2)
uses
...
Pong_fr;
type
TPingFrame = class(TFrame)
...
procedure CreateChildBtnClick(Sender: TObject);
procedure PingPongBtnClick(Sender: TObject);
private
FPong: TPongFrame; // This is the "extra" reference you need to nil when
// freeing the TPongFrame from one of its own event handlers.
...
end;
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
end;
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Edit1.Text := 'Ping';
end;
And on the other end:
// PongFrame (your Frame3)
type
TPongFrame = class(TFrame)
...
procedure PingPongBtnClick(Sender: TObject);
end;
implementation
uses
Ping_fr;
procedure TPongFrame.PingPong1BtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Edit1.Text := 'Pong called';
end;
This method seems all nice and dandy, but it has drawbacks:
You need to use the unit that holds the TPingFrame (Ping_fr in this example). Not too bad I guess, but... as Ping_fr already uses Pong_fr (the unit holding TPongFrame), you can't have Ping_fr using Pong_fr in the interface section and have Pong_fr using Ping_fr in the interface section as well. Doing so would have Delphi throwing an error because of a circular references. This can be solved by moving one of the uses to the implementation section (as done in the example for the use of Ping_fr in the Pong_fr unit.
A bigger drawback is that there is now a very tight coupling between the two frames. You cannot change the TEdit in either one to another type of control (unless that happens to have a Text property as well) without having to change the code in the other unit as well. Such tight coupling is a cause of major headaches and it is good practice to try and avoid it.
Method 2
One way to decrease the coupling between the frames and allow each frame to change it controls as it sees fit is not to use the controls of another form/frame directly. To do so you declare a method on each frame that the other can call. Each method updates its own controls. And from the OnClick event handlers you no longer access the other frame's controls directly, but you call that method
type
TPingFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Ping;
end;
procedure TPingFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
And on the other end:
type
TPongFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Ping;
end;
procedure TPongFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
This is better than Method 1 as it allows both frames to change their controls without having to worryabout "outsiders" referencing them, but still has the drawback of the circular reference that can only be "solved" by moving one "use" to the implementation section.
It is a good practice to try and avoid circular references altogether instead of patching them by moving units to the implementation section's uses clause. A rule of thumb I use is:
Any form/frame may know and use the public interface of the forms/frames it instantiates (though it should avoid the controls in the "default visibility" part of that interface), but no form/frame should not have any knowledge of the specific form/frame that created it.
Method 3
One way of achieving this (there are many) it to use events just like the TButton's OnClick event.
On the TPongFrame side of things you can remove the use of Ping_fr. You no longer need it.
Then you need to declare a property and the field it references and declare a method to fire the event.
type
TPongFrame = class(TFrame)
private
FOnPingPongClicked: TNotifyEvent;
protected
procedure DoPingPongClicked;
public
property OnPingPongClicked: TNotifyEvent
read FOnPingPongClicked write FOnPingPongClicked;
end;
The Ping method stays and its implementation is unchanged. The PingPongBtnClick event handler also stays, but its implementation now becomes:
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
DoPingPongClicked;
end;
procedure TPongFrame.DoPingPongClicked;
begin
if Assigned(FOnPingPongClicked) then
FOnPingPongClicked(Self);
end;
You could put the code from DoPingPongClicked straight in here, but it is a good practice to fire an event in a separate method. It avoids duplicating the exact same code if you would have an event that can be fired from multiple parts of your code. And it also allows descendants (when you get those) to override the "firing" method (you'll have to mark it virtual in the ancestor) and do something specific all the times the event is fired.
On the TPingFrame side of things you need to code a handler for the new event:
type
TPingFrame = class(TFrame)
...
protected
procedure HandleOnPingPongClicked(Sender: TObject);
Note that the signature of this handler needs to be what the TNotifyEvent specifies. And of course you need to ensure that this event handler gets called when the event fires in TPongFrame:
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
// Listen to event fired by PongFrame whenever it's PingPingBtn is clicked
FPong.OnPingPongClicked := HandleOnPingPongClicked;
end;
In the handler code you can do what you need to do. It can be general:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
begin
Edit1.Text := 'OnPingPongClicked event was fired';
end;
And of course you can also use the fact that the event passes a reference to the instance firing the event as well:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
var
Pong: TPongFrame;
begin
// This checks that Sender actually is a TPongFrame and throws an exception if not
Pong := Sender as TPongFrame;
// Use properties from the Pong instance that was passed in
Edit1.Text := 'OnPingPongClicked event was fired by ' + Pong.Name;
end;
Enjoy!

The control 'xxx' has no parent window

I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.
Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?
Thank you!
About the error
That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.
That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.
Specific to your DLL usage, possible fix
I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:
function GiveMeTheNiceFrame:HWND;
The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.
Try replacing your function with something allong the lines of:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.
There are a few important things to remember:
When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
Frames are Controls, but they are not Forms.
When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.
Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.
--jeroen
Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.
You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.
Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.
Or make the parent a parameter in the function.
I found this (CreateParams is called as part of CreateWnd):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?
I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := #TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy

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