Рисунок 9.41. В этом окне вводятся элементы списка — дни недели
Щелкните кнопку OK — список значений компонента WeeklyComboBox задан.
Шаг 21. Выбранный элемент раскрывающегося списка определяется значением свойства ItemIndex. Начальное значение свойства равно -1, что означает — ни один элемент не выбран. Однако в компоненте WeeklyComboBox должно быть выбрано то значение, которое соответствуют текущей дате. С этой целью нужно доработать у формы обработчик события OnCreate. В итоге он будет иметь следующий вид:
procedure TAlarmDetailsForm.FormCreate(Sender: TObject); begin
// Установка текущего времени
TimeMaskEdit.Text := FormatDateTime('hh:mm', Time); // Установка текущего дня недели
WeeklyComboBox.ItemIndex := DayOfWeek(DatePicker.Date) - 1; end;
Реализация метода основана на том, что компонент DatePicker при создании формы сразу содержит текущую дату.
На этом с визуальной частью диалога Alarm Details покончено. Правда, мы ничего не сказали о компоненте DateTimePicker (рисунок 9.42).
Рисунок 9.42. Компонент DateTimePicker
Впрочем, он уже работает так, как требуется. В нем нет ничего сложного, и мы надеемся, что вы разберетесь с ним по таблице 9.13.
Свойство Описание
BevelEdges Вложенные свойства beLeft, beTop, beRight и beBottom
определяют видимость соответственно левой, верхней, правой и нижней сторон рельефной рамки.
BevelInner Внутренний скос рельефной рамки: bvNone — скос
371
|
отсутствует, bvLowered — скос внутрь, bvRaised — скос |
|||
|
наружу; bvSpace — скос заменяется отступом. |
|
||
BevelKind |
Вид рельефной рамки: bkNone — рамки нет, bkTile — |
|||
|
рамка с четкими скосами, bkSoft — рамка со сглаженными |
|||
|
скосами, bkFlat — плоская рамка (без скосов). |
|
||
BevelOuter |
Внешний скос рельефной рамки: bvNone — скос |
|||
|
отсутствует, bvLowered — скос внутрь, bvRaised — скос |
|||
|
наружу; bvSpace — скос заменяется отступом. |
|
||
BevelWidth |
Ширина скосов рельефной рамки. |
|
|
|
CalAlignment |
Способ выравнивания раскрывающегося диалога: dtaLeft |
|||
|
— по левому краю, dtaRight — по правому краю. |
|
||
CalColors |
Цвета: BackColor — ни на что не влияет, существует для |
|||
|
унификации настройки цвета с другими компонентами; |
|||
|
MonthBackColor — цвет фона раскрывающего диалога; |
|||
|
TextColor — цвет текста; TitleBackColor — цвет фона |
|||
|
заголовка; TitleTextColor — цвет текста заголовка; |
|||
|
TrailingTextColor — цвет текста дат, не принадлежащих |
|||
|
текущему месяцу. |
|
|
|
Checked |
Значение переключателя, который отображается, если |
|||
|
свойство ShowCheckbox содержит значение True. |
|
||
Date |
Выбранная дата. |
|
|
|
DateFormat |
Формат даты: dfShort — короткий, dfLong — длинный. |
|||
DateMode |
Режим работы компонента: dmComboBox — по щелчку |
|||
|
кнопки со стрелкой раскрывается окно с месячным |
|||
|
календарем, dmUpDown — вместо кнопки со стрелкой |
|||
|
показывается пара кнопок со стрелками вверх и вниз, |
|||
|
щелчки на которых прокручивают день, месяц или год. |
|||
Format |
Формат даты и времени. |
|
|
|
Kind |
Если равно значению dtkDate, то компонент предназначен |
|||
|
для выбора даты, а если значению dtkTime, то для выбора |
|||
|
времени. |
|
|
|
MaxDate |
Максимальное значение даты, которое может выбрать |
|||
|
пользователь. |
|
|
|
MinDate |
Минимальное значение даты, которое может выбрать |
|||
|
пользователь. |
|
|
|
ParseInput |
Если равно значению True, то по мере ввода значения |
|||
|
пользователем происходит событие OnUserInput. |
|||
ShowCheckbo |
Показывает |
переключатель |
(флажок). |
Значение |
x |
переключателя определяется свойством Checked. |
|
372
Time |
Выбранное время. |
OnChange Происходит при изменении значения даты и времени.
OnCloseUp Происходит при сворачивании раскрывающегося диалога.
OnUserInput Происходит по мере ввода данных пользователем.
Таблица 9.13. Важнейшие свойства и события компонента DateTimePicker
В очередной раз выполните компиляцию программы и запустите ее. Откройте окно диалога Alarm Details и хорошенько его потестируйте (рисунок 9.43).
Рисунок 9.43. Тестирование окна Alarm Details
Все компоненты работают правильно. Можете поздравить себя с очередным достижением. Вы создали важную часть приложения Alarms — окно диалога, а заодно разобрались с множеством новых компонентов.
9.3.9. Установка и получение данных
Окно диалога есть, но пользы от него пока нет. Все дело в том, что мы научились устанавливать параметры будильника, но не научились их принимать и хранить.
Шаг 22. Для хранения параметров будильника нам нужна новая структура данных, очевидно класс объектов. Немного поразмыслив, приходим к следующему описанию:
373
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 определяет периодичность работы будильника и принимает следующие значения:
0 – ежедневно;
1..7 – в заданный день недели (1 — Пн, 2 — Вт, ..., 7 — Вс);
8 – однажды в заданный день.
Впервых двух случаях поле DateTime хранит только время, а в последнем еще и дату. Такое ухищрение позволяет организовать компактное хранение данных. Флаг Handled, объявленный в секции private, является служебным и позволит избежать повторных срабатываний, когда будильник уже прозвенел. Метод GetAlarmStr мы определили для удобства. Он будет формировать строку сообщения, содержащую время и текст напоминания. Метод CheckTime проверит, пора ли выдать сигнал и если да, то сделает это.
Шаг 23. Поместите описание класса TAlarm в раздел interface модуля AlarmDetails. Затем в разделе implementation наберите текст методов GetAlarmStr и CheckTime:
374
function TAlarm.GetAlarmStr: 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, Sec1, MSec1);
//Раскодировать текущее время будильника
DecodeTime(DateTime, Hour2, Min2, Sec2, MSec2);
//Проверить, что текущее время совпадает с временем будильника case Recurring of
0:// для ежедневной периодичности
Match := (Hour1 = Hour2) and (Min1 = Min2); 1..7: // для еженедельной периодичности
Match := (Hour1 = Hour2) and (Min1 = Min2) and
(Recurring = DayOfWeek(Date));
8:// для конкретной даты
Match := (Hour1 = Hour2) and (Min1 = Min2) and (Int(DateTime) = Date);
else
Match := False; end;
// Решить вопрос о выдаче сигнала будильником if Match then
begin
if not Handled then // сигнал! begin
Handled := True; // предотвратить повторные срабатывания if PlaySound then Beep;
MessageDlg(GetAlarmStr, mtWarning, [mbOk], 0); end;
end else
Handled := False; // обеспечить будущие срабатывания
end;
Для правильной работы будильника метод CheckTime должен вызываться не реже одного раза в минуту. Чем чаще вызывается метод, тем меньше инерционность будильника, но тем больше пустых опросов, а значит выше загруженность операционной системы. Компромиссная частота — два раза в секунду. Так как в одну и ту же минуту метод CheckTime будет вызван несколько раз, то для избежания повторных срабатываний используется флаг Handled. Будильник выдает сообщение только в том случае, если текущее время совпадает с временем, на которое будильник установлен и при условии, что в данную минуту он еще не звенел.
Шаг 24. Давайте теперь позаботимся о передаче данных в окно диалога перед его запуском и о приеме данных после завершения. Удобнее всего, чтобы за это отвечало само окно диалога, т.е. форма AlarmDetailsForm. С этой целью определите в классе TAlarmDetilasForm два метода — GetData и SetData. Методы следует поместить в секцию public:
type
TAlarmDetailsForm = class(TForm)
...
public
procedure GetData(Alarm: TAlarm); procedure SetData(Alarm: TAlarm);
end;
В разделе implementation наберите программный текст методов:
375
procedure TAlarmDetailsForm.GetData(Alarm: TAlarm); begin
with Alarm do begin
//Получить из диалога текст сообщения будильника
MsgText := MessageEdit.Text;
//Получить из диалога время срабатывания будильника
DateTime := StrToTime(TimeMaskEdit.Text);
//Получить из диалога состояние переключателя звука
PlaySound := SoundCheckBox.Checked;
//Получить из диалога периодичность срабатывания будильника if EverydayRadioButton.Checked then
Recurring := 0
else if WeeklyRadioButton.Checked then Recurring := WeeklyComboBox.ItemIndex + 1
else { DateRadioButton.Checked } begin
Recurring := 8;
DateTime := DatePicker.Date + DateTime; end;
end; end;
procedure TAlarmDetailsForm.SetData(Alarm: TAlarm); begin
with Alarm do begin
//Установить в окне диалога текст сообщения будильника
MessageEdit.Text := MsgText;
//Установить в окне диалога время будильника
TimeMaskEdit.Text := FormatDateTime('hh:mm', DateTime);
//Установить в окне диалога состояние переключателя звука
SoundCheckBox.Checked := PlaySound;
//Установить в окне диалога периодичность будильника
case Recurring of 0: // ежедневно
EverydayRadioButton.Checked := True; 1..7: // еженедельно
begin
WeeklyRadioButton.Checked := True; WeeklyComboBox.ItemIndex := Recurring - 1;
end;
8: // в конкретный день begin
DateRadioButton.Checked := True;
DatePicker.Date := Int(DateTime); end;
end; end;
end;
Метод GetData просто заполняет поля переданного в параметре объекта Alarm значениями, которые установлены в компонентах окна диалога. Метод SetData выполняет обратные действия, заполняя компоненты окна диалога значениями, которые содержатся в полях объекта Alarm.
На этом с разработкой модуля AlarmDetails покончено и окно диалога Alarm Details полностью готово к использованию. Дальше необходимо обеспечить формирование, редактирование и визуализацию списка будильников. Эта задача решается с помощью компонента ListBox.
9.3.10. Список
Компонент ListBox отображает список элементов, которые пользователь может просматривать и выбирать, но не может непосредственно модифицировать. По умолчанию элементами списка являются строки, но могут быть и графические объекты. Элементы могут располагаться в одну или несколько колонок и автоматически сортироваться. При
376
необходимости обеспечивается возможность прокрутки списка. Компонент ListBox находится в палитре компонентов на вкладке Standard (рисунок 9.44).
Рисунок 9.44. Компонент ListBox
Его характерные свойства собраны в таблице 9.14.
Свойство |
Описание |
|
|
Align |
Способ выравнивания компонента в пределах содержащего |
|
компонента. |
AutoComplete |
Если равно True, то можно быстро выбрать элемент, если |
|
начать набирать его текст на клавиатуре. |
BevelEdges |
Вложенные свойства beLeft, beTop, beRight и beBottom |
|
определяют видимость соответственно левой, верхней, |
|
правой и нижней сторон рельефной рамки. |
BevelInner |
Внутренний скос рельефной рамки: bvNone — скос |
|
отсутствует, bvLowered — скос внутрь, bvRaised — скос |
|
наружу; bvSpace — скос заменяется отступом. |
BevelKind |
Вид рельефной рамки: bkNone — рамки нет, bkTile — |
|
рамка с четкими скосами, bkSoft — рамка со сглаженными |
|
скосами, bkFlat — плоская рамка (без скосов). |
BevelOuter |
Внешний скос рельефной рамки: bvNone — скос |
|
отсутствует, bvLowered — скос внутрь, bvRaised — скос |
|
наружу; bvSpace — скос заменяется отступом. |
BorderStyle |
Определяет, имеет ли список рамку. |
Columns |
Количество колонок в списке. |
ExtendedSelec Если равно значению True, то пользователь может выбрать
tв списке диапазон элементов (однако лишь в том случае, если MultiSelect тоже равно значению True).
IntegralHeight |
Если равно значению True, то высота списка автоматически |
|
уменьшается, чтобы быть кратной высоте элемента. |
ItemHeight |
Высота элемента списка, когда значение свойства Style |
|
равно lbOwnerDrawFixed. |
Items |
Элементы списка. |
MultiSelect |
Если равно значению True, то пользователь может выбрать |
|
в списке несколько элементов. |
377
ScrollWidth Логическая ширина списка в пикселях. Если значение свойства ScrollWidth больше значения свойства Width, то появляется горизонтальная полоса прокрутки. В противном случае полоса прокрутки не показывается.
Sorted |
Если равно значению True, то элементы списка |
||||
|
сортируются в алфавитном порядке. |
|
|
||
Style |
Стиль отображения списка (см. табл. 7.14). |
|
|||
OnData |
Предназначено для формирования списка элементов перед |
||||
|
рисованием. Происходит только в том случае, если |
||||
|
свойство Style содержит значение lbVirtual или |
||||
|
lbVirtualOwnerDraw. |
|
|
|
|
OnDataFind |
Происходит, когда пользователь пытается быстро перейти |
||||
|
к элементу, набирая текст элемента на клавиатуре. |
||||
|
Обработчик этого события должен на основании введенной |
||||
|
пользователем строки вернуть номер соответствующего |
||||
|
элемента. Возникает только в том случае, если свойство |
||||
|
Style |
содержит |
значение |
lbVirtual |
или |
lbVirtualOwnerDraw.
OnDataObject Происходит при обращении к массиву Objects в списке Items, но только в том случае, если свойство Style
содержит значение lbVirtual или lbVirtualOwnerDraw.
Обработчик события должен вернуть соответствующий элементу объект.
OnDrawItem Происходит при рисовании отдельно взятого элемента списка, но только в том случае, если свойство Style содержит одно из следующих значений: lbOwnerDrawFixed, lbOwnerDrawVariable, lbVirtualOwnerDraw.
OnMeasureIte По замыслу разработчиков событие происходит при
mрасчете высоты отдельно взятого элемента списка перед его рисованием на экране и лишь в том случае, если свойство Style содержит значение lbOwnerDrawVariable. Однако из-за дефекта в модуле StdCtrls событие OnMeasureItem не срабатывает.
Таблица 9.14. Важнейшие свойства и события компонента ListBox
Особенности хранения и отображения элементов списка определяются свойством Style, возможные значения которого описаны в таблице 9.15.
Значение Описание
LbStandard Все элементы списка имеют одинаковую высоту, которая рассчитывается исходя из размера шрифта.
LbOwnerDrawFixed Все элементы списка имеют одинаковую высоту, заданную в свойстве ItemHeight. За рисование
378
|
элементов отвечает программист, который должен |
|
создать обработчик события OnDrawItem. |
lbOwnerDrawVariab |
По замыслу разработчиков элементы списка имеют |
le |
разную высоту, определяемую в обработчике события |
|
OnMeasureItem (из-за дефекта в модуле StdCtrls |
|
событие не срабатывает). За рисование элементов |
|
отвечает программист, который должен создать |
|
обработчик события OnDrawItem. |
LbVirtual |
Элементы списка хранятся отдельно от компонента и |
|
запрашиваются с помощью события OnData. За |
|
рисование элементов отвечает компонент. |
LbVirtualOwnerDra Элементы списка хранятся отдельно от компонента и
wзапрашиваются с помощью события OnData. За рисование элементов отвечает программист, который должен создать обработчик события OnDrawItem.
Таблица 9.15. Значения свойства Style компонента ListBox
Шаг 25. Давайте воспользуемся компонентом ListBox для организации списка будильников. Активизируйте форму MainForm, а затем опустите на нее компонент ListBox. Переименуйте компонент в AlarmListBox и скорректируйте его местоположение и размеры. Затем установите свойство TabOrder в значение 0, чтобы при отображении формы список первым получил фокус ввода (рисунок 9.45).
Рисунок 9.45. Компонент ListBox применяется для организации списка будильников
Решим теперь вопрос хранения будильников в компоненте AlarmListBox. Для хранения элементов служит свойство Items. Свойство Items — это объект класса TStrings, в нем свойство-массив Strings хранит отображаемые строки, а свойство-массив Objects — ассоциированные со строками объекты. В нашем примере массив Strings будет хранить выдаваемые по сигналу сообщения, а массив Objects — соответствующие им объекты класса
TAlarm.
Теоретически все понятно, осталось реализовать все это практически. Создание, редактирование и удаление будильника осуществляется по щелчкам на кнопках NewButton,
379
EditButton и DeleteButton соответственно. Поэтому в них требуется создать обработчики события OnClick.
Шаг 26. В кнопке New... обработчик события OnClick уже существует, но его необходимо доработать:
procedure TMainForm.NewButtonClick(Sender: TObject); var
Alarm: TAlarm; begin
AlarmDetailsForm := TAlarmDetailsForm.Create(Self); try
// Выполнить диалог
if AlarmDetailsForm.ShowModal = mrOK then begin
//Создать новый объект будильника
Alarm := TAlarm.Create;
//Получить параметры будильника из диалога
AlarmDetailsForm.GetData(Alarm);
//Добавить будильник в список и выбрать его
AlarmListBox.ItemIndex := AlarmListBox.Items.AddObject( Alarm.GetAlarmStr, Alarm);
end; finally
AlarmDetailsForm.Free; end;
end;
Метод NewButtonClick создает окно диалога Alarm Details и выполняет его в монопольном режиме. Если диалог завершается щелчком кнопки OK, создается новый объект будильника и в него переносятся данные из окна диалога. Затем этот объект добавляется в список AlarmList и его номер присваивается свойству списка ItemIndex. В результате новый элемент становится выделенным.
Вы, разумеется, хотите проверить работу новоиспеченного метода. Сейчас мы так и сделаем, но прежде нужно решить небольшой вопрос. Дело в том, что при уничтожении блока списка освобождаются только строки, но не освобождаются ассоциированные с ними объекты. Хотя память объектов так или иначе освобождается при завершении приложения, мы рекомендуем всегда освобождать память явно. Это считается "хорошим тоном" программирования и иногда позволяет выявить скрытые ошибки. Освобождение использованных в форме динамических данных осуществляется в обработчике события OnDestroy. Для формы MainForm он должен быть таким:
procedure TMainForm.FormDestroy(Sender: TObject); var
I: Integer; begin
for I := 0 to AlarmListBox.Items.Count - 1 do AlarmListBox.Items.Objects[I].Free;
end;
После того как вы написали этот обработчик, выполните компиляцию программы и запустите ее. Попытайтесь добавить в список несколько будильников. Если это у вас получилось, перейдем к следующему шагу — программированию реакции на нажатия кнопок Edit... и Delete.
Шаг 27. Создайте в компоненте EditButton обработчик события OnClick:
380