div.main {margin-left: 20pt; margin-right: 20pt}Регионы - средство повышения качества дизайна
приложений
Сейчас, когда стандартный интерфейс 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 ;-).
|