УСТАНОВКА И ПОЛУЧЕНИЕ ДАННЫХ
Шаг 26. Для хранения параметров будильника нам нужна новая структура данных, очевидно — класс объектов. Немного поразмыслив, приходим к следующему описанию:
type
TAlarm=class
private
Handled: Boolean;
public
MsgText:string;
DateTime:
TDateTime;
PlaySound:
Boolean;
Recurring: Integer;
function
GetAlarmStr: string;
procedure CheckTime;
end;
Поясним назначение полей и методов. Поле MsgText предназначено для хранения текстового сообщения. В поле DateTime записывается время и дата сигнала. Значение поля PlaySound показывает, требуется ли звуковое сопровождение сообщения. Поле Recurring определяет периодичность работы будильника и принимает следующие значения:
В первых двух случаях поле DateTime хранит только время, а в последнем — еще и дату. Такое ухищрение позволяет организовать компактное хранение данных. Флаг Handled, объявленный в секции private, является служебным и позволит избежать повторных срабатываний, когда будильник уже прозвенел. Метод GetAIarmStr мы определили для удобства. Он будет формировать строку сообщения, содержащую время и текст напоминания. Метод CheckTime проверит, пора ли выдать сигнал, и если да, то сделает это.
Шаг 27. Поместите описание класса TAlarm в раздел interface модуля Settings. Затем в разделе implementation наберите текст методов GetAlarmStr и CheckTime:
function TAlarm. GetAIarmStr: string;
begin
Result
:= FormatDateTime('hh:mm ', DateTime) + MsgText;
end;
procedure TAlarm.CheckTime;
var
Hour1,
Min1, Sec1, MSec1: Word;
Hour2,
Min2, Sec2, MSec2: Word;
Match:
Boolean;
begin
{ Раскодировать текущее время }
DecodeTime(Time,
Hour1, Min1, Secl, MSecl);
{ Раскодировать текущее время будильника
}
DecodeTime(DateTime,
Hour2, Min2, Sec2, MSec2);
{Проверить, что текущее время совпадает с временем будильника}
case Recurrinq of
0: { для ежедневной периодичности }
Match
:= (Hourl = Hour2) and (Min1 = Min2);
1..7:
{ для еженедельной периодичности }
Match
:= (Hourl = Hour2) and (Min1 = Min2) and (Recurring =
DayOfWeek(Date));
8:
{для конкретной даты}
Match
:= (Hourl = Hour2) and (Min1 = Min2) and (Int(DateTime) = Date);
end;
(Решить вопрос о выдаче сигнала будильником)
if Match then
begin
if not
Handled then { сигнал! } begin
Handled := True; { предотвратить повторные срабатывания }
if PlaySound
then Beep;
MessageDIg(GetAlarmStr,
mtWarning, [mbOk], 0) ;
end;
end
else Handled := False; { обеспечить будущие срабатывания }
end;
Для правильной работы будильника метод CheckTime должен вызываться не реже одного раза в минуту. Чем чаще вызывается метод, тем меньше инерционность будильника, но тем больше пустых опросов, а значит выше загруженность операционной системы. Компромиссная частота — раз в две секунды. Так как в одну и ту же минуту метод CheckTime будет вызван несколько раз, то для избежания повторных срабатываний используется флаг Handled. Будильник выдает сообщение только в том случае, если текущее время совпадает с временем, на которое будильник установлен и при условии, что в данную минуту он еще не звенел.
Шаг 28. Давайте теперь позаботимся о передаче данных в окно диалога перед его запуском и о приеме данных после завершения. Удобнее всего, чтобы за это отвечало само окно диалога, т.е. форма SettingsForm. С этой целью определите в классе TsettingsForm два метода - GetData и SetData. Методы следует поместить в секцию public :
type
TSettingsForm = class(TForm)
…
public
procedure
GetData (Alarm: TAlarm) ;
procedure
SetData(Alarm: TAlarm) ;
end;
В разделе implementation наберите программный текст методов:
procedure TSettingsForm. GetData (Alarm:
TAlarm) ;
begin
with Alarm do begin
(Получить из диалога текст сообщения будильника)
MsgText : = MessageEdit. Text ;
{ Получить из диалога время срабатывания будильника }
DateTime := StrToTime(TimeMaskEdit.Text);
{ Получить из диалога состояние переключателя звука }
PlaySound := SoundCheck.Checked;
(Получить из диалога периодичность срабатывания будильника)
if EverydayRadio.
Checked then
Recurring
:= 0
else if
WeeklyRadio .Checked then
Recurring
:= WeeklyCombo.Itemlndex + 1
else {
DateRadio.Checked } begin
Recurring
:= 8;
DateTime
:= Calendar.CalendarDate + DateTime;
end;
end;
end;
procedure TSettingsForm. SetData (Alarm:
TAlarm) ;
begin
with Alarm do begin
{ Установить в окне диалога текст сообщения будильника }
MessageEdit.Text := MsgText;
(Установить в окне диалога время будильника)
TimeMaskEdit.
Text : FormatDateTime (' hh : mm' , DateTime);
{ Установить в окне диалога состояние переключателя звука }
SoundCheck.Checked := PlaySound;
{ Установить в окне диалога периодичность будильника }
case
Recurring of
0
: { ежедневная периодичность}
EverydayRadio/
Checked : = True;
1.
. 7 : { еженедельная периодичность }
begin
WeeklyRadio.Checked
:= True;
WeeklyCombo.Itemlndex
:= Recurring - 1;
end;
8
: { конкретная дата }
begin
DateRadio.Checked
:= True;
Calendar.CalendarDate
:= Int(DateTime);
MonthCombo
. ItemIndex : Calendar. Month - 1 ;
YearUpDown.Position
:= Calendar.Year;
end
end;
end;
end;
Метод GetData просто заполняет поля переданного в параметре объекта Alarm значениями, которые установлены в управляющих элементах окна диалога. Метод SetData шолняет обратные действия, заполняя управляющие элементы окна диалога значениями, которые содержатся в полях объекта Alarm.
На этом с разработкой модуля Settings покончено и окно диалога Alarm Settings полностью готово к использованию. Дальше необходимо обеспечить формирование, редактирование и визуализацию списка будильников. Эта задача решается с помощью компонента ListBox.
СПИСОК.
Компонент ListBox отображает прокручиваемый список элементов, которые пользователь может просматривать и выбирать, но не может непосредственно модифицировать. По умолчанию элементами списка являются строки, но могут быть и графические объекты. Элементы могут располагаться в одну или несколько колонок и автоматически сортироваться. Компонент ListBox находится в Палитре Компонентов на cтранице Standart (рис.7.43.)
Рис. 7.43
Его характерные свойства собраны в таблице:
Свойства |
Описание |
Align |
Способ выравнивания списка в пределах владельца. |
BorderStyle |
Определяет,имеет ли список рамку |
Columns |
Количество колонок в списке. |
ExtendedSelect |
Если равно True, то пользователь может выбрать в списке диапазон элементов (однако лишь в том случае, если MultiSelect тоже равно True). |
IntegralHeight |
Если True, то высота списка автоматически уменьшается, чтобы быть кратной высоте элемента. |
ItemHeight |
Высота элемента списка, когда значение свойства Style равно IbOwnerDrawFixed. |
Items |
Элементы списка. |
MultiSelect |
Если равно True, то пользователь может выбрать в списке несколько элементов. |
Sorted |
Если равно True, то элементы списка сортируются в алфавитном порядке. |
Style |
Стиль отображения списка (см. таблицу ниже). |
Особенность отображения элементов списка определяется свойством Style, возможные значения которого описаны в таблице:
Значение |
Описание |
LbStandard |
Все элементы списка имеют одинаковую высоту, которая рассчитывается, исходя из размера шрифта. |
LbOwnerDrawFixed |
Все элементы списка имеют одинаковую высоту, заданную в свойстве ItemHeight. |
LbOwnerDrawVariable |
Элементы списка имеют разную высоту. |
В двух последних случаях компонент ListBox начинает генерировать событие OnDrawttem. Вы можете его перехватить и рисовать каждый элемент списка, как вам вздумается. Если элементы списка имеют разную высоту (стиль IbOwnerDrawVariable), то чтобы узнать высотy каждого элемента, список генерирует событие OnMeasurettem. Стили IbOwnerDrawFixed и ibOwnerDrawVariable часто применяются для отображения списка картинок.
Шаг 29. Давайте воспользуемся компонентом ListBox для организации списка будильников. Активизируйте форму MainForm, а затем опустите на нее компонент ListBox. Переименуйте компонент в AlarmList и скорректируйте его местоположение и размеры, Затем установите свойство TabOrder значение 0,чтобы при отображении формы список первым получил фокус ввода.
Решим теперь вопрос хранения будильников в компоненте AlarmList. Для хранения элементов служит свойство Items. Свойство Items — это объект класса TStrings, в нем юйство-массив Strings хранит отображаемые строки, а свойство-массив Objects — ассоциированные со строками объекты. В нашем примере массив Strings будет хранить выдаваемые по сигналу сообщения, а массив Objects — соответствующие им экземпляры класса TAlarm.
Теоретически все понятно, осталось реализовать все это практически. Создание, редактирование и удаление будильника осуществляется по щелчкам на кнопках NewBtn, EdHBtn и DeleteBtn соответственно. Поэтому в них требуется определить обработчики события OnCHck.
Шаг 30. В кнопке NewBtn обработчик события OnClick существует, но его необходимо доработать:
procedure TMainForm.NewBtnClick(Sender:
TObject);
var
Alarm: TAlarm;
begin
SettingsForm
:= TSettingsForm.Create(Self);
try
{Выполнить диалог}
if SettingsForm.ShowModal = mrOK then begin
{ Создать новый объект будильника }
Alarm:= TAlarm.Create;
{Получить параметры будильника из диалога}
SettingsForm.GetData(Alarm);
{ Добавить будильник в список и выбрать его }
AlarmList.ItemIndex:= AlarmList.Items.Addobject(
Alarm.GetAlarmStr,Alarm);
end;
finally
SettingsForm.Free;
end;
end;
Метод NewBtnClick создает окно диалога Alarm Settings и выполняет его в модальном режиме. Если диалог завершается щелчком на кнопке ОК, создается новый объект будильника и в него переносятся данные из окна диалога. Затем этот объект добавляется в список AlarmList и его номер присваивается свойству списка Itemlndex. В результате новый элемент становится выбранным.
Вы, разумеется хотите проверить работу новоиспеченного метода. Сейчас мы так
и сделаем, но прежде нужно решить небольшой вопрос. Дело в том, что при
уничтожении блока списка освобождаются только строки, но не освобождаются
ассоциированные с ними объекты. Хотя память объектов так или иначе
освобождается при завершении приложения, мы рекомендуем всегда освобождать
память явно. Это считается “хорошим тоном” программирования и иногда позволяет
выявить скрытые ошибки. Освобождение использованных в форме динамических данных
осуществляется в обработчике события OnDestroy. Для формы MainForm он должен быть таким:
procedure TMainForm.FormDestroy( Sender:
TObject)
var
I:
Integer;
begin
for I : = 0
to AlarmList. Items .Count - 1 do
AlarmList.Items.Objects[I].Free;
end;
После того как вы написали этот обработчик, скомпилируйте проект и запустите приложение. Попытайтесь добавить в список несколько будильников. Если у вас получилось, перейдем к следующему шагу — программированию реакции на нажатия кнопок Edit... и Delete.
Шаг 31. Определите в компоненте EditBtn обработчик события OnClick следующего вида:
Procedure TmainForm. EditBtnClick(Sender:
Tobject);
var
Alarm:
TAlarm;
SavedIndex:
Integtr;
begin
SettingsForm
:= TSettingsForm.Create(Self);
txy
{ Получить выбранный будильник }
with
AlarmList do Alarm := TAlarm(Items.Objects[Itemlndex]);
{ Установить управляющее элементы диалога в соответствии с параметрами будильника}
SettingsForm. SetData (Alarm);
{ Выполнить диалог }
if SettingsForm.
ShowModal = mrOK then begin
{Получить из диалога новые параметры будильника}
SettingsForm.
GetData(Aiarm);
with
AlarmList do begin
{ Запомнить номер выбранного в списке элемента }
Savedlndex := Itemlndex;
{Изменить текст элемента. При этом элемент перестает быть выбранным}
Items.Strings[Itemlndex]
:= Alarm.GetAlarmStr;
{ Восстановить номер выбранного в списке элемента }
Itemlndex
:= SavedIndex;
end;
end;
finally
SettingsForm.Free;
end;
end;
Этот метод создает окно диалога Aiarm Settings, инициализирует его управляющие элементы данными из выбранного в списке объекта будильника, а затем выполняет диалог в модальном режиме. Если диалог завершился щелчком на кнопке ОК, то данные из окна диалога переносятся обратно в объект будильника и соответственно изменяется отображаемая в блоке списка строка. Так как в результате последнего действия в списке пропадает полоса выбора (свойство ltemlndex получает значение – 1), номер выбранного элемента предварительно сохраняется в локальной перемене Savedlndex, а затем восстанавливается.
Шаг 32. Осталось определить обработчик события OnClick в компоненте DeleteBtn:
procedure TMainForm.DeleteBtnClick(Sender:
TObject) ;
begin
with AlarmList
do begin
{Разрушить объект будильника }
Items.Objects
[Itemlndex] .Free;
{ Удалить из списка соответствующую объекту строку }
Items.
Delete (Itemlndex) ;
end;
end;
Метод DeleteBtnClick удаляет объект будильника и соответствующую ему строку в списке.
Обработчики событий для всех кнопок заданы, однако не спешите запускать приложение. Необходимо позаботиться о разрешении и запрещении кнопок Edit… и Delete в зависимости от того, есть ли в списке выбранный элемент или нет. Как бы это сделать попроще? Первое решение, которое напрашивается – это вставить необходимые проверки в обработчики событий кнопок. Это неплохое решение, но оно больше подходит тем, кто привык решать задачу в лоб. Мы пойдем другим путем, воспользовавшись событием OnIdle объекта Application.
Объект Application генерирует событие OnIdle в период простоев приложения, например во время ожидания пользовательского ввода. Благодаря этому событию программа может выполнять некоторую фоновую работу, которая в нашем случае заключается в наблюдении за состоянием кнопок.
Шаг 33. Определите в классе TSettingsForm метод обработки события OnIdle следующим образом:
type
TMainForm
= class(TForm)
. . .
public
procedure ApplicationIdle
(Sender : Tobject; var Done: Boolean);
end;
procedure
TMainForm. Applicationldle (Sender: TObject; var Done:Boolean);
begin
EditBtn.Enabled := AlarmList.Itemlndex
<> -1;
DeleteBtn.Enabled :=
AlarmList.Itemlndex <> -1;
Done : = True; {предотвращает непрерывную генерацию события
OnIdle}
end;
В передаваемом по ссылке параметре Done метод возвращает результат своей работы. Значение True показывает, что метод нужно вызывать не постоянно в течение простоя приложения, а только по одному разу в начале каждого периода простоя.
Шаг 34. Инсталяция обработчика должна выполняться при создании формы, т.е. в обработчике события OnCreate :
procedure TMainForm.
FormCreate (Sender: TObject) ;
begin
Application.Onldle :=
Applicationldle;
end;
А вот сейчас скомпилируйте проект, запустите приложение и тщательно протестируйте работу главной формы.
Будильники можно создавать, добавлять, удалять. Нам осталось сделать последний шаг — заставить будильники “звонить”. Для этого нужно периодически вызывать метод CheckTime у каждого помещенного в список объекта TAlarm. Периодические по времени действия выполняются с помощью таймера, о котором мы дальше и поговорим.
Законченное приложение для выдачи сигналов в заданные моменты времени.
ТАЙМЕР.
Таймер- это системный генератор событий, который периодически сообщает программе о завершении заданного промежутка времени. Интервал времени между таймерными событиями может устанавливаться в диапазоне от 1 до 65535 миллисекунд. Однако приложения не могут получать таймерные события чаще, чем 18.2 раза в секунду, и установленный интервал округляется. Используя таймер, учитывайте, что интервалы между таймерными событиями оказываются неточными, так как Windows является многозадачной средой.
В Delphi прием таймерных событий обеспечивает компонент Timer. Он расположен в политре Компонентов на странице System. Им мы и воспользуемся для “оживления” будильников в приложении Alarms
Шаг 35. Откройте в Delphi проект Alarms (если он еще не открыт) и активизируйте форму MainForm . Затем опустите на нее компонент Timer.
Шаг 36. Интервал времени между таймерными событиями задается в миллисекундах как значение свойства Interval и по умолчанию равен 1000 (приблизительно 1 секунда). Контроль будильников будем выполнять один раз в две секунды, поэтому установите свойство interval в значение 2000.
Шаг 37. Через заданные в свойстве Interval промежутки времени компонент Timer генерирует событие OnTimer (единственное событие этого компонента). Для контроля за будильниками нам нужно определить его обработчик. С этой целью выберите в Инспекторе Объектов страницу Events, двойным щелчком в значении события откройте Редактор Кода и наберите текст обработчика:
procedure TmainForm. Timer1Timer(Sender:
Tobject);
var
I
: Integer;
begin
for I : = 0 to
AlarmList. Items. Count - I do
with AlarmList.Items.
Objects (I) as Talarm do
CheckTime;
end;
Смысл выполняемых действий очевиден: у каждого объекта в списке будильников вызывается метод CheckTime. Таким образом каждый будильник периодически проверяет свое время и, если нужно, выдает предупреждение.
Скомпилируйте и запустите проект, а затем проверьте работу всех элементов приложения. Ура! Все работает, терпение и упорство вознаграждены по заслугам.
ФАЙЛЫ НАСТРОЕК
Приложение ALARMS имеет один существенный недостаток: после его завершения все установленные будильники теряются, так что при следующем запуске приложения их приходится создавать снова. Для решения этой проблемы необходимо, чтобы между сеансами работы приложения будильники хранились в конфигурационном файле на диске.
Сохранение и восстановление конфигурации осуществляется в Windows с помощью так называемых файлов настроек. Файл настроек (initialization file) — это текстовый файл, имеющий расширение INI. Он состоит из секций. Секция начинается с имени, заключенного в квадратные скобки. В каждой секции содержатся определения некоторых связаанных по смыслу параметров, представленные в виде пар Имя=3начение. Типичный пример INI-файла — WIN.INI — файл настроек системы Windows.
Структуру файла настроек для приложения ALARMS выберем так, чтобы каждому будильнику соответствовала отдельная секция. Число секций, т.е. будильников, будем хранить в параметре AlarmCount секции Global Options. Вот как могло бы выглядеть содержимое файла
[Global Options]
AlaiTnCount=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
Чтение и запись файла настроек в Delphi-приложениях осуществляется с помощью объектов TiniFile (заметьте, они не являются компонентами). Класс TiniFile описан в модуле IniFiles. Этот модуль необходимо собственноручно добавить в оператор uses вызывающего модуля. При создании экземпляра TiniFile ему в конструктор передается имя INI-файла. Позже это имя можно узнать, обратившись к свойству FileName. Если в имени файла маршрут не указан, считается, что INI-файл находится в каталоге Windows.
Чтение переменных из INI- файла выполняется с помощью описанных ниже методов. В этих методах название секции передается в параметре Section, имя переменной - в параметре ldent, а значение по умолчанию - в параметре Detault .
ReadBool(const Section, ldent: string; Default: Boolean):Boolean - возвращает значение булевской переменной.
Readlnteger(const Section, Ident: string; Default:
LongInt):LongInt - возвращает значение целочисленной переменной.
ReadString(const
Section, Ident, Default: string): string — возвращает значение строковой переменной.
ReadSection(const Section: string; Strings: TStrings) — читает из заданной ceкции имена всех переменных и помещает их в объект класса TStrings.
ReadSectionValues(const Section: string; Strings: Tstrings) - читает из заданной секции все пары Имя=3начение и помещает их в список. Для доступа к Значению по Имени в объектах класса TStrings существуют свойства-массивы Names и Values.
При чтении значений значений из INI-файла может оказаться, что заданный идентификатор или секция отсутствует. В этом случае ошибки не происходит, а функции ReadBool, Readlnteger и 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-файл строковое значение.
Если в момент записи значения оказывается, что заданные секция и (или) идентификатор отсутствуют, они создаются.
Удаление секций INI-файла осуществляется с помощью метода EraseSection, в который передается единственный параметр — название секции.
Шаг 38. Давайте воспользуемся описанными методами для чтения и записи будильников в программе ALARMS. Работу по чтению и записи параметров одного будильника лучше всего поручить классу TAlarm. Для этого добавьте в его описание два новых метода — LoadFromIniFile и SaveToIniFile.
type
TAlarm = class(TObject)
…
procedure LoadFromlniFile (IniFile: TIniFile; const
Section: string);
procedure SaveTolniFile (IniFile: TIniFile; const
Section: string);
end;
Метод LoadFromIniFile предназначен для чтения из INI-файла полей объекта, а метод SaveTolniFile — для записи в INI-файл полей объекта. Секция INI-файла, с которой работают эти методы, передается в параметре Section.
Шаг 39. Наберите программный код методов в разделе implementation:
Procedure
Talarm.LoadFromIniFile(IniFile:
TiniFile; const Section: string);
begin
with IniFile do begin
{ Прочитать текст сообщения}
MsgText := ReadString(Section, 'Message',
'Reminder!');
{ Прочитать строковое значение времени и преобразовать его в формат TDakeTime }
DateTime := StrToTime(ReadString(Section, 'Time',
TimeToStr(Time)));
{Прочитать состояние переключателя звука}
PlaySound := ReadBool(Section, 'PlaySound', True);
{ Прочитать значение периодичности }
Recurring := Readlnteger(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);
{ Записать значение периодичности }
Writelnteger(Section,
'Recurring', Recurring);
if Recurring = 8 then
{Преобразовать дату в строку и записать строку в INI-файл}
WriteString(Section,
'Date', DateToStr(DateTime));
end;
end;
Шаг 40. Перейдем теперь от чтения и записи одного будильника к загрузке и сохранению всего списка. Эти действия следует выполнять соответственно при создании и разрушении главной формы приложения, т.е. в ответ на события OnCreate и OnDestroy. Определите следующие обработчики этих событий для формы MainForm:
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));
{ Добавить будильник в список }
AlarmList.Items.AddObject(Alarm.GetAlarmStr,
Alarm);
end;
IniFile.Free;
end;
end;
procedure TMainForm.FormDestroy (Sender:
TObject) ;
var
IniFile
: TiniFile;
I: Integer;
begin
IniFile
:= TIniFile.Create('ALARMS.INI');
try
{Записать число будильников}
IniFile.Writelnteger('Global
Options', 'AlarmCount1, AlarmList.Items.Count);
{ Записать список будильников }
for I : = 0
to AlarmList.Items.Count - 1 do
with
AlarmList.Items.Objects[I] as TAlarm do
{Записать параметры будильников в соответствующую секцию}
SaveToIniFile(IniFile,
'Alarm' + IntToStr(I +1));
finally
IniFile.Free;
for I := 0 to
AlarmList.Items.Count - 1 do
AlarmList.Items.Objects[I].Free;
end;
end;
Вот пожалуй и все. Сохраните проект, выполните его компиляцию и запустите приложение. Создайте несколько будильников, закройте приложение, а затем запустите его снова… Будильники на месте.