On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru






 

Регионы - средство повышения качества дизайна приложений

Сейчас, когда стандартный интерфейс 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