Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

DiVM / OOP / 12_116608_1_51491

.pdf
Скачиваний:
26
Добавлен:
11.05.2015
Размер:
6.45 Mб
Скачать

procedure TMainForm.EditButtonClick(Sender: TObject); var

Alarm: TAlarm; SavedIndex: Integer;

begin

AlarmDetailsForm := TAlarmDetailsForm.Create(Self); try

// Получить выбранный будильник

with AlarmListBox do Alarm := TAlarm(Items.Objects[ItemIndex]);

//Установить управляющие элементы диалога в соответствии с

//параметрами будильника

AlarmDetailsForm.SetData(Alarm); // Выполнить диалог

if AlarmDetailsForm.ShowModal = mrOK then begin

// Получить из диалога новые параметры будильника

AlarmDetailsForm.GetData(Alarm); with AlarmListBox do

begin

//Запомнить номер выбранного в списке элемента

SavedIndex := ItemIndex;

//Изменить текст элемента

//При этом элемент перестает быть выбранным

Items.Strings[ItemIndex] := Alarm.GetAlarmStr;

//Восстановить номер выбранного в списке элемента

ItemIndex := SavedIndex;

end; end;

finally AlarmDetailsForm.Free;

end; end;

Этот метод создает окно диалога Alarm Details, инициализирует его компоненты данными из выбранного в списке объекта будильника, а затем выполняет диалог в монопольном режиме. Если диалог завершился щелчком на кнопке OK, то данные из окна диалога переносятся обратно в объект будильника и соответственно изменяется отображаемая в блоке списка строка. Так как в результате последнего действия в списке пропадает полоса выбора (свойство ItemIndex получает значение -1), номер выделенного элемента предварительно сохраняется в локальной переменной SavedIndex, а затем восстанавливается.

Шаг 28. Осталось создать обработчик события OnClick в компоненте DeleteButton:

procedure TMainForm.DeleteButtonClick(Sender: TObject); begin

with AlarmListBox do begin

//Разрушить объект будильника

Items.Objects[ItemIndex].Free;

//Удалить из списка соответствующую объекту строку

Items.Delete(ItemIndex);

end; end;

Метод DeleteButtonClick удаляет объект будильника и соответствующую ему строку в списке.

Обработчики событий для всех кнопок заданы, однако не спешите запускать приложение. Необходимо позаботиться о том, чтобы кнопки Edit... и Delete были доступны или недоступны в зависимости от того, выделен в списке элемент или нет. Как бы это сделать попроще? Первое решение, которое напрашивается — это вставить необходимые проверки в обработчики событий кнопок. Это неплохое решение, но оно больше подходит тем, кто привык решать задачу в лоб. Мы пойдем другим путем, воспользовавшись событием OnIdle

объекта Application.

381

В объекте Application происходит событие OnIdle в период простоя программы, например во время ожидания пользовательского ввода. Благодаря этому событию программа может выполнять некоторую фоновую работу, которая в нашем случае заключается в управлении состоянием кнопок.

Для создания обработчика события OnIdle объекта Application воспользуемся уже знакомым вам компонентом ApplicationEvents (см. главу 8).

Шаг 29. Поместите в форму компонент ApplicationEvents, дайте ему одноименный идентификатор и создайте обработчик события OnIdle:

procedure TMainForm.ApplicationEventsIdle(Sender: TObject; var Done: Boolean);

begin

EditButton.Enabled := AlarmListBox.ItemIndex <> -1; DeleteButton.Enabled := AlarmListBox.ItemIndex <> -1;

Done := True; // предотвращает непрерывную генерацию события OnIdle end;

В передаваемом по ссылке параметре Done метод возвращает результат своей работы. Значение True показывает, что метод нужно вызывать не постоянно в течении простоя приложения, а только по одному разу в начале каждого периода простоя.

А сейчас выполните компиляцию, запустите программу и тщательно протестируйте работу главной формы (рисунок 9.46).

Рисунок 9.46. В этом окне создается список будильников

Будильники можно создавать, добавлять, удалять. Нам осталось сделать последний шаг — заставить будильники "звонить". Для этого нужно периодически вызывать метод CheckTime у каждого помещенного в список объекта TAlarm. Периодические по времени действия выполняются с помощью таймера, о котором мы дальше и поговорим.

9.4. Законченное приложение для выдачи сигналов в заданные моменты времени

9.4.1. Таймер

Таймер — это системный генератор событий, который периодически сообщает программе о завершении заданного промежутка времени. Интервал времени между событиями таймера может устанавливаться в диапазоне от 1 до 65535 миллисекунд. Используя таймер, учитывайте, что интервалы между этими событиями оказываются неточными из-за накладных расходов механизма обработки событий Windows.

382

В библиотеке VCL прием событий таймера обеспечивает компонент Timer. Он расположен в палитре компонентов на вкладке System (рисунок 9.47). Им мы и воспользуемся для "оживления" будильников в приложении Alarms.

Рисунок 9.47. Компонент Timer

Шаг 30. Активизируйте форму MainForm. Затем поместите в нее компонент Timer (рисунок 9.48). Если хотите, дайте ему любое имя.

Рисунок 9.48. Компонент Timer — на форме

Шаг 31. Интервал времени между событиями таймера задается в миллисекундах как значение свойства Interval. Изначально интервал равен 1000 миллисекунд (1 секунда). Частота контроля будильников должна быть два раза в секунду, поэтому установите свойство Interval в значение 500.

Шаг 32. Через заданные в свойстве Interval промежутки времени в компоненте Timer происходит событие OnTimer (единственное событие этого компонента). Для контроля за будильниками нам нужно создать обработчик этого события:

procedure TMainForm.TimerTimer(Sender: TObject); var

I: Integer; begin

for I := 0 to AlarmListBox.Items.Count - 1 do with AlarmListBox.Items.Objects[I] as TAlarm do

CheckTime;

end;

Смысл выполняемых действий очевиден: у каждого объекта в списке будильников вызывается метод CheckTime. Таким образом, каждый будильник периодически проверяет свое время и, если нужно, выдает предупреждение (рисунок 9.49).

383

Рисунок 9.49. Когда будильник срабатывает, звучит сигнал и на экран выдается сообщение

Выполните компиляцию и запустите программу, чтобы проверить работу всех элементов программы. Ура! Все работает, терпение и упорство вознаграждены.

9.4.2. Файлы настроек

Программа Alarms имеет один существенный недостаток: после его завершения все установленные будильники теряются, так что при следующем запуске программы их приходится создавать снова. Для решения этой проблемы необходимо, чтобы между сеансами работы программы будильники хранились в конфигурационном файле на диске.

Сохранение и восстановление конфигурации осуществляется в Windows с помощью так называемых файлов настроек. Файл настроек (initialization file) — это текстовый файл, состоящий из секций. Секция начинается с имени, заключенного в квадратные скобки. В каждой секции содержатся определения некоторых связанных по смыслу параметров, представленные в виде пар Имя=Значение. Примером файла настроек может служить файл настроек проекта в системе Delphi. В нашем проекте это файл Alarms.dof.

Структуру файла настроек для программы Alarms выберем так, чтобы каждому будильнику соответствовала отдельная секция. Число секций, т.е. будильников, будем хранить в параметре AlarmCount секции Global Options. Вот как могло бы выглядеть содержимое файла:

384

[Global Options]

AlarmCount=3

[Alarm1] Message=Dinner ! Time=13:00 PlaySound=1 Recurring=0

[Alarm2]

Message=Tennis training Time=16:00

PlaySound=1

Recurring=1

[Alarm3]

Message=My favourite TV-show...

Time=22:30

PlaySound=0

Recurring=8

Date=02/25/96

Чтение и запись файла настроек осуществляется с помощью объектов TIniFile (заметьте, они не являются компонентами). Класс TIniFile описан в модуле IniFiles. Этот модуль необходимо самостоятельно добавить в вызывающий модуль с помощью оператора uses. При создании объекта TIniFile ему в конструктор передается имя INI-файла. Позже это имя можно узнать, обратившись к свойству FileName. Если в имени файла маршрут не был указан, считается что INI-файл находится в каталоге системы Windows.

Чтение переменных из INI-файла выполняется с помощью описанных ниже методов. В этих методах название секции передается в параметре Section, имя переменной – в параметре Ident, а значение по умолчанию – в параметре Default.

ReadBool(const Section, Ident: string; Default: Boolean): Boolean — возвращает значение булевской переменной.

ReadInteger(const Section, Ident: string; Default: Longint): Longint — возвращает значение целочисленной переменной.

ReadString(const Section, Ident, Default: string): string — возвращает значение строковой переменной.

ReadSection(const Section: string; Strings: TStrings) — читает из заданной секции имена всех переменных и помещает их в объект класса TStrings.

ReadSectionValues(const Section: string; Strings: TStrings) — читает из заданной секции все пары Имя=Значение и помещает их в список. Для доступа к Значению по Имени в объектах класса TStrings существуют свойства-массивы Names и Values.

При чтении значений из INI-файла может оказаться, что заданный идентификатор или секция отсутствует. В этом случае ошибки не происходит, а функции ReadBool, ReadInteger и ReadString возвращают значение, переданное в параметре Default.

Кроме методов чтения существуют также методы записи переменных INI-файла, которые описаны ниже. В этих методах название секции передается в параметре Section, имя переменной – в параметре Ident, а значение переменной — в параметре Value.

WriteBool(const Section, Ident: string; Value: Boolean) – записывает в INI-файл булевское значение.

WriteInteger(const Section, Ident: string; Value: Longint) – записывает в INI-файл целочисленное значение.

WriteString(const Section, Ident, Value: string) – записывает в INI-файл строковое значение.

385

Если в момент записи значения оказывается, что заданные секция и (или) идентификатор отсутствуют, они создаются.

Удаление секций INI-файла осуществляется с помощью метода EraseSection, в который передается единственный параметр — название секции.

Шаг 33. Давайте воспользуемся описанными методами для сохранения и восстановления будильников в программе ALARMS. Работу по сохранению и восстановлению параметров одного будильника лучше всего поручить классу TAlarm. Для этого добавьте в его описание два новых метода — LoadFromIniFile и SaveToIniFile.

type

TAlarm = class

...

procedure LoadFromIniFile(IniFile: TIniFile; const Section: string); procedure SaveToIniFile(IniFile: TIniFile; const Section: string);

end;

Метод LoadFromIniFile предназначен для чтения из INI-файла полей объекта, а метод SaveToIniFile — для записи в INI-файл полей объекта. Секция INI-файла, с которой работают эти методы, передается в параметре Section.

Шаг 34. Наберите программный код методов в разделе implementation:

procedure TAlarm.LoadFromIniFile(IniFile: TIniFile; const Section: string); begin

with IniFile do begin

// Прочитать текст сообщения

MsgText := ReadString(Section, 'Message', 'Reminder !');

//Прочитать строковое значение времени и

//преобразовать его в формат TdateTime

DateTime := StrToTime(ReadString(Section, 'Time', TimeToStr(Time)));

//Прочитать состояние переключателя звука

PlaySound := ReadBool(Section, 'PlaySound', True);

//Прочитать значение периодичности

Recurring := ReadInteger(Section, 'Recurring', 0); if Recurring = 8 then

//Прочитать строковое значение даты и

//преобразовать его в формат TDateTime DateTime := StrToDate(

ReadString(Section, 'Date', DateToStr(Date))) + DateTime;

end; end;

procedure TAlarm.SaveToIniFile(IniFile: TIniFile; const Section: string); begin

with IniFile do begin

//Записать текст сообщения

WriteString(Section, 'Message', MsgText);

//Преобразовать время в строку и записать строку в INI-файл

WriteString(Section, 'Time', FormatDateTime('hh:mm', DateTime));

//Записать значение переключателя звука

WriteBool(Section, 'PlaySound', PlaySound); // Записать значение периодичности

WriteInteger(Section, 'Recurring', Recurring); if Recurring = 8 then

// Преобразовать дату в строку и записать строку в INI-файл

WriteString(Section, 'Date', DateToStr(DateTime)); end;

end;

Шаг 35. Перейдем теперь от сохранения и восстановления одного будильника к загрузке и восстановлению всего списка. Эти действия следует выполнять соответственно при создании и уничтожении главной формы программы, т.е. в событиях OnCreate и OnDestroy. Создайте форме MainForm обработчик события OnCreate и доработайте обработчик события OnDestroy (не забудьте подключить модуль IniFiles):

386

procedure TMainForm.FormCreate(Sender: TObject); var

IniFile: TIniFile; Alarm: TAlarm; AlarmCount, I: Integer;

begin

IniFile := TIniFile.Create('Alarms.ini'); try

// Прочитать число будильников

AlarmCount := IniFile.ReadInteger('Global Options', 'AlarmCount', 0); // Прочитать список будильников

for I := 1 to AlarmCount do begin

//Создать будильник

Alarm := TAlarm.Create;

//Прочитать параметры будильника из соответствующей секции

Alarm.LoadFromIniFile(IniFile, 'Alarm' + IntToStr(I));

//Добавить будильник в список

AlarmListBox.Items.AddObject(Alarm.GetAlarmStr, Alarm);

end; finally

IniFile.Free; end;

end;

procedure TMainForm.FormDestroy(Sender: TObject); var

IniFile: TIniFile; I: Integer;

begin

IniFile := TIniFile.Create('Alarms.ini'); try

// Записать число будильников

IniFile.WriteInteger('Global Options', 'AlarmCount', AlarmListBox.Items.Count);

// Записать список будильников

for I := 0 to AlarmListBox.Items.Count - 1 do with AlarmListBox.Items.Objects[I] as TAlarm do

// Записать параметры будильника в соответствующую секцию

SaveToIniFile(IniFile, 'Alarm' + IntToStr(I + 1)); finally

IniFile.Free;

for I := 0 to AlarmListBox.Items.Count - 1 do AlarmListBox.Items.Objects[I].Free;

end; end;

Вот пожалуй и все. Сохраните проект, выполните его компиляцию и запустите программу. Создайте несколько будильников, закройте программу, а затем запустите ее снова...

Будильники на месте. Кстати, утилиту Alarms можно поместить в папку StartUp, тогда она всегда будет у вас под рукой.

9.5. Многостраничные окна диалога

9.5.1. Страницы с закладками

В сложных компьютерных системах предусмотрена настройка десятков различных параметров, которые пользователь просто обязан установить. Это означает либо перегруженность диалоговых окон, либо слишком большое их количество. Решение этой проблемы заключается в организации многостраничных окон диалога. Они создаются с помощью компонента, который изображается в виде множества снабженных закладками страниц, называемых вкладками (рисунок 9.50):

387

Рисунок 9.50. Пример вкладок в окне диалога

Вкладки относятся к разряду сложных компонентов, но в целом они упрощают пользовательский интерфейс, заменяя несколько связанных по смыслу форм. А чем меньше форм, тем легче пользователю ориентироваться в программе.

Создание вкладок рассмотрим на следующем примере. Представьте, что вкладками являются экзаменационные билеты по трем научным дисциплинам: математике, физике, химии. Каждая вкладка содержит один вопрос с возможными вариантами ответа. Пользователь должен на каждой вкладке выбрать правильный вариант ответа и завершить ввод щелчком на кнопке Result. При всех правильных ответах он получит оценку "отлично", при двух ответах — "хорошо", при ответе лишь на один вопрос — "удовлетворительно", а при всех неправильных ответах — "плохо".

Шаг 1. Приступим к реализации примера. Начните новый проект и установите для главной формы следующие свойства:

Name = ExamForm

Caption = Экзамен BorderStyle = bsDialog

Position = poScreenCenter

Размеры формы подберите по своему усмотрению.

388

Шаг 2. Теперь поместите в форму компонент PageControl. Вы найдете его в палитре компонентов на вкладке Win32 (рисунок 9.51).

Рисунок 9.51. Компонент PageControl

Характерные свойства компонента PageControl кратко описаны в таблице 9.16.

Свойство

Описание

 

 

 

 

 

 

 

 

 

ActivePage

Активная вкладка (страница).

 

 

 

Align

Способ выравнивания компонента в пределах

 

содержащего компонента.

 

 

 

DockSite

Определяет, используется ли компонент PageControl

 

для стыковки других компонентов.

 

 

HotTrack

Подсвечивает закладку при наведении на нее указателя

 

мыши.

 

 

 

 

Images

Список значков, отображаемых на закладках. Свойство

 

Images используется совместно со свойством

 

ImageIndex компонентов TabSheet. Компонент

 

PageControl автоматически назначает каждой закладке

 

номер значка в соответствии с очередностью

 

добавления вкладок, однако программист может

 

вручную указать номер значка.

 

 

MultiLine

Располагает закладки в несколько рядов.

 

 

OwnerDraw

Позволяет

программно

рисовать

закладки

в

 

обработчике события OnDrawTab. Если свойство

 

OwnerDraw равно значению False, то закладки имеют

 

стандартный вид и событие OnDrawTab не

 

происходит.

 

 

 

 

Pages

Массив вкладок (страниц). Каждая вкладка является

 

объектом класса TTabSheet. Свойство Pages доступно

 

только из программы.

 

 

 

PageCount

Общее количество вкладок. Доступно только из

 

программы.

 

 

 

 

RaggedRight Если равно значению True, то при включенном режиме MultiLine закладки не выравниваются на ширину компонента.

ScrollOpposite Способ организации рядов закладок. Если равно значению False, то все ряды расположены вместе, например вверху. Если равно значению True,

389

 

неактивные ряды переносятся на другую сторону

 

компонента, например вниз.

 

 

Style

Стиль закладок: tsTabs — обычные трехмерные

 

закладки, tsFlatButtons — плоские закладки, tsButtons

 

— закладки в виде кнопок.

 

 

TabIndex

Номер выбранной закладки (первая закладка имеет

 

номер 0).

 

 

 

 

TabPosition

Местоположение закладок: tpTop — сверху, tpRight

 

справа, tpLeft — слева, tpBottom — снизу.

 

TabWidth,

Ширина и высота закладок. Если эти свойства равны

TabHeight

нулю,

то

размеры

закладок

подбираются

 

автоматически, исходя из размеров надписей.

OnChange

Происходит после смены закладки.

 

OnChanging

Происходит перед сменой закладки.

 

OnDrawTab

Происходит при рисовании закладки на экране.

 

Требует, чтобы свойство OwnerDraw содержало

 

значение True.

 

 

 

OnGetImageInde

Обработчик этого события должен вернуть номер

x

значка для отображаемой закладки.

 

OnGetSiteInfo

Происходит, когда у компонента запрашивается место

 

для стыковки.

 

 

 

Таблица 9.16. Важнейшие свойства и события компонента PageControl

Шаг 3. Первоначально компонент PageControl не содержит ни единой вкладки. Для создания вкладки щелкните правой кнопкой мыши на компоненте и выберите в контекстном меню команду New Page (рисунок 9.52).

390