Наконец, после теоретического отступления рассмотрим несколько примеров создания и преобразования регионов с последующим их использованием для формирования области отсечения окон (форм и элементов управления на формах).

Закругленные окна и многоугольники

   Сначала самые простые примеры: создание регионов без операций над ними. Формы всех трех приведенных здесь примеров содержат по три кнопки различной ширины и высоты, которым также задаются области отсечения.
   Примечание
   В приведенных далее примерах регионы для области отсечения окна создаются при обработке события FormCr eate. Однако это сделано только для удобства отладки и тестирования примеров и ни в коем случае не должно наталкивать вас на мысль, что этот способ является единственно правильным. На самом деле, если в приложении много окон, использующих области отсечения сложной формы, то старт приложения (время от момента запуска до показа первой формы) может длиться по крайней мере несколько секунд. Так происходит потому, что все формы создаются перед показом первой (главной) формы (см. DPR-файл проекта). Исправить ситуацию можно, создавая формы вручную в нужный момент времени либо создавая регионы для областей отсечения, например, перед первым отображением каждой конкретной формы.
   Итак, в приведенном ниже обработчике события FormCreate создается окно в форме эллипса с тремя кнопками такой же формы (листинг 1.10).
Листинг 1.10. Окно и кнопки в форме эллипсов
   procedure TfrmElliptic.FormCreate(Sender: TObject);
   var
   formRgn, but1Rgn, but2Rgn, but3Rgn: HRGN;
   begin
   //Создаем регионы кнопок
   but1Rgn:= CreateEllipticRgn(0, 0, Button1.Width–1, Button1.
   Height–1);
   SetWindowRgn(Button1.Handle, but1Rgn, False);
   but2Rgn:= CreateEllipticRgn(0, 0, Button2.Width–1, Button2.
   Height–1);
   SetWindowRgn(Button2.Handle, but2Rgn, False);
   but3Rgn:= CreateEllipticRgn(0, 0, Button3.Width–1, Button3.
   Height–1);
   SetWindowRgn(Button3.Handle, but3Rgn, False);
   //Регион для окна
   formRgn:= CreateEllipticRgn(0, 0, Width–1, Height–1);
   SetWindowRgn(Handle, formRgn, True);
   end;
   Ширина и высота эллипсов в приведенном примере равна соответственно ширине и высоте тех окон, для которых создаются регионы (формы и каждой из кнопок). При необходимости это можно изменить, например, если требуется, чтобы все кнопки были одной величины независимо от размера, установленного во время проектирования формы.
   Результат работы листинга 1.10 можно увидеть на рис. 1.5.
   Рис. 1.5. Окно и кнопки в форме эллипсов
 
   Далее рассмотрим не менее интересный (возможно, даже более полезный на практике) пример, а именно округление углов формы и кнопок на ней, то есть применение к ним области отсечения в форме прямоугольника с округленными углами. Ниже приводится реализация соответствующего обработчика события FormCreate (листинг 1.11).
Листинг 1.11. Окно и кнопки с округленными краями
   procedure TfrmRoundRect.FormCreate(Sender: TObject);
   var
   formRgn, but1Rgn, but2Rgn, but3Rgn: HRGN;
   begin
   //Создаем регионы для кнопок
   but1Rgn:= CreateRoundRectRgn(0, 0, Button1.Width–1,
   Button1.Height–1,
   Button1.Width div 5,
   Button1. Height div 5);
   SetWindowRgn(Button1.Handle, but1Rgn, False);
   but2Rgn:= CreateRoundRectRgn(0, 0, Button2.Width–1,
   Button2.Height–1,
   Button2.Width div 5,
   Button2.Height div 5);
   SetWindowRgn(Button2.Handle, but2Rgn, False);
   but3Rgn:= CreateRoundRectRgn(0, 0, Button3.Width–1,
   Button3.Height–1,
   Button3.Width div 5,
   Button3.Height div 5);
   SetWindowRgn(Button3.Handle, but3Rgn, False);
   //Регион для окна
   formRgn:= CreateRoundRectRgn(0, 0, Width–1, Height–1,
   Width div 5, Height div 5);
   SetWindowRgn(Handle, formRgn, False);
   end;
   В листинге 1.11 размеры округляющих эллипсов вычисляются в зависимости от размеров конкретного окна (20 % от ширины и 20 % от высоты). Это смотрится не всегда красиво. В качестве альтернативы для ширины и высоты скругляющих эллипсов можно использовать фиксированные небольшие значения.
   Результат работы листинга 1.11 можно увидеть на рис. 1.6.
   Рис. 1.6. Окно и кнопки с округленными краями
 
   Теперь самый интересный из предусмотренных примеров – создание окна и кнопок в форме многоугольников, а конкретно: окна в форме звезды, кнопок в форме треугольника, пяти– и шестиугольника (рис. 1.7).
   Рис. 1.7. Окно и кнопки в форме многоугольников
 
   Создание регионов для областей отсечения формы, показанной на рис. 1.7, выглядит следующим образом (листинг 1.12).
Листинг 1.12. Окно и кнопки в форме многоугольников
   procedure TfrmPoly.FormCreate(Sender: TObject);
   var
   points: array [0..5] of TPoint;
   formRgn, but1Rgn, but2Rgn, but3Rgn: HRGN;
   begin
   //Создаем регионы для окна и кнопок
   //..шестиугольная кнопка
   Make6Angle(Button1.Width, Button1.Height, points);
   but1Rgn:= CreatePolygonRgn(points, 6, WINDING);
   SetWindowRgn(Button1.Handle, but1Rgn, False);
   //..треугольная кнопка
   Make3Angle(Button2.Width, Button2.Height, points);
   but2Rgn:= CreatePolygonRgn(points, 3, WINDING);
   SetWindowRgn(Button2.Handle, but2Rgn, False);
   //..пятиугольная кнопка
   Make5Angle(Button3.Width, Button3.Height, points);
   but3Rgn:= CreatePolygonRgn(points, 5, WINDING);
   SetWindowRgn(Button3.Handle, but3Rgn, False);
   //..форма в виде звезды
   MakeStar(Width, Height, points);
   formRgn:= CreatePolygonRgn(points, 5, WINDING);
   SetWindowRgn(Handle, formRgn, False);
   end;
   Особенностью создания регионов в приведенном листинге является использование дополнительных процедур для заполнения массива points координатами точек-вершин многоугольников определенного вида. Все эти процедуры принимают, помимо ссылки на сам массив points, ширину и высоту прямоугольника, в который должен быть вписан многоугольник. Процедура создания треугольника приводится в листинге 1.13.
Листинг 1.13. Создание треугольника
   procedure Make3Angle(width, height: Integer; var points: array
   of TPoint);
   begin
   points[0].X:= 0;
   points[0].Y:= height – 1;
   points[1].X:= width div 2;
   points[1].Y:= 0;
   points[2].X:= width – 1;
   points[2].Y:= height – 1;
   end;
   В листинге 1.14 приведена процедура создания шестиугольника.
Листинг 1.14. Создание шестиугольника
   procedure Make6Angle(width, height: Integer; var points: array
   of TPoint);
   begin
   points[0].X:= 0;
   points[0].Y:= height div 2;
   points[1].X:= width div 3;
   points[1].Y:= 0;
   points[2].X:= 2 * (width div 3);
   points[2].Y:= 0;
   points[3].X:= width – 1;
   points[3].Y:= height div 2;
   points[4].X:= 2 * (width div 3);
   points[4].Y:= height – 1;
   points[5].X:= width div 3;
   points[5].Y:= height – 1;
   end;
   Листинг 1.15 содержит процедуру создания пятиугольника (неправильного).
Листинг 1.15. Создание пятиугольника
   procedure Make5Angle(width, height: Integer; var points: array
   of TPoint);
   var a: Integer; //Сторона пятиугольника
   begin
   a:= width div 2;
   points[0].X:= a;
   points[0].Y:= 0;
   points[1].X:= width – 1;
   points[1].Y:= a div 2;
   points[2].X:= 3 * (a div 2);
   points[2].Y:= height – 1;
   points[3].X:= a div 2;
   points[3].Y:= height – 1;
   points[4].X:= 0;
   points[4].Y:= a div 2;
   end;
   Пятиугольная звезда, используемая как область отсечения формы, создается при помощи приведенной в листинге 1.15 процедуры Make5Angle. После изменяется порядок следования вершин пятиугольника, чтобы их обход при построении региона выполнялся так, как рисуется звезда карандашом на бумаге (например, 1-3-5-2-4) (листинг 1.16).
Листинг 1.16. Создание пятиугольной звезды
   procedure MakeStar(width, height: Integer; var points: array
   of TPoint);
   begin
   Make5Angle(width, height, points);
   //При построении звезды точки пятиугольника обводятся не по
   //порядку,а через одну
   Swap(points[1], points[2]);
   Swap(points[2], points[4]);
   Swap(points[3], points[4]);
   end;
   Процедура MakeStart (листинг 1.16) использует дополнительную процедуру Swap, меняющую местами значения двух передаваемых в нее аргументов. Процедура Swap реализуется чрезвычайно просто и потому в тексте книги не приводится.

Комбинированные регионы

   Вы уже научились создавать и использовать простые регионы. Однако многим может показаться недостаточным тех форм окон, которые получаются с использованием лишь одного несложного региона в качестве области отсечения. Пришло время заняться созданием окон более сложной формы, применяя рассмотренные ранее операции над регионами.
«Дырявая» форма
   Этот простейший пример сомнительной полезности предназначен для знакомства с операциями над регионами. Здесь применяется только одна из возможных операций – операция XOR для формирования «дырок» в форме (рис. 1.8).
   Рис. 1.8. «Дырки» в форме
 
   На рис. 1.8 явно видно, как в «дырках» просвечивается одно из окон среды разработки Delphi. При этом сообщения от мыши, когда указатель находится над «дыркой», получает не наше окно, а те, часть которых видна в «дырке».
   Программный код, приводящий к созданию формы столь необычного вида, приведен в листинге 1.17.
Листинг 1.17. Создание «дырок» в форме
   procedure TfrmHole.FormCreate(Sender: TObject);
   var
   rgn1, rgn2: HRGN; //"Регионы-дырки" в форме
   formRgn: HRGN;
   begin
   //Создание региона для формы
   formRgn := CreateRectRgn(0, 0, Width – 1, Height – 1);
   //Создание регионов для «дырок»
   rgn1 := CreateEllipticRgn(10, 10, 100, 50);
   rgn2 := CreateRoundRectRgn(10, 60, 200, 90, 10, 10);
   //Создание «дырок» в регионе формы
   CombineRgn(formRgn, formRgn, rgn1, RGN_XOR);
   CombineRgn(formRgn, formRgn, rgn2, RGN_XOR);
   SetWindowRgn(Handle, formRgn, True);
   //Регионы для «дырок» больше не нужны
   DeleteObject(rgn1);
   DeleteObject(rgn2);
   end;
Сложная комбинация регионов
   Теперь пришла очередь более сложного, но и гораздо более интересного примера. Последовательное применение нескольких операций над регионами приводит к созданию формы, показанной на рис. 1.9 (белое пространство – это вырезанные части формы).
   Рис. 1.9. Сложная комбинация регионов
 
   Процедура, в которой производятся операции над регионами, приведена в листинге 1.18.
Листинг 1.18. Сложная комбинация регионов
   procedure TfrmManyRgn.FormCreate(Sender: TObject);
   var
   r1, r2, r3, r4, r5, r6, r7: HRGN;
   formRgn: HRGN;
   butRgn: HRGN;
   begin
   //Создание регионов
   r1 := CreateRoundRectRgn(100, 0, 700, 400, 40, 40);
   r2 := CreateRectRgn(280, 0, 300, 399);
   r3 := CreateRectRgn(500, 0, 520, 399);
   r4 := CreateEllipticRgn(140, 40, 240, 140);
   r5 := CreateEllipticRgn(0, 300, 200, 500);
   r6 := CreateEllipticRgn(500, 40, 600, 140);
   r7 := CreateEllipticRgn(540, 40, 640, 140);
   //Комбинирование
   //..разрезы в основном регионе
   CombineRgn(r1, r1, r2, RGN_XOR);
   CombineRgn(r1, r1, r3, RGN_XOR);
   //..круглая «дырка» в правой стороне
   CombineRgn(r1, r1, r4, RGN_XOR);
   //..присоединение круга в левой нижней части
   CombineRgn(r1, r1, r5, RGN_OR);
   //..создание «дырки» в форме полумесяца
   CombineRgn(r7, r7, r6, RGN_DIFF);
   CombineRgn(r1, r1, r7, RGN_XOR);
   formRgn := CreateRectRgn(0, 0, 0, 0);
   CombineRgn(formRgn, r1, 0, RGN_COPY);
   DeleteObject(r1);
   DeleteObject(r2);
   DeleteObject(r3);
   DeleteObject(r4);
   DeleteObject(r5);
   DeleteObject(r6);
   DeleteObject(r7);
   //Создание круглой кнопки закрытия
   butRgn := CreateEllipticRgn(50, 50, 150, 150);
   SetWindowRgn(Button1.Handle, butRgn, False);
   SetWindowRgn(Handle, formRgn, True);
   end;
   В листинге подписано, какие операции для создания каких элементов итогового региона предназначены. В операциях участвуют семь регионов. Расположение используемых в операциях регионов показано на рис. 1.10.
   Рис. 1.10. Элементарные регионы, используемые для получения формы на рис. 1.9.
Использование шаблона
   Предыдущий пример наглядно демонстрирует мощь функции CombineRgn при построении регионов сложной формы. Однако существует огромное количество предметов, контуры которых крайне сложно повторить, комбинируя простые регионы. Построение многоугольных регионов с большим количеством точек может в этом случае нас выручить, но ведь это крайне нудно и утомительно.
   Если есть изображение предмета, контуры которого должны совпадать с контурами региона, то гораздо проще при построении региона обрабатывать само изображение, отбирая все точки, для которых выполняется определенное условие. Изображение и будет тем шаблоном, по которому «вырезается» регион нужной формы.
   Рассмотрим простейший пример: есть монохромное изображение, каждая точка которого должна попасть в результирующий регион, если ее цвет не совпадает с заданным цветом фона. При этом изображение анализируется по так называемым «скан-линиям», то есть построчно. Из подряд идущих точек не фонового цвета формируются прямоугольные регионы, которые объединяются с результирующим регионом. Пример возможного шаблона приведен на рис. 1.11.
   Рис. 1.11. Пример растрового изображения-шаблона
 
   Функция построения региона указанным способом приведена в листинге 1.19.
Листинг 1.19. Построение региона по шаблону
   function RegionFromPicture(pict: TPicture; backcolor: TColor):
   HRGN;
   var
   rgn, resRgn: HRGN;
   x, y, xFirst: Integer;
   begin
   resRgn := CreateRectRgn(0, 0, 0, 0); //Результирующий регион
   //Анализируем каждую скан-линию рисунка (по горизонтали)
   for y := 0 to pict.Height – 1 do
   begin
   x := 0;
   while x < pict.Width do
   begin
   if (pict.Bitmap.Canvas.Pixels[x, y] <> backcolor) then
   begin
   xFirst := x;
   Inc(x);
   //Определим часть линии, окрашенной не цветом фона
   while (x < pict.Width) and
   (pict.Bitmap.Canvas.Pixels[x, y] <> backcolor) do Inc(x);
   //Создаем регион для части скан-линии и добавляем его
   //к результурующему региону
   rgn := CreateRectRgn(xFirst, y, x–1, y+1);
   CombineRgn(resRgn, resRgn, rgn, RGN_OR);
   DeleteObject(rgn);
   end;
   Inc(x);
   end;
   end;
   RegionFromPicture := resRgn;
   end;
   Загрузка изображения-шаблона и создание региона может происходить, например, при создании формы следующим образом (листинг 1.20).
Листинг 1.20. Создание региона для области отсечения формы
   procedure TfrmTemplate.FormCreate(Sender: TObject);
   var
   pict: TPicture;
   begin
   //Загрузка изображения и создание региона (считаем, что
   //цвет фона – белый)
   pict := TPicture.Create;
   pict.LoadFromFile('back.bmp');
   SetWindowRgn(Handle, RegionFromPicture(pict, RGB(255,255,255)),
   True);
   end;
   В листинге 1.20 подразумевается использование файла back.bmp, находящегося в той же папке, что и файл приложения. Цвет фона – белый. Таким образом, если шаблон, показанный на рис. 1.11, хранится в файле back. bmp, то получим форму, как на рис. 1.12.
   Рис. 1.12. Результат построения региона по шаблону

1.5. Немного о перемещении окон

   Кроме придания необычного вида окнам приложения способами, рассмотренными выше, можно также несколько разнообразить интерфейс за счет оригинального использования перемещения окон. Далее показано, как самостоятельно назначать области, за которые можно перетаскивать (и не только) форму. Еще один пример демонстрирует способ дать пользователю возможность самомузадавать расположение элементов управления на форме.

Перемещение за клиентскую область

   Здесь на конкретном примере (перемещение формы за любую точку клиентской области) продемонстрировано, как можно самостоятельно определять положение некоторых важных элементов окна. Под элементами окна здесь подразумеваются:
   • строка заголовка (не только предназначена для отображения текста заголовка, но и служит областью захвата при перемещении окна мышью);
   • границы окна (при щелчке кнопкой мыши на верхней, нижней, правой и левой границе можно изменять размер окна, если, правда, стиль окна это допускает);
   • четыре угла окна (предназначены для изменения размера окна при помощи мыши);
   • системные кнопки – закрытия, разворачивания, сворачивания, контекстной справки (обычно расположены в строке заголовка окна);
   • полосы прокрутки – горизонтальная и вертикальная;
   • системное меню (раскрывается при щелчке кнопкой мыши на значке окна);
   • меню – полоса меню, обычно вверху окна;
   • клиентская область – по умолчанию все пространство окна, кроме строки заголовка, меню и полос прокрутки.
   Каждый раз, когда над окном перемещается указатель мыши либо происходит нажатие кнопки мыши, система посылает соответствующему окну сообщение WM_NCHITTEST для определения того, над какой из перечисленных выше областей окна находится указатель. Обработчик этого сообщения, вызываемый по умолчанию, информирует систему о расположении элементов окна в привычных для нас местах: заголовка – сверху, правой границы – справа и т. д.
   Как вы, скорее всего, уже догадались, реализовав свой обработчик сообщения WM_NCHITTEST, можно изменить назначение элементов окна. Этот прием как раз и используется в листинге 1.21.
Листинг 1.21. Перемещение окна за клиентскую область
   procedure TfrmMoveClient.WMNCHitTest(var Message: TWMNCHitTest);
   var
   rc: TRect;
   p: TPoint;
   begin
   //Если точка приходится на клиентскую область, то заставим
   //систему считать эту область частью строки заголовка
   rc := GetClientRect();
   p.X := Message.XPos;
   p.Y := Message.YPos;
   p := ScreenToClient(p);
   if PtInRect(rc, p) then
   Message.Result := HTCAPTION
   else
   //Обработка по умолчанию
   Message.Result := DefWindowProc(Handle, Message.Msg, 0,
   65536 * Message.YPos + Message.XPos);
   end;
   Приведенный в листинге 1.21 обработчик переопределяет положение только строки заголовка, возвращая значение HTCAPTION. Этот обработчик может возвращать следующие значения (целочисленные константы, возвращаемые функцией DefWindowProc):
   • HTBORDER – указатель мыши находится над границей окна (размер окна не изменяется);
   • НТВОТТОМ, НТТОР, HTLEFT, HTRIGHT – над нижней, верхней, левой или правой границей окна соответственно (размер окна можно изменить, «потянув» за границу);
   • HTBOTTOMLEFT, HTBOTTOMRIGHT, HTTOPLEFT, HTTOPRIGHT – В левом нижнем, правом нижнем, левом верхнем или правом верхнем углу окна (размер окна можно изменять по диагонали);
   • HTSIZE, HTGROWBOX – над областью, предназначенной для изменения размера окна по диагонали (обычно в правом нижнем углу окна);
   • HTCAPTION – над строкой заголовка окна (за это место окно перемещается);
   • HTCLIENT – над клиентской областью окна;
   • HTCLOSE – над кнопкой закрытия окна;
   • HTHELP – над кнопкой вызова контекстной справки;
   • HTREDUCE, HTMINBUTTON – над кнопкой минимизации окна;
   • HTZOOM, HTMAXBUTTON – над кнопкой максимизации окна;
   • HTMENU – над полоской меню окна;
   • HTSYSMENU – над значком окна (используется для вызова системного меню);
   • HTHSCROLL, HTVSCROLL – указатель находится над вертикальной или горизонтальной полосой прокрутки соответственно;
   • HTTRANS PARENT – если возвращается это значение, то сообщение пересылается окну, находящемуся под данным окном (окна должны принадлежать одному потоку);
   • HTNOWHERE – указатель не находится над какой-либо из областей окна (например, на границе между окнами);
   • HTERROR – то же, что и NTNOWHERE, только при возврате этого значения обработчик по умолчанию (DefWindowProc) воспроизводит системный сигнал, говорящий об ошибке.

Перемещаемые элементы управления

   В завершение материала о перемещении окон приведем один совсем несложный, но довольно интересный пример, позволяющий прямо «на лету» модифицировать внешний вид приложения, перемещая и изменяя размер элементов управления так, как будто это обычные перекрывающиеся окна.
   Чтобы вас заинтересовать, сразу приведем результат работы примера. Итак, на рис. 1.13 показан внешний вид формы в начале работы примера.
   Рис. 1.13. Первоначальный вид формы
 
   Теперь устанавливаем флажок Перемещение элементов управления и получаем результат, показанный на рис. 1.14.
   Рис. 1.14. Элементы управления можно перемещать (флажок не учитывается)
 
   Выполняем произвольные перемещения, изменение размера окон, занявших место элементов управления, снимаем флажок и получаем результат – измененный интерфейс формы (рис. 1.15).
   Рис. 1.15. Внешний вид формы после перемещения элементов управления
 
   Как же достигнут подобный эффект? Очень даже просто. Ведь вы уже знаете, что элементы управления рисуются внутри своих собственных окон (дочерних по отношению к окну формы). Окна элементов отличает отсутствие в их стиле флагов (по – дробнее в гл. 2), позволяющих отображать рамку, изменять размер окна элемента управления. Это легко исправить, самостоятельно задав нужные флаги в стиле окна при помощи API-функции SetWindowLong. Для удобства можно написать отдельную процедуру, которая будет дополнять стиль окна флагами, необходимыми для перемещения и изменения размера (как, собственно, и сделано в примере) (лис – тинг 1.22).
Листинг 1.22. Разрешение перемещения и изменения размера
   procedure MakeMovable(Handle: HWND);
   var
   style: LongInt;
   flags: UINT;
   begin
   //Разрешаем перемещение элемента управления
   style := GetWindowLong(Handle, GWL_STYLE);
   style := style or WS_OVERLAPPED or WS_THICKFRAME or WS_CAPTION;
   SetWindowLong(Handle, GWL_STYLE, style);
   style := GetWindowLong(Handle, GWL_EXSTYLE);
   style := style or WS_EX_TOOLWINDOW;
   SetWindowLong(Handle, GWL_EXSTYLE, style);
   //Перерисуем в новом состоянии
   flags := SWP_NOMOVE or SWP_NOSIZE or SWP_DRAWFRAME or
   SWP_NOZORDER;
   SetWindowPos(Handle, 0, 0, 0, 0, 0, flags);
   end;
   Как можно увидеть, задание дополнительных флагов происходит в два этапа. Сначала считывается старое значение стиля окна. Потом при помощи двоичной операции ИЛИ стиль (а это целочисленное значение) дополняется новыми флагами. Это делается для того, чтобы не пропали ранее установленные значения стиля окна.
   Вообще, процедура MakeMovable изменяет два стиля окна: обычный и расширенный. Расширенный стиль окна изменяется лишь для того, чтобы строка заголовка получившегося окна занимала меньше места (получаем так называемое окно панели инструментов). Полный перечень как обычных, так и расширенных стилей можно посмотреть в приложении 2.
   Логично также реализовать процедуру, обратную MakeMovable, запрещающую перемещение окон элементов управления (листинг 1.23).
Листинг 1.23. Запрещение перемещения и изменения размера
   procedure MakeUnmovable(Handle: HWND);
   var
   style: LongInt;
   flags: UINT;
   begin
   //Запрещаем перемещение элемента управления
   style := GetWindowLong(Handle, GWL_STYLE);
   style := style and not WS_OVERLAPPED and not WS_THICKFRAME
   and not WS_CAPTION;
   SetWindowLong(Handle, GWL_STYLE, style);
   style := GetWindowLong(Handle, GWL_EXSTYLE);
   style := style and not WS_EX_TOOLWINDOW;
   SetWindowLong(Handle, GWL_EXSTYLE, style);
   //Перерисуем в новом состоянии
   flags := SWP_NOMOVE or SWP_NOSIZE or SWP_DRAWFRAME or
   SWP_NOZORDER;
   SetWindowPos(Handle, 0, 0, 0, 0, 0, flags);
   end;
   Осталось только реализовать вызовы процедур MakeMovable и MakeUnmovable в нужном месте программы. В нашем примере вызовы заключены внутри обработчика изменения состояния флажка на форме (листинг 1.24).
Листинг 1.24. Управление перемещаемостью элементов управления
   procedure TfrmMovingControls.chkSetMovableClick(Sender: TObject);
   begin
   if chkSetMovable.Checked then
   begin
   //Разрешаем перемещение элементов управления
   MakeMovable(Memo1.Handle);
   MakeMovable(ListBox1.Handle);
   MakeMovable(Button1.Handle);
   end
   else
   begin
   //Запрещаем перемещение элементов управления
   MakeUnmovable(Memo1.Handle);
   MakeUnmovable(ListBox1.Handle);
   MakeUnmovable(Button1.Handle);
   end;
   end;

1.6. Масштабирование окон

   Возможность масштабирования окон (форм) является интересным приемом, который может быть заложен в дизайн приложения.
   При этом имеется в виду масштабирование в буквальном смысле этого слова: как пропорциональное изменение размера элементов управления формы, так и изменение размера шрифта.
   Использовать масштабирование при работе с Delphi крайне просто, ведь в класс TWinControl, от которого наследуются классы форм, встроены методы масштабирования. Вот некоторые из них:
   • ScaleControls – пропорциональное изменение размера элементов управления на форме;
   • ChangeScale – пропорциональное изменение размера элементов управления с изменением шрифта, которым выводится текст в них.
   Оба приведенных метода принимают два целочисленных параметра: числитель и знаменатель нового масштаба формы. Пример задания параметров для методов масштабирования приводится в листинге 1.25.
Листинг 1.25. Масштабирование формы с изменением шрифта
   procedure TfrmScaleBy.cmbSmallerClick(Sender: TObject);
   begin
   ChangeScale(80, 100); //Уменьшение на 20 % (новый масштаб – 80 %)
   end;
   procedure TfrmScaleBy.cmbBiggerClick(Sender: TObject);
   begin
   ChangeScale(120, 100); //Увеличение на 20 % (новый масштаб – 120 %)
   end;
   Чтобы размер шрифта правильно устанавливался, для элементов управления нужно использовать шрифты семейства TrueType (в нашем примере это шрифт Times New Roman).
   На рис. 1.16 приводится внешний вид формы до изменения масштаба.
   Рис. 1.16. Форма в оригинальном масштабе
 
   Внешний вид формы после уменьшения масштаба в 1,25 раза (новый масштаб составляет 80 % от первоначального) демонстрируется на рис. 1.17.
   Рис. 1.17. Форма в масштабе 80 %
 
   То, что форма не изменяет своего размера при масштабировании, можно легко исправить, установив, например, свойство AutoSize в True при помощи редактора свойств объектов (Object Inspector).
   Если по каким-либо причинам использование свойства AutoSize вас не устраивает, то можно рассчитать новый размер формы самостоятельно. Только пересчитывать нужно размер не всего окна, а его клиентской области, ведь строка заголовка при масштабировании остается без изменений. Расчет размера окна можно выполнить так:
   1. Получить прямоугольник клиентской области окна (GetClientRect).
   2. Посчитать новый размер клиентской области.
   3. Рассчитать разницу между новой и первоначальной шириной, новой и первоначальной высотой клиентской области; сложить полученные значения с первоначальными размерами самой формы.
   Пример расчета приводится ниже (для увеличения размера клиентской области в 1,2 раза):
   GetClientRect(Handle, rc);
   newWidth := (rc.Right – rc.Left) * 120 div 100;
   newHeight := (rc.Bottom – rc.Top) * 120 div 100;
   Width := Width + newWidth – (rc.Right – rc.Left);
   Height := Height + newHeight – (rc.Bottom – rc.Top);
   Примечание
   Чтобы после уменьшения или увеличения масштаба формы можно было вернуться в точности к первоначальному масштабу (при помощи соответствующей обратной операции), нужно для уменьшения или увеличения использовать коэффициенты, произведение которых равно 1. Например, при уменьшении масштаба на 20 % (в 0,8 раза) его нужно увеличивать при обратной операции на 25 % (в 1/0,8 = 1,25 раза).