Антон Григорьев дата публикации 11-06-1999 00:00 Окно с изменяемой степенью прозрачности.
Прозрачность окна реализована на основе алгоритма смешения цветов.
На рисунке показано, как именно выглядит "полупрозрачное" окно.
Здесь приведено только общее описание проекта, предоставленное автором.
Исходный код очень подробно и доходчиво откомментирован.
Не очень здорово выглядит перемещение окна, но это уже поле деятельности для
оптимизации проекта.
Скачивайте исходный код и попробуйте сделать лучше.
Прежде всего, хочу предупредить, что этот текст рассчитан на людей,
знакомых с функциями WinAPI и сообщениями Windows и поэтому вряд ли
будет полезен тем, кто предпочитает ограничиваться работой на уровне
компонентов Delphi.
Теперь ещё три предупреждения: во первых, механизм перерисовки окон
в Windows не предусматривает создания полупрозрачных окон. Поэтому
приходится выискивать лазейки и действовать в обход правил Windows.
Крайне маловероятно, чтобы это могло привести к нестабильности системы,
но вот само полупрозрачное окно иногда перерисовывается не совсем
правильно. Во-вторых, кое-что в этом примере подобрано методом проб и
ошибок, и даже я сам не всегда могу объяснить, почему это работает.
В таких случаях я честно признаюсь в этом. И в-третьих, эта программа
написана только для демонстрации самого принципа создания полупрозрачных
окон и кое-что в ней можно (и иногда даже нужно) улучшить. По ходу текста
я постараюсь указать на такие места.
Внутренняя логика перерисовки окон в Windows такова, что каждый раз
перерисовывается только та его часть, которая не закрыта другими окнами и,
следовательно, видна на экране. Это ускоряет процесс обновления экрана.
Но полупрозрачное окно должно каким-то образом получать информацию о том,
что нарисовано под ним. Здесь мы вступаем в противоречие с правилами
Windows, поэтому следует прибегнуть к некоторому обману. При необходимости
перерисовать полупрозрачное окно надо это окно ненадолго убрать с экрана.
Как только все нижележащие окна будут перерисованы, надо запомнить ту
область экрана, которая будет закрыта окном, вновь вывести на экран это
окно и отрисовать его с учётом сохранённой картинки. Первая трудность на
этом пути - как узнать, что перерисовка всех окон закончилась? Windows
не считает нужным информировать программу об этом. Единственный способ,
который я знаю, - выждать определённое количество времени, надеясь, что
этого хватит. Недостатки этого способа настолько очевидны, что я даже не
хочу писать о них. Скажу только, что времени задержки 400 мс, которое
установлено в этом примере хватало на моём компьютере (почти уже музейном
экспонате, если честно). Но всегда может найтись долго перерисовывающаяся
программа, для которой не хватит даже Pentium !!!-500 со всеми возможными
ускорителями. Вторая трудность - если вдруг окно, лежащее под
полупрозрачным, обновилось, то это не приведёт сразу же к перерисовке
этого окна. Особенно некрасиво получается, когда обновлённое окне только
частично находится под полупрозрачным. Тогда видимая его часть уже
содержит обновлённое изображение, а скрытая под полупрозрачным - всё
ещё старое.
Теперь о том, как осуществить всё это на практике. Перерисовка окна
определяется обработкой двух сообщений: WM_EraseBkgnd и WM_Paint.
Я пробовал и то, и другое - оказалось, что WM_Paint лучше по трём
причинам. Во-первых, WM_EraseBkgnd почему-то может посылаться по
несколько раз, соответственно окно перерисовывается до трёх раз подряд,
а выглядит это некрасиво, особенно если вспомнить, что каждая
перерисовка сопровождается задержкой. Во-вторых, между WM_EraseBkgnd и
WM_Paint есть существенная разница: в первом случае Windows сам (сама?
само? Windows - он кто? Мужчина или женщина?) определяет, какая часть
окна должна перерисоваться, и рисовать за пределами этой части просто
не разрешает. А полупрозрачное окно, вообще говоря, обладает нетрадиционной
точкой зрения на этот вопрос, что и приводит к конфликтам, особенно
тогда, когда полупрозрачное окно частично закрыто другими окнами. Что
же касается WM_Paint, то и тут Windows, конечно же, держит всё под
контролем и тоже следит за тем, какая область окна должна быть перерисована.
Однако, к счастью для полупрозрачных окон, это всё не выливается в
прямые запреты, как в случае с WM_EraseBkgnd, здесь Windows ограничивается
только выдачей ценных указаний (ЦУ) (через BeginPaint и TPaintStruct). Ну,
а мы, естественно, пренебрегаем этими ЦУ и внаглую отрисовываем окно
целиком. И наконец, в-третьих, Windows зачем-то генерирует WM_EraseBkgnd
после выполнения ShowWindow, поэтому попытка спрятать окно при обработке
этого сообщения приведёт к бесконечной рекурсии. Оно конечно, можно вставить
в программу переменную-семафор, с помощью которой будет блокироваться
повторный вызов ShowWindow, но зачем писать код, без которого можно
обойтись? Надеюсь, я убедил вас, что WM_Paint лучше, чем WM_EraseBkgnd.
Впрочем, пренебрегать WM_EraseBkgnd тоже не стоит. Дело в том, что во
время запуска программы этому процессу, видимо, присваивается повышенный
приоритет (а может, и не повышенный приоритет, а я даже и не знаю, что,
но все остальные программы почти останавливаются на это время). Вот это
самое не знаю что приводит к тому, что наше окно начинает рисоваться,
Windows посылает WM_EraseBkgnd, стандартная процедура обработки этого
события закрашивает всю клиентскую часть окна красивым серым цветом
(как обычно), затем обрабатывается WM_Paint, в котором окно прячется с
экрана, после чего остальные окна должны быстро перерисоваться и, когда
пройдёт заданное время, программа посмотрит, что там нарисовано, начнёт
наложение светофильтра... Опаньки! Окна-то не успели перерисоваться!
Программа увидит своё собственное окно, которое не успели стереть. Это
не входит в наши планы, поэтому, чтобы не увеличивать время ожидания,
нужно заблокировать вызов стандартного обработчика WM_EraseBkgnd. Это,
естественно, никак не отразится на скорости перерисовки остальных окон,
но ведь и само окно ничего не нарисует на экране, и после ожидания
программа увидит то, что надо. Это я как мог изложил теорию процесса,
а как это реализовано на практике - читайте комментарии в тексте самих
процедур.
И последний штрих: при перемещении окна Windows, во избежание ненужных
действий, не запускает механизм перерисовки, а просто переносит изображение
с одного места на другое. Для полупрозрачных окон это недопустимо, изображение
должно обновляться при каждом переносе. Для этого надо отслеживать
сообщение WM_Move, которое возникает в таких случаях. И, соответственно,
запускать перерисовку окна. Если WM_Move вам почему-то не подходит
(не знаю, почему бы это могло быть, ну да ладно; может, у вас аллергия
на него или он оскорбляет ваши убеждения), вы можете использовать
WM_WindowPosChanged. Я пробовал и то, и другое и не заметил разницы.
Вот если бы мы использовали не WM_Paint, а WM_EraseBkgnd, то эти два
способа были бы неравноценны, но мы договорились использовать WM_Paint,
поэтому не будем на этом останавливаться.
Проблема заключается в том, что в
некоторых версиях системы при перемещении окна не рисуется рамка, а
каждый раз происходит перерисовка всего окна целиком. То же самое
происходит и при изменении размеров окна. Так происходит, например, в
Win NT и в Win95 при установленном MS Plus! Ключ к решению проблемы
лежит в обработке сообщений WM_EnterSizeMove и WM_ExitSizeMove. Нужно
завести переменную логического типа, которая будет изменяться при начале
перетаскивания с False на True и наоборот при его завершении.
Соответственно обработчик WM_Paint должен следить за этой переменной и
не выполнять задержку. Проблема - как узнать, что должно быть под окном.
Если каждый раз запоминать не только нужную часть экрана, а весь экран
целиком, то к моменту входа в режим перетаскивания программа будет
обладать всей необходимой информацией о том, что там внизу. Теперь надо
будет только вырезать нужный кусок. Главный недостаток такого подхода -
программа должна быть, как пионер, всегда готова к началу перетаскивания
и всегда сохранять экран целиком, что приведёт к дополнительному расходу
памяти. Но, как известно, красота требует жертв.
// Примечание: в настоящей версии это исправлено
Ну вот, окно и создано. Смотрится оно вполне прилично, но всё же не
лишено некоторых недостатков. Наверное, их число можно уменьшить. Вот
сдам сессию и займусь этим вопросом поближе. А пока разрешите пожелать
вам успехов в использовании полупрозрачных окон и во всём остальном
и попрощаться.
Григорьев Антон
P.S. Этот пример распространяется абсолютно свободно и может быть без
каких-либо ограничений использован для создания как коммерческих, так
и некоммерческих приложений. Если вдруг этот пример поможет кому-нибудь
написать что-то хорошее, то я попросил бы этого человека по возможности
написать что-нибудь про меня в окне About. (Вот такой я тщеславный. А что
делать?). Впрочем, если такой возможности не будет, то и не надо.
P.P.S. Привет всем землякам-черноголовцам, если такие вдруг найдутся. }
К материалу прилагаются файлы:
[TForm] [Окна, оконные сообщения] [GDI, рисование на канве] [Фоновые рисунки, прозрачность, скины ] [WM_PAINT] [WM_MOVE] [WM_ENTERSIZEMOVE] [WM_EXITSIZEMOVE] [WM_WINDOWPOSCHANGED]
Обсуждение материала [ 06-11-2006 12:35 ] 6 сообщений |