why does tmediaplayer delay a caption changing on a tpanel? - delphi

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;

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

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);
......

How to avoid the focus on Delphi application?

I need develop an application (Delphi) that never receive the focus, I use the DLL to display the video on second monitor (I found in Torrys Delphi - Dr.SAGURA Media Player v.1.0) that receive the focus every time play the video, how avoid this ?
I try :
procedure TForm.WMActivate(var Msg: TWMActivate);
begin
WA_ACTIVE :
begin
Msg.Result := 0;// cancel focus
end;
end;
Unsuccessful !
Thanks
Jean Alysson
In your project DPR set Application.MainFormOnTaskBar := False.
Override the form's OnShow and OnActivate events and add this to both of them: ShowWindow(Application.Handle, SW_HIDE);
Override your form's CreateParams procedure and add WS_EX_NOACTIVATE to Params.ExStyle.
The first two items hide the application from the taskbar and the Alt+Tab list and the last keeps it from gaining focus when it's shown and when clicking on it.
What's the intention behind not be focusable? You want to avoid the user from closing/minimize/maximize the window?
Maybe your need can be acomplished understanding the real problem.
Maybe having a sevice instead of a regular application can make the trick.

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.

Under what conditions will a TForm fire OnResize on show?

As an extension of this question:
TForm.OnResize is sometimes fired before a form is first shown, but not always. For example, if BorderStyle is either bsDialog or bsNone, then OnResize will not fire. For all other BorderStyle values (and with all other properties at their defaults), OnResize does fire.
Are there other things that affect whether OnResize will fire before the form is shown? For example, other properties, or combinations of properties, that can affect this?
The OnResize event is a result of the ShowWindow API function sending a WM_SIZE message to the window. That bears repeating: the message is coming from Windows, not from Delphi. It's a Windows function (ShowWindow) that's (sometimes) sending the message that triggers the event -- so the VCL source code is not really helpful in this case.
Bonus points for definitive answers based on documented ShowWindow / WM_SIZE behavior, e.g. references to MSDN documentation or Petzold books.
Maybe it even depend on user's display settings or desktop theme or Windows version. If OnResize were giving me problems like this, I would build my program to always expect it and handle it in any situation, no matter what I think to be the cause.
I believe that OnResize will fire when an event dispatch a message
saying that form size (left, bottom, width, height) will be modified.
Since you already discovered which message fires that event, you need
now trace where the message is sent in the vcl.
Look at the vcl source code to see if you can spot those operations.
Edit: let's go low level. Forms in windows (grossly talking) have what
is called "window class" (it's not a class like we know it oop). All times the window class of the form is resized (and form is visible), the WM_SIZE is sent.
So it will not happen all the times the form is shown, but only the it's dimensions are changed compared with underlying window class.
As you have observed, many properties valuez change the dimensions of the form (even a few pixels).
This is a very superficial explanation, that's a ton of other details - but it's my understanding how things works "under the hood".
There's no substitute for testing. How about creating a form in code, setting the properties you're interested in and recording when the resize event is called.
If you'll excuse the ugliness of the code, here's a rough proof of concept that tests all combinations of BorderStyle and Position without explicitly coding for each one. You can add more properties and take it as far as you like. A tool like CodeSite would make the logging cleaner and easier, too.
Create an application with 2 forms. Make sure the second one isn't auto-created.
In the second form, add a property and add a little logging code to the form's Resize event:
private
FOnResizeFired: TNotifyEvent;
public
property OnResizeFired: TNotifyEvent read FOnResizeFired write FOnResizeFired;
end;
...
procedure TForm2.FormResize(Sender: TObject);
begin
if Assigned(FOnResizeFired) then
FOnResizeFired(self);
end;
In the main form, add TypInfo to the uses clause and drop a button and a memo on the form.
Add a simple procedure:
procedure TForm1.ResizeDetected(Sender: TObject);
begin
Memo1.Lines.Add(' *** Resize detected');
end;
Now add the following to the ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
lBorderStyle: TFormBorderStyle;
lBorderStyleName: string;
lPosition: TPosition;
lPositionName: string;
lForm: TForm2;
begin
Memo1.Clear;
for lBorderStyle in [low(TFormBorderStyle) .. high(TFormBorderStyle)] do
begin
for lPosition in [low(TPosition) .. high(TPosition)] do
begin
lBorderStyleName := GetEnumName(TypeInfo(TFormBorderStyle), Integer(lBorderStyle));
lPositionName := GetEnumName(TypeInfo(TPosition), Integer(lPosition));
Memo1.Lines.Add(Format('Border: %s Position: %s', [lBorderStyleName, lPositionName]));
Memo1.Lines.Add(' Creating form');
lForm := TForm2.Create(self);
try
Memo1.Lines.Add(' Form Created');
lForm.OnResizeFired := ResizeDetected;
Memo1.Lines.Add(' Setting border style');
lForm.BorderStyle := lBorderStyle;
Memo1.Lines.Add(' Setting Position');
lForm.Position := lPosition;
Memo1.Lines.Add(' Showing form');
lForm.Show;
Memo1.Lines.Add(' Form Shown');
lForm.Close;
Memo1.Lines.Add(' Form Closed');
finally
FreeAndNil(lForm);
Memo1.Lines.Add(' Form Freed');
end;
end;
end;
end;
You'll notice that resize fires when some properties are set before the form is shown, and I see that in some combinations, resize seems to fire twice when the form is shown. Interesting.

Resources