Назад.    Содержание.

УСТАНОВКА И ПОЛУЧЕНИЕ ДАННЫХ

Шаг 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;

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

Назад.    Содержание.