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

Сайт Польоту
 

Елементи керування
Змійка - Progress (модуль Ctl)

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

Фактично змійка показує значення якоїсь змінної, яке звичайно поступово збільшується від деякого мінімального до деякого максимального значення. Простий приклад - нам треба скопіювати 10 файлів; тоді у нас мінімальне значення буде 0 (на початку - ми нічого не скопіювали), максимальне - 10, і після копіювання кожного файла значення змінної буде збільшуватись на одиницю. Змійка сама перераховуватиме ці значення змінної в екранні координати: при мінімальному значенні змінної вона буде виглядати порожньою, при максимальному - повністю заповненою, при проміжних значеннях буде заповнена відповідна її частина.

На відміну від Windows'них ProgressBar'ів, політівська змійка має додатковий стан, що називається невизначеним (теж можна побачити у Файл-менеджері, перед початком файлової операції).




Створити змійку
Function Progress_Create(Prog:PProg;
  VMin,VMax,VValue:Integer;
  VKind:TProgressKind): PControl;
  • VMin, VMax - мінімальне та максимальне значення.
  • VValue - поточне значення.
  • VKind - тип змійки. Може бути pkDeterm (звичайний вигляд) або pkIndeterm (невизначений вигляд). Невизначений вигляд змійки використовується, коли ми не можемо оцінити, скільки кроків займе виконання нашої роботи, але хочемо показати юзеру, що робота іде. Приклад можна побачити в тому ж Файл-менеджері: перед початком файлової операції, коли він складає список файлів, змійка знаходиться у невизначеному стані. Виглядає це так: по змійці біжать діагональні риски. (Дехто вважає, що називати їх "діагональними" неправильно; добре, хай будуть "нахилені під кутом у 45 градусів" :). Причому біжать вони автоматично, поки тип змійки дорівнює pkIndeterm.


Намалювати змійку
Procedure Progress_Paint(Me:PControl; 
  VX1, VY1, VX2:Integer);
Висота змійки - 20 пікселів.



Установити (нові) параметри змійки
Procedure Progress_SetParams(Me:PControl; 
  VMin,VMax,VValue:Integer);
VMin, VMax, VValue - відповідно нові мінімальне, максимально та поточне значення.



Установити (нове) значення змінної
Procedure Progress_SetValue(Me:PControl; 
  VValue:Integer);
VValue - нове поточне значення змінної. Один із основних прийомів роботи зі змійкою.



Збільшити значення на 1
Procedure Progress_Step(Me:PControl);
Другий основний прийом роботи.



Установити тип змійки
Procedure Progress_SetKind(Me:PControl;
  VKind:TProgressKind);
VKind - новий тип змійки (pkDeterm - звичайний або pkIndeterm - невизначений). З момента установки типу pkIndeterm змійка починає "бігти" автоматично, поки її тип не буде встановлений у pkDeterm. Зроблено це заради зручності: можна поставити тип змійки pkIndeterm і починати робити нашу роботу, не відволікаючись на змійку, а вона собі бігтиме автоматично.



Приклад на змійку буде досить нетривіальним. Давайте зробимо програмку, що рахуватиме входження заданого символа в текстовий файл. Під час "перекопування" файла програмка і показуватиме змійку, що буде поступово заповнюватись. Заодно цей приклад проілюструє використання обробника OnIdle, принципи політівської багатозадачності і побудову політівських програм взагалі (на відміну від ДОСівських на Delphi'вських).

Отже, нам потрібно зробити прогарму, що виконуватиме достатньо тривалу роботу (це можуть бути секунди або навіть десятки секунд, залежно від розмірів файла). Але програму ми пишемо під багатозадачну систему - треба щоб під час її роботи не "завмирало" все інше у Польоті - інші програми, курсор мишки...

Загальний принцип такий. Нам роботу цю треба робити не всю відразу, а розбити її на багато викликів процедури-обробника (частіше за все OnIdle), і за один виклик робити досить маленький "шматочок" роботи. Тоді між викликами нашого обробника Політ і іншим програмам даватиме попрацювати, і за рухом мишки стежитиме, і робитиме ще багато чого всередині себе :) - і все це паралельно із пошуком у нашій програмці.

Значить, наша програмка працюватиме приблизно так. Нам будуть потрібні два поля вводу - для імені файла та символа, який ми будемо шукати, кнопка "Почати пошук", змійка та мітка для виведення результатів. Спочатку програма не робитиме нічого; пошук почнеться по кліку на кнопкі "Почати пошук". Як нам розбити цей пошук на маленькі "шматочки"? Перше, що спадає на думку - обробляти за один виклик обробника один рядок нашого файла. Так і зробимо. І ще одне питання - як вибрати межі та змінну для змійки? Так само, один крок - один рядок, тут зробити не вийде, бо ми спочатку не знаємо, скільки рядків у нашому файлі. Тут можна викрутитися так - при відкритті файла узнати його розмір у байтах і працювати з ним: призначити його максимальним значенням (а мінімальним - 0), а поточним значенням буде сума довжин уже оброблених рядків.

Тоді логіка програми буде такою. По кліку на кнопці "Почати пошук" ми відкриваємо файл, виставляємо параметри змійки та "підключаємо" наш обробник OnIdle. Він за один виклик буде читати з файла один рядок, шукати в ньому наш символ, додавати розмір прочитаного рядка до поточної кількості оброблених байт та ставитиме це значення як поточне для змійки.

Будемо починати. Беремо Майстер програм, на сторінці обробників відмічаємо OnIdle, робимо заготовку TestPro.
Описуємо змінні. Зверніть увагу, тут у нас будуть змінні не лише для елементів керування. Всі інші змінні програми треба також описувати тут, у записі TTestProData:
TTestProData=Record
  InpFName, InpChar: PControl;
  BtnStart: PControl;
  ProGress: PControl;
  LabResult: PControl;
  F: Text; { файл }
  C: Char; { символ, який ми будемо шукати }
  CurSize: LongInt; { поточна кількість оброблених байт }
  Occurences: Integer; { кількість входжень зразка }
End;
Далі створюємо елементи керування:
InpFName:=Ctl.InpLine_Create(Me, 'kernel.pas', Nil);
InpText:=Ctl.InpLine_Create(Me, ';', Nil);
BtnStart:=Ctl.Button_Create(Me, 'Почати пошук',
  Nil, ppLeft, OnStartClick);
ProGress:=Ctl.Progress_Create(Me, 0, 10, 0);
LabResult:=Ctl.Label_Create(Me, '-', Nil);
Тепер знайдіть в обробнику OnInit рядок Me^.OnIdle:=OnIdle та витріть його. Молодці, добре :). З самого початку цей обробник працювати не повинен, ми його аналогічним присвоюванням "підключимо" далі (Політ дозволяє робити такі фокуси з обробниками).

Далі пишемо обробник OnStartClick:
Procedure OnStartClick(Me:PProg; Control:PControl);
Var
  TempStr, FName: String;
Begin
  With Me^, TTestProData(Data^) Do
  Begin
    FName:=Ctl.InpLine_GetText(InpFName);
      { чи існує файл? }
    If Not D.FileExist(FName) Then Exit;
    TempStr:=Ctl.InpLine_GetText(InpChar);
    If Length(TempStr)<>1 Then Exit;
    C:=TempStr[1];
    Assign(F, FName);
    Reset(F);
    Ctl.Progress_SetParams(ProGress, 0, FileSize(F), 0);
    CurSize:=0;
    Occurences:=0;
  End;
  Me^.OnIdle:=OnIdle
End;
Він працює точно так, як ми планували раніше, але попередньо перевіряє, чи все правильно (чи існує файл, чи введено один символ для пошуку). Якщо ж щось не так, тут можна навіть видати повідомлення у мітку LabResult, або зробити запис у файл журналу, наприклад:
Log.BugMsg('TestPro', 'OnStartClick',
  'File not found: '+FName);
Про журнал ми, як водиться, будемо говорити пізніше.

Беремося за найцікавіше - обробник OnIdle, який власне і виконуватиме пошук.
Procedure OnIdle(Me:PProg);
Var
  S: String;
  V: Byte;
Begin
  With Me^, TTestProData(Data^) Do
  Begin
    If Not EOF(F) Then
    Begin { обробляємо поточний рядок }
      ReadLn(F, S);
      For V:=1 To Length(S) Do
        If S[V]=C Then Inc(Occurences);
      Ctl.Label_SetCaption(LabResult, D.St(Occurences));
      Inc(CurSize, Length(S)+2);
      Ctl.Progress_SetValue(ProGress, CurSize)
    End
    Else Begin { кінець файла, закінчуємо }
      Close(F);
      Me^.OnIdle:=Kernel.DummyProc
    End
  End
End;
Власне кажучи, програма готова.

Кому цікаво, можна зробити так, щоб програмка ця рахувала входження у файл не окремого символа, а цілого рядка - користі від неї відразу стане більше. Для цього треба буде лише переробити підрахування Occurences в OnIdle. Ну і прибрати перевірку на довжину поля в 1 символ :).