2010-12-08 45 views
2

J'ai un programme qui lit trois fichiers .wav de manière synchrone lorsqu'un bouton est cliqué. Le problème est que même si je désactive le bouton sur la première instruction du gestionnaire de clic, des événements de clic de souris supplémentaires sont mis en file d'attente et exécutés lorsque la lecture se termine si l'utilisateur clique pendant que le son est en cours de lecture. Comment puis-je éviter ça?C# Message file d'attente pendant que .wav est en cours de lecture

Voici mon gestionnaire de clic, playSnippet_P1,2,3 jouer les trois fichiers audio dans des ordres différents:

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); 
    } 
+0

Vous allez devoir exécuter le code qui joue l'audio sur un autre thread, car l'interface utilisateur va être complètement ne répond pas dans le gestionnaire de clic, mise en file d'attente des événements à distribuer lorsque le contrôle revient à la boucle principale Winforms. – cdhowie

+0

pouvez-vous afficher le code qui lit un extrait? – BrokenGlass

+0

Vous voulez éviter cela - mais vous n'avez pas dit ce que vous voulez ... –

Répondre

1

Une repro plus simple de ce comportement:

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

Le problème est que les clics de souris qui sont enregistrées alors que le thread d'interface utilisateur est occupé aller dans la file d'attente des messages et y sont bloqués jusqu'à ce que le gestionnaire d'événements est terminé. Lorsque cela se produit, le bouton est déjà activé à nouveau, a permis à l'événement Click de s'exécuter à nouveau.

La réparation est fugue et inclut le paiement du prix d'exécution du code sur un thread de travail. Une solution possible est de purger la file d'attente des messages et de supprimer tous les messages de la souris avant de réactiver le bouton, mais ce n'est pas facile dans Winforms. Le code est réellement là mais il est interne. est celui qui pourrait me causer des ennuis, mais est sûr et efficace Une solution vraiment pragmatique:

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

Je pense que cela pourrait être mieux que ce que j'ai imaginé. Merci pour la suggestion que je vais essayer maintenant. EDIT: Je suppose qu'il peut y avoir un certain danger en appelant DoEvents() dans des applications plus sophistiquées qui entraîneront un état incohérent. Mais je pense que ça devrait convenir à mon programme à cause de sa simplicité - il y a peu de choses qui peuvent arriver pendant la lecture du son. – JeffE

+0

Non, c'est complètement sûr. Voyant que cela nécessite de grokking ce que DoEvents fait. Le test IsDisposed est le bit crucial. –

+0

Cela fonctionne et semble plus raisonnable que ce que je suis venu avec. Merci pour l'aide. Si c'est complètement sûr pourquoi cela vous causerait des ennuis? Qu'est-ce que vous entendez par là? – JeffE

1

Ceci est une caractéristique délibérée de Windows. C'est la même chose qui vous permet de taper dans une fenêtre occupée - votre texte apparaît une fois que l'application est revenue. Pourquoi ne pas désactiver le bouton et lire les sons de manière asynchrone?

+0

Je désactive le bouton, mais je ne veux pas jouer les sons de manière asynchrone, car il est important que rien ne se passe pendant la lecture audio. Connaissez-vous un moyen de joindre des données à un événement ou de dire à la file d'attente de messages de ne rien accepter d'un contrôle de formulaire spécifique? – JeffE

+0

Vous pouvez [attacher un filtre de message] (http://msdn.microsoft.com/en-us/library/system.windows.forms.application.addmessagefilter.aspx) et empêcher les messages d'arriver au formulaire. Pourquoi ne voulez-vous pas rendre l'interface graphique active pendant la lecture du son? –

+0

Il s'agit d'un programme très simple (un bouton), et dans ce contexte, il serait préférable de forcer l'utilisateur à attendre la fin de la lecture. Le filtre de message semble très prometteur! Je vais revenir à vous dans un peu après que je l'ai essayé – JeffE

1

Je suppose de votre description que playsnippet() bloque jusqu'à ce que l'extrait ait fini de jouer, donc ceci devrait être fait séparément (vous pouvez utiliser une classe BackgroundWorker). Cela évitera à votre interface graphique de rester coincé et devrait également résoudre le problème de cliquer sur le bouton.

Lorsque le BackgroundWorker est terminée, vous pouvez réactiver le bouton, mais assurez-vous de le faire sur le fil GUI (avec Control.Invoke ou Control.BeginInvoke)

Bonne chance.

+0

Le programme est mono-thread pour que rien ne bloque. J'essaie de ne pas faire de multi-threading si possible. Le seul problème est la mise en file d'attente des messages pendant la lecture audio. – JeffE

0

La solution que je suis venu avec est comme suit:

Dans le gestionnaire de clic de souris, désactivez le bouton mais ne pas réactiver lorsque la lecture est terminée. Au lieu de cela, lancez un Forms.Timer avec un intervalle suffisant pour permettre aux messages qui arrivent sur le bouton désactivé d'être effacés. Sur la première case, réactivez le bouton et arrêtez la minuterie.

Cela a empêché les événements de clic supplémentaires de faire quoi que ce soit car le bouton serait toujours désactivé. Bien sûr, cette solution est très spécifique à ma situation, et je comprends généralement que la solution serait de jouer de l'audio sur un fil séparé.

Merci pour toutes vos suggestions !!

EDIT: Hans Passant a publié une solution plus sensée - voir sa suggestion sur la purge de la file d'attente des messages avant d'activer le contrôle.