开发者

C# Message Queueing while .wav is playing

I have a program which plays three .wav files synchronously when a button is clicked. The problem is that even if I disable the button on the first instruction of the click handler, additional mouse click events are queued and then executed when playback completes if the use clicks while sound is playing. How can I avoid this?

Here's my click handler, playSnippet_P1,2,3 play the three audio files in different orders:

void btnPlay_Click(object sender, EventArgs e)
{
    btnPlay.Enabled = false;
    this.Refresh();

    //play a snippet for the current passage
    switch (myProgram.condition)
    {
        case 1:
            playSnippet_P1();
            break;
        case 2:
            playSnippet_P2();
            break;
        case 3:
            playSnippet_P3();
            break;
        default:
            if (myProgram.debug)
            {
                MessageBox.Show("Error(frmPassage): Invalid condition set: " + myProgram.condition);
            }
            break;
    }

    //leave this phase once final passage is finished
    if (snipsPlayed >= (myProgram.snipCount_1 
                        + myProgram.snipCount_2 
                        + myProgram.snipCount_3))
    {
        myProgram.phaseController.runNextPhase();
    }

    //reset the form to show text for next passage
    if (snipsPlayed >= (getSnipCount(1) + getSnipCount(2)))
        currentPassage = 2;
    else if (snipsPlayed >= getSnipCount(1))
        currentPassage = 1;
    else
        currentPassage = 0;

    lblTitle.Text = "Passage " + randPOrder[currentPassage].ToString();

    btnPlay.Enabled = true;
}

private void playSnippet_P1()
    {
        snipsPlayed++;

        int cSnipCount = getCSnipCount();
        int snipNum = (snipsPlayed % cSnipCount);
        int subNum = getSubNum(snipsPlayed);
        if (snipNum == 0)
            snipNum = cSnipCount;

        int rPassage = randPOrder[currentPassage];

        //play "Next question is for..."
        JE_SP.playSound(Application.StartupPath
            + "\\res\\audio\\next\\Next" + subNum.ToString()
            + ".wav", true);

        //play snippet
        JE_SP.playSound(Application.StartupPath
            + "\\res\\audio\\passages\\Snip" + rPassage.ToString()
            + "-" + snipNum.ToString()
            + ".wav", true);

        //play question
        JE_SP.playSound(Application.StartupPath
            + "\\res\\audio\\passages\\Q" + rPassage.ToString()
            + "-" + snipNum.ToString()
            + ".wav", true);

        string[] writeMe = 
       开发者_开发问答     {
                rPassage.ToString(), 
                "\\res\\audio\\passages\\Snip" + rPassage.ToString()
                    + "-" + snipNum.ToString(), 
                "\\res\\audio\\passages\\Q" + rPassage.ToString()
                    + "-" + snipNum.ToString(), 
                subNum.ToString(), 
                myProgram.condition.ToString()
            };

        JE_Log.logData(writeMe, "\t", myProgram.groupFile);
    }


A simpler repro of this behavior:

    private void button1_Click(object sender, EventArgs e) {
        button1.Enabled = false;
        System.Threading.Thread.Sleep(1000);
        button1.Enabled = true;
    }

The problem is that the mouse clicks that are recorded while the UI thread is busy go into the message queue and are stuck there until the event handler completes. When that happens, the button is already enabled again, allowed the Click event to run again.

Fixing this is fugly and includes paying the price of running the code on a worker thread. A possible good fix is to purge the message queue and remove all the mouse messages before re-enabling the button but that's not easy in Winforms. The code is actually there but it is internal. A really pragmatic fix is one that might get me in trouble but is safe and effective :

    private void button1_Click(object sender, EventArgs e) {
        button1.Enabled = false;
        System.Threading.Thread.Sleep(1000);
        Application.DoEvents();
        if (!button1.IsDisposed) button1.Enabled = true;
    }


This is a deliberate feature of Windows. It's the same thing that lets you type into a window that's busy - your text appears once the application comes back.

Why not disable the button and play the sounds asynchronously?


I'm assuming from your description that playsnippet() blocks until the snippet is done playing, so this should be done a separate thread (you can use a BackgroundWorker class). This will avoid your GUI from getting stuck and should also solve the button clicking issue.

When the BackgroundWorker has completed, you can re-enable the button, but make sure to do this on the GUI thread (with Control.Invoke or Control.BeginInvoke)

Good luck.


The solution I came up with is as follows:

In the mouse click handler, disable the button but do no re-enable it when playback completes. Instead start a Forms.Timer with sufficient interval to allow the messages that arrive on the disabled button to be cleared. On the first tick, re-enable the button and stop the timer.

This prevented the additional click events from doing anything because the button would still be disabled. Of course, this solution is very specific to my situation, and I understand usually the solution would be to play audio on a separate thread.

Thanks for all you suggestions!!

EDIT: Hans Passant posted a more sensible solution - see his suggestion on purging the message queue before enabling the control.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜