Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 497
CCM Avatar asked Oct 27 '16 02:10

CCM


1 Answers

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.

like image 113
quasoft Avatar answered Sep 28 '22 23:09

quasoft