Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Как сделать полупрозрачное окно 
:(
    Опции темы
Poseidon
Дата 9.6.2005, 18:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Delphi developer
****


Профиль
Группа: Комодератор
Сообщений: 5273
Регистрация: 4.2.2005
Где: Гомель, Беларусь

Репутация: 53
Всего: 133



ТЕОРИЯ:

Предисловие:
Недавно поднималась тема о прозрачности окна и непрозрачности компонентов. Решения были разные. Представляю вашему вниманию еще одно решение с улучшением- наложением светофильтров. Окно программы будет не просто прозрачным, но и цветным.

Прежде всего, хочу предупредить, что этот текст рассчитан на людей, знакомых с функциями WinAPI и сообщениями Windows и поэтому вряд ли будет полезен тем, кто предпочитает ограничиваться работой на уровне компонентов Delphi. 

Теперь ещё два предупреждения: во первых, полупрозрачное окно иногда перерисовывается не совсем правильно. Во-вторых, эта программа написана только для демонстрации самого принципа создания полупрозрачных окон и кое-что в ней можно (и иногда даже нужно) улучшить. 

Внутренняя схема перерисовки окон в Windows такова, что каждый раз перерисовывается только та его часть, которая не закрыта другими окнами и, следовательно, видна на экране. Это ускоряет процесс обновления экрана. Но полупрозрачное окно должно каким-то образом получать информацию о том, что нарисовано под ним. Здесь мы вступаем в противоречие с правилами Windows, поэтому следует прибегнуть к некоторому обману. При необходимости перерисовать полупрозрачное окно надо это окно ненадолго убрать с экрана. Как только все нижележащие окна будут перерисованы, надо запомнить ту область экрана, которая будет закрыта окном, вновь вывести на экран это окно и отрисовать его с учётом сохранённой картинки. Первая трудность на этом пути - как узнать, что перерисовка всех окон закончилась? Самый примитивный способ- подождать! Скажу только, что времени задержки 400 мс, которое установлено в этом примере вполне хватило. Вторая трудность - если вдруг окно, лежащее под полупрозрачным, обновилось, то это не приведёт сразу же к перерисовке этого окна.

Теперь о том, как осуществить всё это на практике. Перерисовка окна определяется обработкой одного из двух сообщений: WM_EraseBkgnd и WM_Paint. WM_Paint лучше вот почему: Во-первых, WM_EraseBkgnd может посылаться по несколько раз, соответственно окно перерисовывается до нескольких раз подряд. Во-вторых, между WM_EraseBkgnd и WM_Paint есть существенная разница: в первом случае Windows сам определяет, какая часть окна должна перерисоваться, и рисовать за пределами этой части просто не разрешает. А полупрозрачное окно, вообще говоря, обладает нетрадиционной точкой зрения на этот вопрос, что и приводит к конфликтам, особенно тогда, когда полупрозрачное окно частично закрыто другими окнами. Что же касается WM_Paint, то и тут Windows, конечно же, держит всё под контролем и тоже следит за тем, какая область окна должна быть перерисована. Однако, к счастью для полупрозрачных окон, это всё не выливается в прямые запреты, как в случае с WM_EraseBkgnd, здесь Windows ограничивается только выдачей ценных указаний через BeginPaint и TPaintStruct. Ну, а теперь мы отрисовываем окно целиком. И наконец, в-третьих, Windows зачем-то генерирует WM_EraseBkgnd после выполнения ShowWindow, поэтому попытка спрятать окно при обработке этого сообщения приведёт к бесконечной рекурсии. Впрочем, пренебрегать WM_EraseBkgnd тоже не стоит. Дело в том, что во время запуска программы этому процессу, видимо, присваивается повышенный приоритет. Это приводит к тому, что наше окно начинает рисоваться, Windows посылает WM_EraseBkgnd, стандартная процедура обработки этого события закрашивает всю клиентскую часть окна красивым серым цветом (как обычно), затем обрабатывается WM_Paint, в котором окно прячется с экрана, после чего остальные окна должны быстро перерисоваться и, когда пройдёт заданное время, программа посмотрит, что там нарисовано, начнёт наложение светофильтра... А окна-то не успели перерисоваться! Программа увидит своё собственное окно, которое мы не успели стереть. Это нам не надо, поэтому, чтобы не увеличивать время ожидания, нужно заблокировать вызов стандартного обработчика WM_EraseBkgnd. Это, естественно, никак не отразится на скорости перерисовки остальных окон, но ведь и само окно ничего не нарисует на экране, и после ожидания программа увидит то, что надо.  

И последний штрих: при перемещении окна Windows, во избежание ненужных действий, не запускает механизм перерисовки, а просто переносит изображение с одного места на другое. Для полупрозрачных окон это недопустимо, изображение должно обновляться при каждом переносе. Для этого надо отслеживать сообщение WM_Move, которое возникает в таких случаях. И, соответственно, запускать перерисовку окна. Если WM_Move вам не подходит , вы можете использовать WM_WindowPosChanged. Я не заметил разницы... 

Проблема заключается в том, что в некоторых версиях системы при перемещении окна не рисуется рамка, а каждый раз происходит перерисовка всего окна целиком. То же самое происходит и при изменении размеров окна. Так происходит, например, в Win NT и в Win95 при установленном MS Plus! Ключ к решению проблемы лежит в обработке сообщений WM_EnterSizeMove и WM_ExitSizeMove. Нужно завести переменную типа boolean, которая будет изменяться при начале перетаскивания с False на True и наоборот при его завершении. Соответственно обработчик WM_Paint должен следить за этой переменной и не выполнять задержку. Проблема - как узнать, что должно быть под окном. Если каждый раз запоминать не только нужную часть экрана, а весь экран целиком, то к моменту входа в режим перетаскивания программа будет обладать всей необходимой информацией о том, что там внизу. Теперь надо будет только вырезать нужный кусок. Главный недостаток такого подхода - программа должна быть, как пионер, всегда готова к началу перетаскивания и всегда сохранять экран целиком, что приведёт к дополнительному расходу памяти. Но от этого тоже можно избавиться, храня только нужный кусочек экрана (пока не реализовано).

ПРАКТИКА:
Бросим на форму Button, SpinEdit, ColorDialog.

Потом приводим код к такому состоянию:
Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Spin;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ColorDialog1: TColorDialog;
    SpinEdit1: TSpinEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    BM:TBitmap;
    BM2:TBitmap;
    Moving:Boolean;
    procedure WMEraseBkgnd(var Msg:TWMEraseBkgnd);message WM_EraseBkgnd;
    procedure WMPaint(var Msg:TWMPaint);message WM_Paint;
    procedure WMMove(var Msg:TMessage);message WM_Move;


    procedure WMEnterSizeMove(var Msg:TMessage);message WM_EnterSizeMove;
    procedure WMExitSizeMove(var Msg:TMessage);message WM_ExitSizeMove;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  TranspColor: TColor;
   Transparency:Integer=40;
const
      DelayTime:Integer=400;

implementation

{$R *.DFM}

type PRGBArray=^TRGBArray;
     TRGBArray=array[0..1000000] of TRGBTriple;


procedure Delay(DelayTime:Integer);
 var TicksNow:Integer;
  begin
   TicksNow:=GetTickCount;
   repeat
    Application.ProcessMessages
   until GetTickCount-TicksNow>=DelayTime
  end;



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);


     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;

   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
      end
    end;

   ShowWindow(Handle,SW_Show);
   DC:=BeginPaint(Handle,PS);

   BitBlt(DC,0,0,BM2.Width,BM2.Height,BM2.Canvas.Handle,0,0,SrcCopy);

   Msg.DC:=DC;
   inherited;

   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);
  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. 


Приведенный код без комментариев. Они все в присоединенном файле. Дерзайте.

Послесловие:
Тут довольно много некорректного кода (по причине того, что я много еще не знаю).
Просьба все улучшения выкладывать в этой теме (без флейма, плз).
В примере добавлены два label`а, чтобы показать, что их свойство Transparent работает.
И еще- попробуйте в SpinEdit добавить ноликов, чтобы значение выходило за «100», будет красиво!

Помогли- DelphiKingdom


--------------------
Если хочешь, что бы что-то работало - используй написанное, 
если хочешь что-то понять - пиши сам...
PM MAIL ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0879 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.