Форум: "Основная";
Текущий архив: 2003.02.24;
Скачать: [xml.tar.bz2];
ВнизПолупрозрачность формы Найти похожие ветки
← →
Leks (2003-02-12 17:42) [0]Как сделать форму полупрозрочной ? т е чтобы сквозь некий фоновый цвет формы просвечивали остальные окна.
← →
Pasechnik (2003-02-12 17:45) [1]alphablend у формы
← →
Андрей Сенченко (2003-02-12 17:55) [2]На мой взгляд стоит упоминания, несмотря на WinXP
Note: AlphaBlend does not work on all systems. The application must be running under Windows 2000 or better, and the machine must be a P90 or better.
← →
MAN-In-RED (2003-02-12 20:07) [3]Для всех Windows"ов:
unit Unit1;
{ Как сделать полупрозрачное окно
Прежде всего, хочу предупредить, что этот текст рассчитан на людей,
знакомых с функциями WinAPI и сообщениями Windows и поэтому вряд ли
будет полезен тем, кто предпочитает ограничиваться работой на уровне
компонентов Delphi. А тем, кто решит всё-таки прочитать это всё,
настоятельно рекомендую сначала запустить программу и немного
поиграться с окном, чтобы представлять себе некоторые особенности
его поведения, о которых я буду упоминать.
Теперь ещё три предупреждения: во-первых, механизм перерисовки окон
в Windows не предусматривает создания полупрозрачных окон. Поэтому
приходится выискивать лазейки и действовать в обход правил Windows.
Крайне маловероятно, чтобы это могло привести к нестабильности системы,
но вот само полупрозрачное окно иногда перерисовывается не совсем
правильно. Во-вторых, кое-что в этом примере подобрано методом проб и
ошибок, и даже я сам не всегда могу объяснить, почему это работает.
В таких случаях я честно признаюсь в этом. И в-третьих, эта программа
написана только для демонстрации самого принципа создания полупрозрачных
окон и кое-что в ней можно (и иногда даже нужно) улучшить. По ходу текста
я постараюсь указать на такие места.
Внутренняя логика перерисовки окон в Windows такова, что каждый раз
перерисовывается только та его часть, которая не закрыта другими окнами и,
следовательно, видна на экране. Это ускоряет процесс обновления экрана.
Но полупрозрачное окно должно каким-то образом получать информацию о том,
что нарисовано под ним. Здесь мы вступаем в противоречие с правилами
Windows, поэтому следует прибегнуть к некоторому обману. При необходимости
перерисовать полупрозрачное окно надо это окно ненадолго убрать с экрана.
Как только все нижележащие окна будут перерисованы, надо запомнить ту
область экрана, которая будет закрыта окном, вновь вывести на экран это
окно и отрисовать его с учётом сохранённой картинки. Первая трудность на
этом пути - как узнать, что перерисовка всех окон закончилась? Windows
не считает нужным информировать программу об этом. Единственный способ,
который я знаю, - выждать определённое количество времени, надеясь, что
этого хватит. Недостатки этого способа настолько очевидны, что я даже не
хочу писать о них. Скажу только, что времени задержки 400 мс, которое
установлено в этом примере хватало на моём компьютере (почти уже музейном
экспонате, если честно). Но всегда может найтись долго перерисовывающаяся
программа, для которой не хватит даже Pentium !!!-500 со всеми возможными
ускорителями. Вторая трудность - если вдруг окно, лежащее под
полупрозрачным, обновилось, то это не приведёт сразу же к перерисовке
этого окна. Особенно некрасиво получается, когда обновлённое окне только
частично находится под полупрозрачным. Тогда видимая его часть уже
содержит обновлённое изображение, а скрытая под полупрозрачным - всё
ещё старое.
← →
MAN-In-RED (2003-02-12 20:07) [4]
Теперь о том, как осуществить всё это на практике. Перерисовка окна
определяется обработкой двух сообщений: 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 поддерживает стиль окна WS_EX_TRANSPARENT.
Если верить описанию, то такое окно должно получать WM_Paint только
после того, как все нижележащие окна нарисованы. Звучит заманчиво,
но эксперименты с этим окном показывают, что оно ведёт себя, мягко
говоря, невразумительно. Мне пока не удалось получить что-либо путное
от этого окна. Будем думать дальше.
← →
MAN-In-RED (2003-02-12 20:08) [5]
И последний штрих: при перемещении окна Windows, во избежание ненужных
действий, не запускает механизм перерисовки, а просто переносит изображение
с одного места на другое. Для полупрозрачных окон это недопустимо, изображение
должно обновляться при каждом переносе. Для этого надо отслеживать
сообщение WM_Move, которое возникает в таких случаях. И, соответственно,
запускать перерисовку окна. Если WM_Move вам почему-то не подходит
(не знаю, почему бы это могло быть, ну да ладно; может, у вас аллергия
на него или он оскорбляет ваши убеждения), вы можете использовать
WM_WindowPosChanged. Я пробовал и то, и другое и не заметил разницы.
Вот если бы мы использовали не WM_Paint, а WM_EraseBkgnd, то эти два
способа были бы неравноценны, но мы договорились использовать WM_Paint,
поэтому не будем на этом останавливаться.
Но Windows не был бы (Не была бы? Не было бы? См. выше) Windows"ом, если бы
не приготовил (-а? -о?) бы ещё хотя бы один сюрприз. Дело в том, что, когда
пользователь таскает по экрану окна, они могут либо перерисовываться целиком,
либо может рисоваться только рамочка. Управляется это специальными системными
установками, которые, во-первых, могут быть разными в разных версиях и,
во-вторых, настраиваются разными утилитами вроде MS-Plus! С рамочкой всё
просто: мы её подвинули, куда надо (Или не мы. Или куда не надо. Неважно.),
окно переместилось, и начался процесс перерисовки: спрятались, подождали,
нарисовались (в смысле появились) и нарисовались (в прямом смысле). Всё
просто замечательно в том смысле, что наше окно всё это прекрасно делает.
Единственный минус - задержка перед окончательным появлением. А теперь
представьте, что будет, если таскается не рамка, а всё окно целиком. Чуть
двинули мышью, и началось: спрятались, подождали и далее по тексту. Выглядит
всё это более чем ужасно, такую издёвку над конечным пользователем может
позволить себе только Microsoft, а вот к нам требования повыше будут. Так
что напряжёмся ещё немного и сделаем что-нибудь с этим. Во-первых, нам надо
найти способ узнать, чем вызвана перерисовка окна - тем, что его куда-то
поволокли, или чем-то ещё. И, соответственно, вести себя по-разному.
Во-вторых, неплохо бы знать, что должно быть под окном. С первым всё просто:
есть сообщения WM_EnterSizeMove и WM_ExitSizeMove, которые сообщают о
начале и конце перетаскивания. А вот со вторым - проблемы. Старый
проверенный способ нам не подходит, слишком медленный, а за скорость
приходится платить, обычно - памятью. Надо запоминать не только ту область
экрана, которая нас интересует в данный момент, а думать и о будущем тоже,
то есть, проще говоря, запоминать сразу весь экран. Чтобы коварный
пользователь не застал внезапным перетаскиванием программу врасплох, она
должна всегда запоминать весь экран. Для справки - при разрешении
1024х768 точек при любом числе цветов на это уйдёт два с четвертью
мегабайта. Возникает желание не тратиться так каждый раз, а начинать
запоминать весь экран только после начала перетаскивания, а до этого
довольствоваться лишь необходимым куском. Но представьте себе такую
ситуацию: пользователь пытается зацепить окно и потащить его, а оно сразу
же после нажатия кнопки мыши исчезает на полсекунды, чтобы запомнить, что
же там под ним нарисовано. Скорее всего, непредупреждённый пользователь
сразу же отпустит мышь. Окно появится, он снова попытается зацепить его,
окно снова исчезнет. Догадайтесь с трёх раз, кого из родственников автора
программы он вспомнит после этого. Кроме того, если установлен режим
перетаскивания не всего окна, а только рамки, то на это время перерисовка
всех остальных окон, и даже если наша программа будет ждать не 400
миллисекунд, а 49.7 дней - ничего под окном не перерисуется. (Почему
49.7 дней? Именно за такое время внутренний счётчик миллисекунд системы
переполняется, и Windows 95 благополучно виснет. Windows NT, правда,
покрепче будет, но и более длительное ожидание нам не поможет.) В общем,
если мы хотим, чтобы окошко нормально вело себя при перетаскивании, мы
должны постоянно запоминать весь экран. Исключения возможны только если
применить какой-нибудь алгоритм, по которому во время перемещения окно
отображается по-другому. Самый грубый способ - сделать его на это время
просто непрозрачным, более тонкий - использовать в качестве фона при
перемещении тот кусок экрана, над которым окно находилось до начала
перемещения. Будет не совсем то, что надо, но и не слишком ужасно.
Непонятно только, что делать, если пользователь решит увеличить размеры
окна. Так что этот способ подходит только для окон с постоянным размером.
Но я не буду больше отвлекаться, потому что придумывание таких методов -
работа для дизайнера, а не для программиста. А мы будем использовать
самый универсальный способ - постоянно запоминать весь экран.
← →
MAN-In-RED (2003-02-12 20:09) [6]
Если вы уже посмотрели мой код, то, наверное, заметили, что его можно
существенно ускорить без потери функциональности. Речь идёт опять-таки
о перетаскивании окон в режиме перетаскивания окна целиком. Действительно,
каждый раз перед перерисовкой на вырезанную часть запомненного ранее
экрана накладывается светофильтр. На это наложение уходит время. А
ведь можно было один раз, сразу после запоминания экрана, наложить на
всю эту картинку светофильтр и при перетаскивании не тратить на это
время, а просто вырезать нужный кусок. Но ведь мы договорились, что
пишем универсальную программу. Этот же метод приемлем только тогда,
когда мы накладываем однородный светофильтр. А если мы захотим применить
какой-то особый эффект? (Об эффектах см. подробнее в комментариях в тексте
программы.) Тогда цвет и прозрачность каждой точки будет зависеть от её
координаты, и мы не сможем один раз применить этот эффект так, чтобы
результат потом подходил для любого положения окна. Тем же, кто
намеревается ограничится однородным светофильтром, я настоятельно рекомендую
переделать мой код. }
← →
MAN-In-RED (2003-02-12 20:10) [7]
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Spin;
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
ColorDialog1: TColorDialog;
SpinEdit1: TSpinEdit;
Label2: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure SpinEdit1Change(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
BM:TBitmap; { картинка, в которой хранится изображение экрана }
BM2:TBitmap; { картинка, в которой хранится фон окна }
Moving:Boolean; { а эта переменная равна True, если окно в данный момент
перетаскивается пользователем }
procedure WMEraseBkgnd(var Msg:TWMEraseBkgnd);message WM_EraseBkgnd;
procedure WMPaint(var Msg:TWMPaint);message WM_Paint;
procedure WMMove(var Msg:TMessage);message WM_Move; // Если вместо WM_Move поставить
// WM_WindowPosChanged, ничего
// не изменится.
procedure WMEnterSizeMove(var Msg:TMessage);message WM_EnterSizeMove;
procedure WMExitSizeMove(var Msg:TMessage);message WM_ExitSizeMove;
public
{ Public declarations }
end;
var
Form1: TForm1;
const DelayTime:Integer=400; { Время задержки в миллисекундах }
var
TranspColor:TColor=clBlack;
Transparency:Integer=40;
implementation
{$R *.DFM}
type PRGBArray=^TRGBArray;
TRGBArray=array[0..1000000] of TRGBTriple;
{ Вместо 1000000 может быть любое число, даже 0, только тогда придётся
отключить проверку диапазона. Экземпляры массивов этого типа всё равно
не создаются }
procedure Delay(DelayTime:Integer);
var TicksNow:Integer;
begin
TicksNow:=GetTickCount;
repeat
Application.ProcessMessages
until GetTickCount-TicksNow>=DelayTime
end;
{ Эта процедура приостанавливает линейное выполнение программы на заданное число
миллисекунд, не прерывая, тем не менее, фоновых процессов. }
← →
MAN-In-RED (2003-02-12 20:10) [8]
procedure TForm1.WMEraseBkgnd;
begin
Msg.Result:=1
{ В общем-то, мы ничего не сделали в ответ на это сообщение, но
зато послали отчёт, что всё сделано в лучшем виде. Если это
будут читать маленькие дети, то пусть они помнят, что обманывать
всё равно нехорошо, хоть я и показываю плохой пример. }
end;
procedure TForm1.WMPaint;
var DC:HDC; // Контекст устройства. Он нам понадобится целых два раза
PS:TPaintStruct; // А сюда будут записаны те самые ЦУ, которые мы проигнорируем.
CW,CH,CX,CY:Integer; // размеры клиентской части окна
SL:PRGBArray; // Указатель на строку пикселей
X,Y:Integer; // Нужно для организации циклов
begin
CW:=ClientWidth;
CH:=ClientHeight; // На всякий случай запоминаем все необходимые размеры.
CX:=ClientOrigin.X; // Может быть, после того, как окно будет спрятано, они изменятся.
CY:=ClientOrigin.Y; // А может, и нет. Проверьте сами, если не лень
if not Moving then // Этот кусок кода не стоит выполнять, когда окно
begin // перетаскивается пользователем.
ShowWindow(Handle,SW_Hide); // Прячем окно. Кстати, я пробовал не прятать окно,
// а использовать SetWindowRgn, чтобы вырезать его
// клиентскую часть. Почему-то не сработало.
// Что касается этого механизма, то он не будет
// работать с окнами типа MDIChild, потому что
// такие окна нельзя спрятать.
SetActiveWindow(0); // Эта строка заслуживает более подробного комментария.
// Когда наше окно прячется, будучи активным, то активным
// становится другое окно. Цвет его заголовка меняется, и
// в результате не выполнена главная задача: сделать так,
// чтобы на экране было всё то же самое, но без нашего окна.
// Поэтому делаем все окна неактивными, и получаем нужный
// результат. Если же наше окно было неактивным, то эта
// строчка никому не мешает (сам не знаю, почему, но факт!)
Delay(400); // Ждём и молимся, чтобы все окна успели перерисоваться!
DC:=GetDC(0); // Получаем контекст рабочего стола
BitBlt(BM.Canvas.Handle,0,0,BM.Width,BM.Height,DC,0,0,SrcCopy);
ReleaseDC(0,DC); // Больше этот контекст нам не нужен, о чём мы и сообщаем
end;
// Начиная с этого места, код выполняется при любом значении Moving
← →
MAN-In-RED (2003-02-12 20:11) [9]
BM2.Width:=CW+1; // Ну, это даже не интересно рассказывать...
BM2.Height:=CH+1; // Просто готовим картинку к тому, что сейчас будем рисовать
BM2.PixelFormat:=pf24bit;
BM2.Canvas.Draw(-CX,-CY,BM);
for Y:=0 to CH do // А в этих циклах на записанный нами кусок экрана
begin // Накладывается светофильтр
SL:=BM2.ScanLine[Y];
for X:=0 to CW do
begin
SL[X].rgbtRed:=(Transparency*SL[X].rgbtRed+(100-Transparency)*GetRValue(TranspColor)) div 100;
SL[X].rgbtGreen:=(Transparency*SL[X].rgbtGreen+(100-Transparency)*GetGValue(TranspColor)) div 100;
SL[X].rgbtBlue:=(Transparency*SL[X].rgbtBlue+(100-Transparency)*GetBValue(TranspColor)) div 100
{ Предыдущие три строчки - реализация алгоритма смешения цветов
Pr:=(Pa*Wa+Pb*Wb)/(Wa+Wb), где Pr - результирующий цвет,
Pa и Pb - исходные цвета, Wa и Wb - веса этих цветов.
У нас в качестве Pa берётся цвет пикселя скопированной с экрана картинки,
В качестве Pb - заранее заданный цвет TranspColor, Wa=Transparency,
Wb=100-Transparency. Очевидно, что эту операцию необходимо выполнить для
каждого из основных цветов в отдельности.
Здесь открывается широкое поле для деятельности. Можно, например, сделать
Transparency не постоянным, а зависящим от координаты - получится градиентная
прозрачность. Или можно в качестве Pb взять не фиксированный цвет, а цвет
пикселя другой картинки - получится окно, фоном которого служит
полупрозрачная картинка. В конце концов, можно изменить алгоритм смешения
цветов, и тогда откроются новые возможности.
Кстати, вот пример градиентной прозрачности:
SL[X].rgbtRed:=((CH-Y)*SL[X].rgbtRed+Y*GetRValue(TranspColor)) div CH;
SL[X].rgbtGreen:=((CH-Y)*SL[X].rgbtGreen+Y*GetGValue(TranspColor)) div CH;
SL[X].rgbtBlue:=((CH-Y)*SL[X].rgbtBlue+Y*GetBValue(TranspColor)) div CH;
Хочу добавить, что это смотрится нормально только в режимах True Color.
High Color для этого недостаточно. А в режимах, худших, чем High Color,
полупрозрачные окна выглядят страшнее, чем ядерная война.
}
end
end;
ShowWindow(Handle,SW_Show); // Снова показываем окно
DC:=BeginPaint(Handle,PS); // Получаем разрешение начать перерисовку вместе с ЦУ.
// Кстати, если разобраться с исходниками стандартных
// модулей Delphi, то видно, что их авторы тоже
// проигнорировали все ЦУ. Наводит на размышления...
BitBlt(DC,0,0,BM2.Width,BM2.Height,BM2.Canvas.Handle,0,0,SrcCopy); // Рисуем получившуюся картинку
Msg.DC:=DC; // Эти две строчки учитывают особенности обработки WM_Paint в Delphi.
inherited; // Windows всегда посылает это сообщение с параметром wParam=0.
// Обработчик Delphi сделан так, что он может обрабатывать это
// сообщение при wParam<>0. В этом случае этот параметр интерпретируется
// как дескриптор контекста, BeginPaint и EndPaint не вызываются.
// Это позволяет писать вот такие обработчики.
← →
MAN-In-RED (2003-02-12 20:12) [10]
EndPaint(Handle,PS) // Ну, в общем-то и всё...
end;
procedure TForm1.WMMove;
begin
Invalidate; // Всё, пора перерисовываться
inherited
end;
procedure TForm1.WMEnterSizeMove;
begin
Moving:=True;
inherited
end;
procedure TForm1.WMExitSizeMove;
begin
inherited;
Moving:=False
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
BM:=TBitmap.Create;
BM.Width:=GetSystemMetrics(SM_CXScreen); // Так мы узнаём размеры экрана. В принципе,
BM.Height:=GetSystemMetrics(SM_CYScreen); // если TaskBar виден постоянно, то нам,
// казалось бы, нужно запоминать несколько
// меньшую часть экрана. Но окна, не
// имеющие рамки и заголовка, могут занимать
// и эту область, а когда они занимают,
// то и нормальные окна тоже могут
// покушаться на эту территторию. Так что
// не будем мелочиться.
BM.PixelFormat:=pf24bit;
BM2:=TBitmap.Create;
Moving:=False
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if ColorDialog1.Execute then
begin
TranspColor := ColorDialog1.Color;
Invalidate
end
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
Transparency:=SpinEdit1.Value;
Invalidate
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
BM.Free;
BM2.Free
end;
end.
← →
MAN-In-RED (2003-02-12 20:14) [11]Усе...
Страницы: 1 вся ветка
Форум: "Основная";
Текущий архив: 2003.02.24;
Скачать: [xml.tar.bz2];
Память: 0.54 MB
Время: 0.013 c