Стаття робота з регіонами


Windows API надає набір функцій, що дозволяють описати довільну (при бажанні - досить складну) геометричну фігуру, яка потім може використовуватися при роботі з вікнами, або, в термінології Delphi, елементами управління. Використання може полягати, наприклад, в відображенні на полотні, в завданні специфічного регіону поновлення вікна, і т.д. Крім таких ось щодо невинних можливостей, технологія регіонів позволяяет також глумитися над благородними обрисами будь-якого нащадка TWinControl (іншими словами, будь-яким контролом, які мають Handle, aka TForm, TButton, і т.д.). Особливо широко регіони використовуються в формах, при їх відображенні і оновленні. Отже, для початку, давайте розберемося, що таке цей самий регіон.

Win32 SDK регіон визначає наступним чином:

У Microsoft Windows регіоном називається прямокутник, полігон або еліпс (або комбінація двох або більше цих фігур), які можуть бути заповнені, намальовані, інвертовані, обрамлені і можуть використовуватися для визначення місцезнаходження курсора

(Мається на увазі, що є стандартна функція, яка визначає чи входить точка (X, Y) в наш регіон).

У SDK згадані три основних типи регіонів: прямокутні, еліптичні, і полігональні. Про чётвёртом типі, прямокутному із закругленими краями, сором'язливо замовчується, ну та й фіг з ним. Мабуть це пов'язано з тим, що його можна отримати з перших двох. Справа в тому, що регіони можуть комбінуватися між собою із застосуванням логічних операцій OR, XOR і т.д. Але до цього ми повернемося пізніше.

HRGN = type LongWord;


Як бачимо, нічого надприродного. По суті це покажчик на якусь структуру в пам'яті. Структура ця описана в такий спосіб:


_RGNDATA = record
rdh. TRgnDataHeader;
Buffer. array # 91; 0. 0 # 93; of CHAR;
Reserved. array # 91; 0. 2 # 93; of CHAR;
end;


Що примітно, в Win32 SDK _RGNDATA оголошена трохи інакше. Найбільш цікавий член цього запису, безумовно, TRgnDataHeader. У модулі Windows.pas фігурує і він:

_RGNDATAHEADER = packed record
dwSize. DWORD;
iType. DWORD;
nCount. DWORD;
nRgnSize. DWORD;
rcBound. TRect;
end;


Ось його варто розглянути докладніше.

Об'єднує регіони p2 і p3, виключаючи пересічні області


Значення, що повертаються можуть бути NULLREGION (порожній регіон), SIMPLEREGION (один прямокутник), COMPLEXREGION (все інше) і ERROR (ніфіга не створено). Подивимося, як це виглядає на практиці (щоб не сильно мучить, я просто дописував попередній приклад).

procedure TForm1. FormCreate # 40; Sender. TObject # 41; ;
var
ap. array # 91; 1. 7 # 93; of TPoint;
av. array # 91; 1. 2 # 93; of integer;
r. r2. r3. r4. hRGN;
begin
ap # 91; 1 # 93 ;. = Point # 40; 0. 0 # 41; ;
ap # 91; 2 # 93 ;. = Point # 40; Width. 0 # 41; ;
ap # 91; 3 # 93 ;. = Point # 40; Width. Height div 2 # 41; ;
ap # 91; 4 # 93 ;. = Point # 40; 0. Height div 2 # 41; ;
ap # 91; 5 # 93 ;. = Point # 40; Width div 2. Height div 2 # 41; ;
ap # 91; 6 # 93 ;. = Point # 40; 0. Height # 41; ;
ap # 91; 7 # 93 ;. = Point # 40; Width. Height # 41; ;
av # 91; 1 # 93 ;. = 4;
av # 91; 2 # 93 ;. = 3;
r. = CreatePolyPolygonRgn # 40; ap. av. 2. WINDING # 41; ;
try
r2. = CreateRoundRectRgn
# 40; Width div 4 - 20. Height div 6 - 20.
Width div 4 + 20. Height div 6 + 20. 16. 16 # 41; ;
r3. = CreateRoundRectRgn
# 40; Width div 4 * 3 - 20. Height div 6 - 20.
Width div 4 * 3 + 20. Height div 6 + 20. 16. 16 # 41; ;
r4. = CreateEllipticRgn # 40; Width
div 10. Height div 9 * 3.
Width div 10 * 9. Height div 9 * 4 # 41; ;
try
CombineRgn # 40; r. r. r2. RGN_XOR # 41; ;
CombineRgn # 40; r. r. r3. RGN_XOR # 41; ;
CombineRgn # 40; r. r. r4. RGN_XOR # 41; ;
finally
DeleteObject # 40; r2 # 41; ;
DeleteObject # 40; r3 # 41; ;
DeleteObject # 40; r4 # 41; ;
end;
SetWindowRgn # 40; Handle. r. TRUE # 41; ;
finally
DeleteObject # 40; r # 41; ;
end;
end;


Нерасмотренной з першої групи функцій залишилася тільки ExtCreateRgn. Я зараз не буду на ній загострюватися, скажу тільки, що на пару з функцією GetRegionData, вона може стати в нагоді, наприклад, для збереження і завантаження регіонів в файл / з файлу.

Регіони потрібні не тільки для того, щоб різати дірки в формах. Іноді вони можуть виявитися досить корисним інструментом саме в своєму "рідному" як, тобто для відтворення на екрані досить складних геометричних фігур. Наприклад, для виведення карт, що представляють собою сукупність ламаних ліній, побудованих по масивах точок. Створити таку лінію нам уже не складе труднощів, пора розібратися, як її показати користувачеві.

З функцій відтворення дві перші нам уже смутно знайомі: вони роблять те саме, що робить параметр FillMode (ALTERNATE / WINDING) для функцій CreatePolygonRgn і CreatePolyPolygonRgn. GetPolyFillMode отримує заданий для зазначеного контексту режим заливки, а SetPolyFillMode встановлює його. Просто на цей раз мова йде не про створення регіону, а всього лише про його відображенні. Встановлене значення матиме сенс для всіх функцій, що заливають регіон, тобто PaintRgn і FillRgn, при цьому сам регіон залишиться таким, яким він і був створений, а ось розфарбований буде по різному, в тому випадку, якщо він складається з декількох пересічних регіонів. Для простих регіонів типу прямокутника або еліпса установка даного значення нічого не змінює.

Отже. Давайте терміново що-нить створимо і намалюємо. Можна, звичайно, зробити це в одній функції, наприклад в OnCreate, але тоді зображення буде дуже недовговічним - до першої перемальовування форми. Тому зробимо інакше: оголосимо private property fRgn, в OnCreate його инициализируем, в OnPaint будемо його відображати, а в OnDestroy - знищимо. Код методів представлений нижче:

procedure TForm1. FormCreate # 40; Sender. TObject # 41; ;
begin
fRgn. = CreateEllipticRgn # 40; 10. 10. 200. 200 # 41; ;
end;

procedure TForm1. FormDestroy # 40; Sender. TObject # 41; ;
begin
DeleteObject # 40; fRgn # 41; ;
end;

procedure TForm1. FormPaint # 40; Sender. TObject # 41; ;
begin
Canvas. Brush. Color. = ClBlack;
PaintRgn # 40; Canvas. Handle. fRgn # 41; ;
end;


Слід пам'ятати, що Функції відтворення регіонів завжди працюють з кольором,
зазначеним в Canvas.Brush.Color. Навіть малюючи бордюр (frame) використовуватися буде не колір Canvas.Pen, що, в общем-то, видається більш логічним, а колір Canvas.Brush.

Нічого такий вийшов кружечок. Похоронного вигляду. Давайте зробимо його більш життєрадісним, і заодно розберемося, як працює FrameRgn:

procedure TForm1. FormPaint # 40; Sender. TObject # 41; ;
var
bmp. TBitmap;
begin
bmp. = TBitmap. Create;
try
bmp. LoadFromFile # 40; 'C: WINDOWS \ Блакитні мережива 16.bmp' # 41; ;
Canvas. Brush. Bitmap. = Bmp;
PaintRgn # 40; Canvas. Handle. fRgn # 41; ;
Canvas. Brush. Color. = ClBlack;
FrameRgn # 40; Canvas. Handle. fRgn. Canvas. Brush. Handle. 2. 2 # 41; ;
finally
Canvas. Brush. Bitmap. = Nil;
bmp. Free;
end;
end;


У мене вийшла така ось картинка:

Стаття робота з регіонами

Наскільки я можу судити, функції FillRgn і PaintRgn відрізняються один від одного тільки тим, що перша дозволяє вказати дескриптор кисті, не пов'язаної з поточним canvas'ом. Сумнівна фіча з точки зору Дельфах, тому що маніпулювати з поточним кольором кисті кинувся всяко легше, ніж створювати окремий екземпляр класу TBrush. Ось, власне, і все про відображенні. Примітно те, що для того, щоб намалювати регіон нам не потрібно знати, що він із себе представляє. Ми просто передаємо дескриптор однієї і тієї ж процедурою, а вона відобразить на екрані коло, овал, трикутник, зірку Давида - все, що завгодно.
Функції, представлені в розділі інше нічого особливо цікавого з себе не представляють, і, в общем-то, інтуїтивно зрозумілі. тому Рассотрім лише деякі з них.

Зробимо це на прикладі. Давайте спантеличив можливістю тягати мишкою по всій формі коло, створений в попередньому прикладі. Що нам потрібно. По-перше, запам'ятовувати, де почалося перетягування (fStartX, fStartY). По-друге, прапор (fDragging), який вказує, що користувач дійсно перетсківает наш регіон, а не просто ганяє з екрану мух. По-третє, треба дізнатися, ткнув він на регіон, а не мимо (PtInRegion). По-четверте, треба рухати регіон в міру того, як він рухає миша (OffsetRgn). Ось, мабуть, і все. На цей раз текст модуля приведу повністю. Єдине що там варто згадати - це властивість DoubleBuffered. Воно виставлено в TRUE, тому що інакше з'являється мерехтіння. Отже.

uses
Windows. Messages. SysUtils. Variants. Classes. Graphics.
Controls. Forms. Dialogs;

type
TForm1 = class # 40; TForm # 41;
procedure FormCreate # 40; Sender. TObject # 41; ;
procedure FormDestroy # 40; Sender. TObject # 41; ;
procedure FormPaint # 40; Sender. TObject # 41; ;
procedure FormMouseDown # 40; Sender. TObject; Button. TMouseButton; Shift.
TShiftState; X. Y. Integer # 41; ;
procedure FormMouseMove # 40; Sender. TObject; Shift.
TShiftState; X. Y. Integer # 41; ;
private

fDragging. boolean;
fRgn. hRGN;
fStartX.
fStartY. integer;
public

end;

var
Form1. TForm1;

procedure TForm1. FormCreate # 40; Sender. TObject # 41; ;
begin
fRgn. = CreateEllipticRgn # 40; 10. 10. 200. 200 # 41; ;
fDragging. = FALSE;
DoubleBuffered. = TRUE;
end;

procedure TForm1. FormDestroy # 40; Sender. TObject # 41; ;
begin
DeleteObject # 40; fRgn # 41; ;
end;

procedure TForm1. FormPaint # 40; Sender. TObject # 41; ;
var
bmp. TBitmap;
begin
bmp. = TBitmap. Create;
try
bmp. LoadFromFile # 40; 'C: WINDOWS \ Блакитні мережива 16.bmp' # 41; ;
Canvas. Brush. Bitmap. = Bmp;
PaintRgn # 40; Canvas. Handle. fRgn # 41; ;
Canvas. Brush. Color. = ClBlack;
FrameRgn # 40; Canvas. Handle. fRgn. Canvas. Brush. Handle. 2. 2 # 41; ;
finally
Canvas. Brush. Bitmap. = Nil;
bmp. Free;
end;
end;

procedure TForm1. FormMouseDown # 40; Sender. TObject; Button.
TMouseButton; Shift. TShiftState; X. Y. Integer # 41; ;
begin
if # 40; Button = mbLeft # 41; and # 40; PtInRegion # 40; fRgn. X. Y # 41; # 41; then begin
fDragging. = TRUE;
fStartX. = X;
fStartY. = Y;
end;
end;

procedure TForm1. FormMouseMove # 40; Sender. TObject; Shift.
TShiftState; X. Y. Integer # 41; ;
begin
if # 40; ssLeft in Shift # 41; and fDragging then begin
OffsetRgn # 40; fRgn. X - fStartX. Y - fStartY # 41; ;
fStartX. = X;
fStartY. = Y;
Refresh;
end;
end;

procedure TForm1. FormMouseUp # 40; Sender. TObject; Button. TMouseButton;
Shift. TShiftState; X. Y. Integer # 41; ;
begin
fDragging. = FALSE;
end;


Як бачимо, абсолютно нічого складного в роботі з регіонами немає. А ось можливості вони дають досить цікаві. Фігуру будь-якої форми можна розфарбувати як завгодно (в т.ч. і бітмапами), вивести на екран, визначити, чи входить точка (X, Y) в цю фігуру, рухати її, і багато ще іншого. Для повноти картини нам залишилося тільки навчитися зберігати регіони на диск і зчитувати назад.

Збереження і завантаження регіону

Як вже говорилося на самому початку, всі дані про регіон зберігаються в структурі RGNDATA. Згадувалася також і функція, що дозволяє цю структуру отримати: GetRegionData. У цій функції є приємна особливість: якщо в третій параметр передати nil, то вона поверне розмір пам'яті, необхідний для збереження регіону.

procedure SaveRegion # 40; FileName. string # 41; ;
var
s. TStream;
size. cardinal;
data. pointer;
begin
s. = TFileStream. Create # 40; FileName. fmCreate # 41; ;
try
size. = GetRegionData # 40; fRgn. SizeOf # 40; RGNDATA # 41 ;. nil # 41; ;
data. = GlobalAllocPtr # 40; GPTR. size # 41; ;
try
GetRegionData # 40; fRgn. size. data # 41; ;
s. Write # 40; data ^. size # 41; ;
finally
GlobalFreePtr # 40; data # 41; ;
end;
finally
s. Free;
end;
end;


Аналогічним чином можна і прочитати записаний на диск регіон:

function LoadRegion # 40; FileName. string # 41 ;. hRGN;
var
data. PRgnData;
s. TStream;
begin
s. = TFileStream. Create # 40; FileName. fmOpenRead # 41; ;
try
data. = GlobalAllocPtr # 40; GPTR. s. size # 41; ;
try
s. Read # 40; data ^. s. Size # 41; ;
Result. = ExtCreateRegion # 40; nil. s. Size. data ^ # 41; ;
finally
GlobalFreePtr # 40; data # 41; ;
end;
finally
s. Free;
end;
end;


Ось на цьому, мабуть, можна закінчити цей огляд, аж ніяк не претендує на вичерпність.
Хочеться сподіватися, що когось цей опус спонукає на створення чогось нитка хорошого, або просто заощадить кілька годин повзання по Win32 SDK.Все питання ви можете задати за цим милу. по цій асьці: 89576939. але найкраще, звичайно, ось в цьому форумі.
Хай щастить.


best regards,
x77.