Сейчас, когда стандартный интерфейс windows уже порядком поднадоел, каждому
разработчику хочется сделать свое приложение не похожим на другие.
Функциональность, к сожалению, бросается в глаза далеко не в первую очередь. Как
только пользователь установил ваше приложение и впервые запустил его, он
обращает свое внимание на интерфейс и дизайн. В этом месте оригинальный дизайн
будет определять настроение пользователя в начале опробования вашего творения.
Не надо и объяснять, что чем лучше его настроение, тем меньше он будет обращать
внимания на те отрицательные мелочи (именно мелочи) которые в любом случае
остаются в реализации самой функциональности.
В системе windows специально для этого есть инструменты. К
сожалению, далеко не все разработчики их используют. Некоторые ссылаются на то,
что интерфейс должен быть именно стандартным для каждого приложения, чтобы не
сбивать пользователя с толку и не заставлять его "метаться" в поисках
той или иной стандартной функции (например, крестик в правом верхнем углу окна,
закрывающий это окно). Да, согласен, главное не переборщить. В этом нам помогает
старый опыт программирования (и использования сторонних программ) в DOS (если
кто еще помнит, а некоторые и не знают). Далеко отходить от стандартов не
следует. В то же время, внести свою оригинальность не является лишним. Так вот,
вернемся к инструментам, которые специально и внедрены в windows. Наиболее яркий
инструмент, это регионы. Регионы позволяют создать окна неправильной формы. В
этой статье я и хочу немного рассказать вам о том, как пользоваться этим
достаточно мощным "оружием" против стандартных окон.
Сначала я покажу, как с помощью функций WinAPI построить
простейшее окно неправильной формы (состоящее из простых примитивов), потом,
комбинируя их, покажу как строятся более сложные фигуры. И, в завершении
изложения, хочу показать и рассказать о маленьком фокусе, который снимает все
ограничения с вашей фантазии - возможность создания окон из рисунка.
Итак, приступим.
1. Простейшее окно неправильной формы
Для примера построим круглое окно. В обработчик главной
формы OnCreate поставьте следующий код:
procedure TForm1.FormCreate(Sender: TObject); var
Region:HRGN; begin Region:=CreateEllipticRgn(30,30,200,200); if
Region = 0 then raise Exception.Create('Пустой регион');
SetWindowRgn(Form1.Handle,Region,true); end;
Давайте перед тем как посмотреть что вышло обсудим детали
реализации:
Во первых, в разделе uses укажите модуль windows (скорее
всего он уже там указан, но это я так, на всякий случай). В этом модуле
находится интерфейсное описание этой функции. Теперь посмотрим на каждую
строчку:
Region:HRGN;
Переменная Region, имеющая тип HRGN. Это описатель (handle)
нашего будущего региона.
Region:=CreateEllipticRgn(30,30,200,200);
Тут мы создаем регион "Эллипс", вписанный в
квадрат с координатами левого верхнего угла 30, 30 и правого нижнего 200, 200
(т.е. круг).
if Region = 0 then raise Exception.Create('Пустой
регион');
Если регион задан неправильно, то функция возвращает 0.
Такой регион дальше использовать нельзя.
SetWindowRgn(Form1.Handle,Region,true);
Устанавливает созданный регион для окна. Form1.Handle - это
как раз и есть дескриптор (HWND) нашего главного окна. Третий параметр указывает
на то, нужно ли сразу после создания региона обновить его содержимое. Эта
функция тоже возвращает значение, сравнив с нулем которое можно узнать
успешность ее действия.
Теперь запустите пример. Что мы видим? Появился кружек.
Теперь попробуйте его подвигать, закрыть. Не получается? ;-). К сожалению, не
все так просто. Нужно еще дополнить некоторой функциональностью наш новый
регион. Для начала сделаем, чтобы он двигался. Ах да, вы наверное до сих пор на
него смотрите, не зная, как его снять? Если так, то вернитесь в Delphi IDE и
нажмите Ctrl+F2 (Program Reset).
Познакомлю вас с двумя методами придания двигательной
функциональности нашему "неправильному" окошку. Один метод "в
лоб", т.е. это первый, который должен приходить в голову догадливому
читателю, но, как и полагается, не самый лучший. Рассмотрим этот первый
метод:
type TForm1 = class(TForm) procedure
FormCreate(Sender: TObject); procedure FormMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure
FormMouseUp(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y:
Integer); procedure FormMouseMove(Sender: TObject; Shift: TShiftState;
X,Y: Integer); procedure FormDblClick(Sender: TObject); private
public
end;
var Form1: TForm1; IsMouseDown:boolean; LastX,
LastY:integer; Region:HRGN;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject); begin
Region:=CreateEllipticRgn(20,20,200,200); if Region = 0 then
raise Exception.Create('Пустой регион');
SetWindowRgn(Form1.Handle,Region,true); IsMouseDown:=false; end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer); begin IsMouseDown:=true;
LastX:=X; LastY:=Y; end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
begin IsMouseDown:=false; end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y:
Integer); begin if IsMouseDown then begin
Self.Top:=Self.Top+Y-LastY; Self.Left:=Self.Left+X-LastX;
LastX:=X; LastY:=Y; end; end;
procedure TForm1.FormDblClick(Sender: TObject); begin
Application.Terminate; // Закрытие программы при двойном клике по
региону end;
end.
Как говорится, без комментариев: А теперь немного подумаем.
Когда окно приходит в движение? Правильно, когда нажали мышью на заголовок и
потянули. Но ведь у нас нет заголовка окна. Правда, тут можно и поспорить:
попробуйте в первом примере заменить строку
Region:=CreateEllipticRgn(30,30,200,200); на строку
Region:=CreateEllipticRgn(1,1,200,200); О, что мы видим - заголовок
(точнее его кусок). Но, это совсем не то. Вы же хотите создать свой оригинальный
заголовок. Так вот, у нас нет заголовка окна. Так давайте попробуем ввести
систему в заблуждение, говоря ей, что пользователь кликает не по клиентской
области, а по заголовку окна!
private : procedure WMNCHitTest(var Message : TWMNCHitTest); message
WM_NCHITTEST;
var Region:HRGN;
implementation :
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest); begin
Message.Result := HTCAPTION; end;
Ну вот, то что нам и нужно, окно двигается за любое место в
регионе.
2. Более сложное окно неправильной формы
Теперь рассмотрим как можно комбинировать стандартные
примитивы для создания более сложных окон.
procedure TForm1.FormCreate(Sender: TObject); var TempRgn,
Region2:HRGN; begin Region:=CreateEllipticRgn(20,20,120,120);
TempRgn:=CreateEllipticRgn(100,20,200,120);
CombineRgn(Region,Region,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(180,20,280,120);
CombineRgn(Region,Region,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(60,100,160,200);
CombineRgn(Region,Region,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(140,100,240,200);
CombineRgn(Region,Region,TempRgn,RGN_OR);
Region2:=CreateEllipticRgn(40,40,100,100);
TempRgn:=CreateEllipticRgn(120,40,180,100);
CombineRgn(Region2,Region2,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(200,40,260,100);
CombineRgn(Region2,Region2,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(80,120,140,180);
CombineRgn(Region2,Region2,TempRgn,RGN_OR);
TempRgn:=CreateEllipticRgn(160,120,220,180);
CombineRgn(Region2,Region2,TempRgn,RGN_OR);
CombineRgn(Region,Region,Region2,RGN_XOR);
SetWindowRgn(Form1.Handle,Region,true); end;
Вот так вот. Ну, что касается оптимизации - тут есть над
чем поработать ;-). Но, нам главное принцип. Вся магия заключается в функции
CombineRgn. Ей надо бы уделить побольше внимания. Давайте рассмотрим ее
поближе:
Region:=CreateEllipticRgn(20,20,120,120);
TempRgn:=CreateEllipticRgn(100,20,200,120);
CombineRgn(Region,Region,TempRgn,RGN_OR);
Что делает этот код? Сначала мы создаем привычный нам уже
круглый регион. Следующая строка создает другой регион, тоже круглый, но
смещенный по оси X на 80 пикселей. А вот следующая строка просто объединяет их.
Ну и какая тут магия спросите вы? Да, действительно, никакой, все гениальное -
просто!
Значения параметров этой функции такие:
CombineRgn(Param1,Param2,Param3,Param4);
Param1: HRGN - регион, который получится в результате
работы функции; Param2: HRGN - первый входящий регион; Param3:
HRGN - второй входящий регион; Param4: INTEGER - режим объединения
регионов. Этот параметр задает булево условие объединения: RGN_AND
Создает пересечение из двух областей (регионов). RGN_COPY Создает копию
области, идентифицированной Param2. RGN_DIFF Объединяет части области
Param2, которые - не входят в область Param3. RGN_OR Создает объединение
двух областей. RGN_XOR Создает объединение двух объединенных областей
кроме любых зон перекрытия.
Теперь, вы беспрепятственно разберетесь дальше по коду.
Поэкспериментируйте с этой функцией, попробуйте задавать разный режим
объединения, посмотрите, что от этого будет меняться.
3. Создание окон неправильной формы из рисунка
Если после рассмотрения 2 предыдущих способов выхода за
рамки стандартного интерфейса, вам кажется, что еще "не все возможно",
то, вы правы. Давайте рассмотрим одну маленькую хитрость, которая дает нам еще
больше возможностей для воплощения дизайнерских фантазий. Мы будем использовать
только стандартные, уже рассмотренные функции работы с регионами. Как вы
убедитесь, прочитав эту часть, этого вполне достаточно. Итак, приступим к
рассмотрению полного кода:
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms,Dialogs;
type TForm1 = class(TForm) procedure FormCreate(Sender:
TObject); procedure FormDblClick(Sender: TObject);
private procedure WMNCHitTest(var Message : TWMNCHitTest); message
WM_NCHITTEST; procedure WMNCLButtonDBLClk(var Message
:TWMNCLBUTTONDBLCLK); message WM_NCLBUTTONDBLCLK; procedure
CreateRegionFromBitmap(Bitmap: TBitmap; TransparentColor: TColor);
public
end;
var Form1: TForm1; Region:HRGN;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject); var
Bitmap:TBitmap;
begin Bitmap:=TBitmap.Create;
Bitmap.LoadFromFile('c:\SOMEPICT.BMP');
Self.CreateRegionFromBitmap(Bitmap, clBlack, 100);
Bitmap.Free; end;
procedure TForm1.FormDblClick(Sender: TObject); begin
Application.Terminate; end;
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest); begin
Message.Result := HTCAPTION; end;
procedure TForm1.WMNCLButtonDBLClk(var Message: TWMNCLBUTTONDBLCLK); begin
//Application.Terminate; Self.Close; end;
procedure TForm1.CreateRegionFromBitmap(Bitmap: TBitmap; TransparentColor:
Tcolor;Range:integer); var x,y,FirstX:integer;
LastBeen:boolean; ComplexRGN, TempRGN:HRGN;
begin ComplexRgn:=CreateRectRgn(0,0,1,1); for y:=0 to
Bitmap.Height - 1 do begin FirstX:=0;
LastBeen:=false; for x:=0 to Bitmap.Width -1 do
begin if
(abs(Bitmap.Canvas.Pixels[x,y]-TransparentColor)>Range) and
(x<>pred(Bitmap.Width)) then
begin if not LastBeen then
begin LastBeen:=true;
FirstX:=x; end; end else
begin if LastBeen then
begin LastBeen:=false;
TempRGN:=CreateRectRgn(FirstX,y,x,y+1);
CombineRgn(ComplexRGN, ComplexRGN, TempRGN, RGN_OR);
DeleteObject(TempRGN); end; end;
end; end; SetWindowRgn(Form1.Handle,ComplexRGN,true); end;
end.
Рассмотрим код более подробно:
Bitmap.LoadFromFile('c:\SOMEPICT.BMP');
Это будет картой нашей формы. Задайте свой путь и картинку.
Лучше, если это будет черно-белая картинка, имеющая залитое определенным цветом
очертание будущего неправильного окна.
procedure TForm1.CreateRegionFromBitmap(Bitmap: TBitmap; TransparentColor:
Tcolor;Range:integer);
Метод, который и создает окно. Параметры: Bitmap - битмап, по которому
создавать окно; TransparentColor - цвет, который в битмапе будет соответствовать
прозрачной области (т.е. отсутствию видимой области); Range - чувствительность к
прозрачному цвету. Т.е. насколько далеко отстоящий от заданного, цвет еще будет
считаться прозрачным. Если картинка черно-белая (без серого градиента), то Range
можно задавать любым (реально Range=1). Если кому будет не лень, можете сделать
чувствительность по каждому из компонентов (RGB).
ComplexRgn:=CreateRectRgn(0,0,1,1);
Создаем пустой регион. Это необходимо для дальнейшего
вызова функции CombineRgn.
for y:=0 to Bitmap.Height - 1 do
:
for x:=0 to Bitmap.Width -1 do
:
Цикл проходящий по всем пикселам нашего битмапа.
if
(abs(Bitmap.Canvas.Pixels[x,y]-TransparentColor)>Range)and(x<>pred(Bitmap.Width))
then begin if not LastBeen then begin
LastBeen:=true; FirstX:=x; end; end else begin if
LastBeen then begin LastBeen:=false;
TempRGN:=CreateRectRgn(FirstX,y,x,y+1); CombineRgn(ComplexRGN,
ComplexRGN, TempRGN, RGN_OR); DeleteObject(TempRGN); end;
Тут мы накаливаем полоску, толщиной в 1 пиксель и длиной не более
Bitmap.Width, исключая непрозрачные области, присоединяем их к нашему конечному
ComplexRGN. И так до Bitmap.Height. Окончательный вариант нашего региона будет в
ComplexRGN, который мы и установим на наше окно с помощью уже знакомой функции
SetWindowRgn(Form1.Handle,ComplexRGN,true);
Ну, вот и все. Мне остается только пожелать вам удачи в
постижении верхов дизайнерского искусства и попрощаться со стандартным
интерфейсом в windows ;-).
Литература по Borland Delphi
|