Політ: книга програмера  
0. Вступ
1. Особливості політівських програм
2. Події та їх обробники
3. Елементи керування: принципи, Button, Label, Check, Radio,
InpLine, PicCtl, Header, Progress, Track, TimerCtl, Notebook,
SpinEdit, PopupBtn, Switcher, MainMenu, Splitter, Memo, ListBox

Сайт Польоту
 

Події та їх обробники

Отже, обробники подій. Ми вже бачили три з них - OnPaint, OnClose, OnInit. Крім них, існують іще три - OnIdle, OnMessage, OnKey. Давайте зробимо ще одну програмку, в якій будуть обробники для всіх події. Знову запускаємо Політ, Майстер програм, даємо програмі нову назву, хай це буде TestApp2. Тепер вибираємо другу сторінку - "Обробники" - і відмічаємо там усі події. Клікаємо "Зробити заготовку", виходимо з Польоту та запускаємо Борланд-паскаль. Там так само, як і нашу першу програму, додаємо цю (TestApp2) у секцію Uses головної програми Polit.pas. Можна запустити TestApp2 з-під Польоту - вона виглядатиме так само, як і наша найперша програмка. Обробники нових подій включені в неї, але вони не роблять нічого.

Що ж, давайте знайомитися з цими обробниками ближче - і з новими, і з тими, що ми уже бачили...




Procedure OnPaint(Me:PProg);

Відповідає за малювання вікна. Точніше, власне вікно - порожнє вікно з рамкою - малює сам Політ; після цього він викликає цей обробник, щоб він намалював вміст вікна (частіше за все елементи керування у вікні). Для прикладу хай наша у вікні нашої програми буде синій крадрат. Знаходимо обробник OnPaint і відразу після коментарів пишемо рядок:
Gfx.Rectangle(X1+10, Y1+10, X1+100, Y1+50, clBlue);
Цей маленький рядок уже міг викликати у вас купу запитань :). По-перше, на рахунок Gfx.Rectangle. Борланд-Паскаль дозволяє перед назвою процедури (чи функції) ось так через крапку писати назву модуля, який містить цю процедуру. Я пропоную користуватися саме таким записом - так відразу видно, до якого модуля належить процедура, а модулів у Польоті бага-ато :). По-друге, змінні X1, Y1, X2, Y2 описані у записі Me^.ClientRect типу TRect:
Type
  TRect=Record
    X1, Y1, X2, Y2: Integer
  End;
ClientRect - це координати нашого вікна (точніше, його порожньої області, без рамки та заголовку, так званої клієнтської області). Отже, ці координати треба розуміти так: лівий верхній кут прямокутника на 10 пікселів правіше і на 10 пікселів нижче лівого верхнього краю вікна, розмірами 40 на 40 пікселів. Запустіть програмку, ви зрозумієте. Ну і нарешті clBlue - це константа, описана в модулі Gfx; вона означає (світлий) синій колір. Якщо хочете, можете прописати тут інший колір - clPurple, clGreen, clRed, clWhite...

Щоб краще зрозуміти координати, пропоную ще один приклад. Виправте виклик Gfx.Rectangle на ось такий:
Gfx.Rectangle(X1+10, Y1+10, X2-10, Y2-10, clBlue);
Запустіть і подивіться, що вийшло. Розмір нашого прямокутника залежить від розмірів вікна! Таким самим фокусом користуються деякі політівські програми, наприклад Записник.



Procedure OnIdle(Me:PProg);

Ця подія виникає постійно, по черзі для всіх працюючих політівських програм. Власне, завдяки цій події (та її обробникам) політівські програми можуть працювати паралельно. Хай нашій програмі треба зробити якусь досить довгу (і нудну :) роботу. Скажімо, дискету форматувати. Щоб зробити це, дозволяючи паралельно працювати іншим програмам, нам треба виконувати цю роботу в обробнику OnIdle, один "крок" за один виклик. Причому крок цей має бути якомога меншим, бо Політ під час роботи обробника не може переключати задачі (а також, до речі, слідкувати за рухом курсора мишки) - він може робити це лише між викликами обробників.

Простий приклад. Зробимо з нашої програми щось типу FirstApp (Тестової програми) - хай у її вікні мерехтять випадкові різнокольорові лінії. Знаходимо обробник OnIdle і вписуємо прямо під коментарем таку шнягу:
Gfx.Line(X1+Random(X2-X1), Y1+Random(Y2-Y1), 
  X1+Random(X2-X1), Y1+Random(Y2-Y1), Random(256));
Запускаємо. Фокус-покус! Але якщо над нашим вікном поставити курсор мишки, лінії будуть його затирати. Щоб цього не було, перед малюванням лінії треба повідомити кернел, що ми збираємося малювати:
Kernel.BeginPaint(Me^.ClientRect);
А після малювання лінії, відповідно, сказати що ми закінчили:
Kernel.EndPaint;
Взагалі, якщо наша програма буде малювати щось напряму (засобами модуля Gfx) не в обробнику OnPaint, процедури малювання треба завжди оточувати викликами Kernel.BeginPaint ... Kernel.EndPaint. Елементів керування, які ми розглянемо пізніше :), це не стосується (вони самі за цим слідкують).

Взагалі, розбивати довгий і складний процес на такі маленькі кроки - справа досить нетривіальна. Якщо не забуду, я десь у кінці напишу окремий розділ про це.



Function OnMessage(Me, Sender:PProg; Msg:String; Data:Pointer): Pointer;

Ця подія виникає, коли якась із працюючих програм надіслала нашій програмі повідомлення. Me - як завжди, вказівник на нашу програму. Sender - вказівник на програму, яка надіслала нам оце повідомлення. До речі, String(Sender^.Name^) - назва цієї програми. Msg - текст повідомлення. Data - на параметри повідомлення, якщо вони потрібні. Обробник цієї події зроблений як функція, вона також повертає вказівник на результати, якщо це потрібно. Про вигляд тексту повідомлення (Msg) мають знати обидві програми, і та, що надсилає повідомлення, і та, що отримує його - так само, як про розуміння параметру Data та значення, що повертає функція OnMessage.

До речі, повідомлення надсилаються так:
Res:=Kernel.SendMessage(Me, TargetProg, 'Message text', Data);
Тут TargetProg - вказівник на програму, якій ми шлемо повідомлення; Data - вказівник на якісь параметри повідомлення, якщо вони потрібні; функція повертає також вказівник на якісь результати, якщо вони потрібні. Вказівник на якусь програму можна отримати за її назвою:
TargetProg:=Kernel.FindProg('ProgName');
Для прикладу пропоную "поспілкуватися" з Навігаційною панеллю (Navi). Вона розуміє повідомлення GetDir і повертає вказівник на свій поточний шлях. Хай наша програмка при запуску питає у Наві шлях та показує його у заголовку свого вікна. Шукаємо обробник OnInit та заводимо в ньому такі змінні:
Var
  TempProg: PProg;
  TempPtr: Pointer;
Далі заміняємо рядок
D.AssignPStr('Тестова програмка', WinTitle);
на ось таку конструкцію:
TempProg:=Kernel.FindProg('Navi');
If TempProg<>Nil Then
Begin { Navi запущена }
  TempPtr:=Kernel.SendMessage(Me, TempProg, 'GetDir', Nil);
  D.AssignPStr(String(TempPtr^), WinTitle)
End
Else D.AssignPStr('Тестова програмка', WinTitle);
Пограйтеся з програмкою: відкрийте Навігаційну панель, а потім міняйте у ній шлях та запускайте TestApp2.



Function OnKey(Me:PProg; Key,ExtKey:Char): Boolean;

Подія виникає при натисненні клавіші на клавіатурі. Перед тим, як передавати клавішу активному елементу керування, Політ викликає обробник цієї події. Таким чином легко робити гарячі клавіші, або реалізовувати керування в іграх. Key та ExtKey типу Char - це результати одного чи двох викликів ReadKey. Якщо значення Key між #1 та #255, то це якась якась алфавітно-цифрова клавіша; якщо ж Key=#0, то юзер натиснув спеціальну клавішу типу F1, і її код в ExtKey. OnKey - функція булівського типу; від нас вимагається повернути True, якщо програма наша "зрозуміла" і обробила клавішу, і False у протилежному випадку (тоді клавіша буде передана активному елементу керування, якщо такий є).

Для прикладу зробимо так, щоб наша програма реагувала на гарячі клавіші. Хай по F2 заголовок вікна міняється на "Збереження файла", а по F3 - на "Завантаження файла":
Function OnKey(Me:PProg; Key,ExtKey:Char): Boolean;
Begin
  OnKey:=False;
  If (Key=#0) And (Extkey=#65) Then
  Begin
    Kernel.SetWindowCaption(Me, 'Збереження файла');
    OnKey:=True
  End;
  If (Key=#0) And (Extkey=#66) Then
  Begin
    Kernel.SetWindowCaption(Me, 'Завантаження файла');
    OnKey:=True
  End
End;


Procedure OnClose(Me:PProg);

Ця процедура виникає, коли нашу програму збираються закривати. Наприклад, якщо юзер клікнув на кнопці закриття у заголовку вікна. Або якась інша добра програма вирішила прибити нашу викликом Kernel.CloseProg :) - Менеджер задач іноді робить так. Тут нам треба позвільняти всю пам'ять, зайняту нашою програмою (скоріш за все в OnInit) та якщо треба, виконати ще якісь завершувальні дії. Увага, все це треба робити до рядка, що звільняє Me^.Data^. Наша тестова програмка нічого такого не робить, тому вставленого Майстром програм рядка досить. Для практики можемо хіба що попищати при закритті:
Procedure OnClose(Me:PProg);
Begin
   Sound(700);
   Delay(700);
   NoSound;
   FreeMem(Me^.Data, SizeOf(TTestApp2Data))
End;


Procedure OnInit(Me:PProg);

OnInit, наша стара знайома - запуск програми. Розберемось, що тут повинно бути зроблено, і що зробив для нас Майстер програм. Із самого початку іде ряд присвоєнь:
Me^.OnPaint:=OnPaint;
Me^.OnIdle:=OnIdle;
Me^.OnMessage:=OnMessage;
Me^.OnKey:=OnKey;
Me^.OnClose:=OnClose;
Нагадую, Me - це вказівник на запис нашої програми у ядрі. Тут ми зберігаємо в цьому записі адреси обробників нашої програми (інакше ядро не зможе їх викликати). Зазвичай це робить Майстер програм. Але якщо вам захочеться після створення заготовки додати ще якийсь обробник - не забудьте після написання його тексту ось тут передати ядру його адресу.

Далі наша програма готує параметри свого вікна, створює його і малює на екрані:
D.AssignPStr('Тестова програмка', WinTitle);
MinX:=350;
MinY:=240;
Kernel.InitWindow(Me, 'BlueWin', [wbMin, wbMax, wbClose]);
Kernel.DrawWindow(Me);
Спочатку ми присвоюємо значення заголовку вікна (WinTitle) і визначаємо його мінімальні розміри (MinX, MinY). Все це - змінні запису нашої програми; не забувайте, що це робиться всередині блоку With Me^ Do. Далі ми отримуємо пам'ять під змінні нашої програми:
GetMem(Data, SizeOf(TTestApp2Data));
Після цього ми можемо відкривати блок приєднання With TTestApp2Data(Data^) Do і присвоювати нашим змінним значення.

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