VCL.Зацикливание TUpDown.OnChanging при открытии модального окна в обработчике
Антон Григорьев
дата публикации 12-11-2004 14:33
Версия для печати КАТЕГОРИЯ БИБЛИОТЕКА.VCL.Зацикливание TUpDown.OnChanging при открытии модального окна в обработчике
ПРОДУКТ Delphi
ПЛАТФОРМА
Прежде чем приступить собственно к описанию проблемы, хочу поблагодарить Никитоса, автора вопроса КС №26206, который обнаружил этот эффект. В данной статье проблема описана более подробно, и, кроме того, исправлены некоторые неточности в моём ответе на указанный вопрос.
Итак, создаём новый проект, на форму кладём компонент TUpDown (закладка Win32) и его событию OnClick назначаем следующий обработчик:
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
Application.MessageBox('Text','Caption',MB_OK);
end;
Теперь, если запустить программу и нажать на верхнюю кнопку UpDown1, откроется окно с сообщением (при нажатии на нижнюю кнопку окно не будет открываться потому, что по умолчанию у компонента TUpDown свойство Position равно нулю и свойство Min тоже равно нулю, поэтому нажатие на нижнюю кнопку не приводит к изменению значения Position, и событие OnClick не возникает; если изменить значение свойства Min или Position, то тот же эффект будет наблюдаться и при нажатии на нижнюю кнопку). Если закрыть это окно, то щелчок мыши в любом месте формы снова приведёт к срабатыванию события OnClick и открытию окна, и так — до бесконечности: любой щелчок по форме в любом её месте будет снова и снова приводить к появлению сообщения. Эффект наблюдается и в том случае, когда вместо стандартного сообщения в обработчике показывается любая другая модальная форма. Кроме того, тот же эффект будет, и если использовать события OnChanging или OnChangingEx вместо OnClick, но мы далее для определённости будем говорить только об OnClick.
Если этот код пройти по шагам в отладчике, то никакого зацикливания не возникает: OnClick вызывается один раз, любое последующее нажатие кнопки мыши на форме не приводит ни к каким необычным результатам.
Причина этой проблемы — в том, как VCL обрабатывает сообщения, которые система помещает в очередь. При нажатии на кнопку компонента TUpDown в очередь сообщений помещаются два сообщения: WM_LButtonDown и WM_Notify. Компонент TUpDown по умолчанию имеет стиль csCaptureMouse — это означает, что при обработке WM_LButtonDown VCL захватывает мышь в монопольное использование для данного компонента.
Примечание: монопольное использование мыши означает, что любые сообщения, связанные с мышью, будут поступать захватившему мышь окну, даже если курсор мыши в это время находится за пределами данного компонента. Примером захвата мыши может служить любая кнопка: нажмите кнопку мыши над любой кнопкой на экране и, не отпуская кнопки мыши, начните перемещать курсор. Когда курсор будет выходить за пределы кнопки, она будет отжиматься, находить на неё — снова нажиматься. Теперь отведите курсор за пределы кнопки, отпустите кнопку мыши и снова подведите его к кнопке. Кнопка не нажмётся. Это происходит потому, что пока кнопка мыши удерживается, мышь захвачена кнопкой, и сообщение об отпускании кнопки мыши передаётся кнопке, независимо от того, над каким окном находится курсор. Это позволяет кнопке правильно реагировать на отпускание пользователем мыши, в том числе и за её пределами.
Затем начинает обрабатываться событие WM_Notify, которое уведомляет программу о том, что пользователь нажал на кнопку компонента TUpDown. Именно при обработке этого сообщения VCL вызывает событие TUpDown.OnClick, в котором открывается модальное окно. Всё это происходит очень быстро, поэтому кнопку мыши пользователь отпускает тогда, когда модальное окно уже оказалось на экране. В результате сообщение WM_LButtonUp либо попадает в очередь открывшегося диалогового окна, если мышь находилась над ним, либо вообще никуда не попадает, если мышь была вне модального окна. На время существования модального окна система "забывает" о том, что мышь захвачена для монопольного использования, но "вспоминает" об этом, как только модальное окно закрывается. Монопольное использование мыши компонентом TUpDown должно отменяться при обработке сообщения WM_LButtonUp, но оно, как было сказано выше, в очередь не попадает, поэтому после закрытия окна мышь остаётся в монопольном использовании данным компонентом. Поэтому любое нажатие кнопки мыши воспринимается системой как относящееся к UpDown1, и снова приводит к помещению в очередь сообщений WM_LButtonDown и WM_Notify, которые обрабатываются описанным выше образом. Так получается порочный круг, из которого при нормальной работе программы нет выхода. Этот круг может быть разорван, например, отладчиком, который отменяет монопольное использование мыши компонентами программы, чтобы иметь возможность работать.
В этой проблеме виновата VCL, которая зачем-то назначает компоненту TUpDown стиль csCaptureMouse. Данный компонент реализуется не средствами VCL, это — стандартное окно системного класса UPDOWN_CLASS, а класс TUpDown является только оболочкой для него. Поэтому все необходимые перехваты мыши выполняются самой системой, VCL нет нужды в это вмешиваться.
Эффект проверялся в Delphi версий 3, 4, 5, 6 и 7, присутствует везде.
Типовые решения
Чтобы избавиться от проблемы, нужно убрать csCaptureMouse из списка стилей компонента. Делается это так:
UpDown1.ControlStyle:=UpDown1.ControlStyle-[csCaptureMouse];
Этот код достаточно выполнить один раз (например, в FormCreate), и проблемы с зацикливанием исчезнут.
Смотрите также материалы по темам:
Обсуждение материала [ 18-11-2004 11:17 ] 5 сообщений
Комментариев нет:
Отправить комментарий