#c# #.net #wav
#c# #.net #wav
Вопрос:
У меня есть программа, которая воспроизводит три файла .wav синхронно при нажатии кнопки. Проблема в том, что даже если я отключу кнопку в первой инструкции обработчика щелчков мышью, дополнительные события щелчка мышью будут помещены в очередь, а затем выполнены по завершении воспроизведения, если использовать щелчки во время воспроизведения звука. Как я могу избежать этого?
Вот мой обработчик кликов, playSnippet_P1, 2, 3 воспроизводит три аудиофайла в разных порядках:
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);
}
Комментарии:
1. Вам придется запустить код, который воспроизводит аудио в другом потоке, поскольку пользовательский интерфейс будет полностью не отвечать на запросы в обработчике щелчков, ставя в очередь события, которые будут отправлены, когда управление вернется в основной цикл Winforms.
2. можете ли вы показать код, который воспроизводит фрагмент?
3. Вы хотите избежать этого, но вы не сказали, чего хотите…
4. Я пытаюсь сделать это однопоточное приложение, и чего я хочу избежать, так это постановки сообщений в очередь при воспроизведении звука в этом потоке. Я опубликовал пример метода, который воспроизводит три файла
Ответ №1:
Более простое воспроизведение этого поведения:
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
System.Threading.Thread.Sleep(1000);
button1.Enabled = true;
}
Проблема в том, что щелчки мыши, которые записываются, когда поток пользовательского интерфейса занят, попадают в очередь сообщений и застревают там до завершения работы обработчика событий. Когда это происходит, кнопка уже снова включена, что позволяет событию Click запускаться снова.
Исправить это непросто и включает в себя оплату выполнения кода в рабочем потоке. Возможным хорошим решением является очистка очереди сообщений и удаление всех сообщений мыши перед повторным включением кнопки, но в Winforms это непросто. Код на самом деле есть, но он внутренний. Действительно прагматичное исправление — это то, которое может вызвать у меня проблемы, но является безопасным и эффективным :
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
System.Threading.Thread.Sleep(1000);
Application.DoEvents();
if (!button1.IsDisposed) button1.Enabled = true;
}
Комментарии:
1. Я думаю, это может быть лучше, чем то, что я придумал. Спасибо за предложение, я попробую это прямо сейчас. РЕДАКТИРОВАТЬ: Я подозреваю, что в вызове DoEvents() в более сложных приложениях может быть некоторая опасность, что приведет к несогласованному состоянию. Но я думаю, что с моей программой все должно быть в порядке из-за ее простоты — во время воспроизведения звука может произойти всего несколько вещей.
2. Нет, это абсолютно безопасно. Для того, чтобы увидеть это, действительно требуется посмотреть, что делает DoEvents. Тест IsDisposed является решающим моментом.
3. Это работает и кажется более разумным, чем то, что я придумал. Спасибо за помощь. Если это абсолютно безопасно, почему это может вызвать у вас проблемы? Что вы имели в виду под этим?
4. Точно по той же причине, по которой вы сказали «некоторая опасность при вызове». DoEvents не совсем хорошо изучен и получил огромную плохую репутацию еще во времена VB6. Злоупотребление не было неоправданным. Многие посетители SO инстинктивно тянутся к стрелке понижения, когда видят, что она используется для решения проблемы. Пожалуйста, закройте ваш поток, прежде чем это произойдет. Большая галочка рядом с вашим любимым ответом.
Ответ №2:
Это преднамеренная особенность Windows. Это то же самое, что позволяет вводить текст в окне, которое занято — ваш текст появится, как только приложение вернется.
Почему бы не отключить кнопку и не воспроизводить звуки асинхронно?
Комментарии:
1. Я отключаю кнопку, но я не хочу воспроизводить звуки асинхронно, потому что важно, чтобы во время воспроизведения звука ничего не происходило. Знаете ли вы какой-либо способ прикрепить данные к событию или указать очереди сообщений ничего не принимать от определенного элемента управления формой?
2. Вы могли бы прикрепить фильтр сообщений и предотвратить поступление сообщений в форму. Почему вы не хотите делать графический интерфейс активным во время воспроизведения звука?
3. Это очень простая программа (одна кнопка), и в данном контексте было бы лучше заставить пользователя дождаться завершения воспроизведения. Фильтр сообщений выглядит очень многообещающе! Я свяжусь с вами через некоторое время после того, как опробую это
4. Отключение формы должно привести к этому
5. Отключение формы не сработало, поскольку сообщения все еще могли помещаться в очередь, а затем доставляться по возвращению обработчика кликов (в этот момент кнопка снова включается). Фильтр сообщений также не сработал … похоже, он никогда не вызывает мою реализацию PreFilterMessage. Я нашел решение, которое включает таймер (см. Ниже)
Ответ №3:
Из вашего описания я предполагаю, что playsnippet() блокируется до тех пор, пока фрагмент не будет воспроизведен, поэтому это должно быть сделано отдельным потоком (вы можете использовать класс BackgroundWorker). Это позволит избежать зависания вашего графического интерфейса пользователя, а также должно решить проблему с нажатием кнопки.
После завершения работы BackgroundWorker вы можете снова включить кнопку, но обязательно сделайте это в потоке GUI (с помощью Control.Вызов или управление.BeginInvoke)
Удачи.
Комментарии:
1. Программа однопоточная, поэтому ничего не блокируется. Я стараюсь по возможности не выполнять никакой многопоточности. Единственная проблема заключается в очереди сообщений во время воспроизведения аудио
Ответ №4:
Решение, которое я придумал, заключается в следующем:
В обработчике щелчков мыши отключите кнопку, но не включайте ее повторно по завершении воспроизведения. Вместо этого запустите формы.Таймер с достаточным интервалом, чтобы разрешить удаление сообщений, поступающих на отключенную кнопку. При первом тике снова включите кнопку и остановите таймер.
Это предотвратило какие-либо действия дополнительных событий нажатия, поскольку кнопка все равно была бы отключена. Конечно, это решение очень специфично для моей ситуации, и я понимаю, что обычно решением было бы воспроизводить аудио в отдельном потоке.
Спасибо за все ваши предложения!!
РЕДАКТИРОВАТЬ: Ханс Пассант опубликовал более разумное решение — смотрите Его предложение по очистке очереди сообщений перед включением элемента управления.