Переключатели являются еще одним видом основных, часто используемых элементов управления. Переключателями они называются потому, что могут находиться в 2 состояниях – в выбранном или невыбранном.
Переключатели бывают 2 типов – зависимыми (собственно переключатель, известный так же как «радиокнопка») и независимыми (флажки). Разница между ними заключается в том, что в группе зависимых переключателей может быть выбран только один, а независимые никак не связаны между собой. Визуально зависимые переключатели выглядят как кружки, а независимые – как квадратики. При щелчке по переключателю он меняет свое состояние, т.е. если был включенным (отмеченным), то становится выключенным, и наоборот.
Начнем с независимых переключателей – флажков. В VCL флажок представлен компонентом CheckBox. Он выглядит как небольшой прямоугольник с текстовым заголовком, расположенным справа. Если же вдруг появится острая необходимость «развернуть» флажок таким образом, чтобы текст находился слева, то можно установить свойство Alignment в taLeftJustify.
Но самым важным свойством флажка, пожалуй, является Checked. Именно оно определяет его состояние. Так, если флажок включен (что визуально проявляется как наличие галочки на квадратике), то это свойство имеет значение истины, а если выключен – лжи. Этим свойством можно управлять программно, визуально это будет проявляться как появление или исчезновение галочки.
Вместе с тем, для флажка предусмотрено еще одно состояние – запрещенное. Это состояние является опциональным и за его наличие или отсутствие отвечает свойство AllowGrayed. В том случае, если оно установлено в истину, то при щелчке мышкой по такому флажку будет происходить циклическая смена между 3 состояниями: установлен, снят и запрещен. В последнем режиме он выглядит как отмеченный, но с очень низкой контрастностью (рис. 12.4).
Рис. 12.4. Все состояния флажка Checkbox
Поскольку состояний может быть не 2, а 3, то булевского значения в таких случаях оказывается недостаточно, чтобы представить все варианты. В таком случае для контроля или назначения состояния флажка используют свойство State. Оно может принимать следующие 3 значения типа TCheckBoxState:
cbUnchecked – флажок не отмечен;
cbChecked – флажок отмечен;
cbGrayed – флажок недоступен.
Следует учитывать, что при изменении состояния переключателя не только пользователем (щелчком мышкой или нажатием пробела, когда элемент имеет фокус ввода), но и программно, происходит событие onClick. Это бывает удобным, когда требуется выполнять те или иные инструкции, в зависимости от текущего состояния флажка. Скажем, его включение или выключение может быть признаком доступности других элементов интерфейса на той же форме.
Второй вид переключателей – зависимые – представлен в Delphi компонентом RadioButton (отсюда и его жаргонное название «радиокнопка»). Как уже было отмечено, его основным отличием от флажка является то, что изменение состояния одного такого переключателя влияет на состояние других. Иначе говоря, только один зависимый переключатель в группе может быть отмечен. Поэтому такие переключатели обычно используют для указания взаимоисключающих опций. Соответственно, щелчком мышки его можно только включить. Для отключения же потребуется включить другой зависимый переключатель из этой же группы. Примерно так работают кнопки на старых радиоприемниках, что объясняет название этого компонента.
Поскольку состояний у такой кнопки может быть только 2 – включено или выключено, то и свойств остается только 2 – Checked и Alignment.
Рассмотрим поведение флажков и радиокнопок на примере. Для этого создадим новое приложение, на главную форму которого поместим 2 столбика переключателей: независимые – слева, а зависимые – справа. Для одного из флажков, скажем, последнего, установим свойство AllowGrayed в истину. Затем внизу формы расположим обычную кнопку, а для ее свойства Caption укажем «Сброс» (рис. 12.5).
Рис. 12.5. Переключатели — флажки и радиокнопки
Запустив приложение на выполнение, можно убедиться, что выставление флажков никак не влияет друг на друга, зато при щелчке по любой из радиокнопок только она становится выбранной. Отметим, что при проектировании формы мы не сделали выбранным ни один из переключателей. Соответственно, после того, как хотя бы один раз был произведен щелчок по любой радиокнопке, вернуть все к начальному виду не удастся. Именно потому мы и предусмотрели кнопку сброса. Остается написать лишь код для нее.
Реализовать сброс всех флажков можно последовательно, банальным перечислением каждого элемента и выставления свойства Checked в ложь:
CheckBox1.Checked:=false; CheckBox2.Checked:=false; … RadioButton3.Cgecked:=false;
В принципе, когда флажков всего несколько штук, этот вариант является оптимальным. Тем не менее, рассмотрим и другой способ, который может оказаться полезным при большом числе переключателей. Состоит он в обходе всех компонентов формы при помощи цикла по свойству Controls и в присваивании нужного значения нужному свойству всех подходящих компонентов. В нашем случае задача несколько усложняется, поскольку мы имеем 2 разных класса компонентов, и свойство Checked у них происходит от разных предков. Поэтому нам придется реализовать проверку на принадлежность объекта к тому или иному классу, и действовать уже исходя из этих данных. В результате мы получим следующий обработчик события для щелчка мышкой по кнопке:
procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin for i:=0 to Form1.ControlCount-1 do begin if Form1.Controls[i] is TCheckBox then (Form1.Controls[i] as TCheckBox).Checked:=false; if Form1.Controls[i] is TRadioButton then (Form1.Controls[i] as TRadioButton).Checked:=false; end; end;
Прежде всего, здесь организован цикл, обходящий все дочерние компоненты формы. В теле цикла сначала проверяется, принадлежит ли текущий компонент классу TCheckBox. Если да, то он явно приводится к типу TCheckBox при помощи оператора as, после чего с ним можно поступать так же, как если бы это было явное указание на объект этого класса, т.е. идет обращение к свойству Checked, которому присваивается новое значение. Затем то же самое проделывается для случая, когда компонент оказывается принадлежащим классу TRadioButton.
Такой подход, с использованием обязательной проверки на принадлежность к типу (с применением оператора is) исключает возможные ошибки времени выполнения. Например, если даже все переключатели были бы одного типа, скажем, флажками, нельзя упускать из виду того, что на форме кроме них могут находиться и другие компоненты. Даже в рассматриваемом нами случае такой компонент имеется — это та же кнопка «Сброс», которую, разумеется, не получилось бы привести от типа TButton к типу TCheckBox.
Timer
Компонент Timer располагается на закладке System. Компонент — Timer, или таймер — это невизуальный компонент, который инкапсулирует в себе функции Windows API по взаимодействию с системным таймером. Он имеет всего 2 свойства — Enabled и Interval. Свойство Enabled делает таймер активным или неактивным, а свойство Interval задает промежуток времени (в миллисекундах), через который будет возникать событие OnTimer. Событие OnTimer — это единственное событие этого компонента, оно происходит, через промежуток времени, заданный свойством Interval, если свойство Enabled установлено в истину.
ДОПОЛНИТЕЛЬНО:
Применение таймера может быть самым разнообразным, но первое, что приходит на ум — это реализация часов. Некоторые старожилы, возможно, помнят те времена, когда на компьютерах устанавливалась система Windows 3.1. Нас она в данном случае интересует с той точки зрения, что в ней была программа, представляющая собой часы, выглядящие как аналоговые. Попробуем реализовать такую программу для современных версий Windows, воспользовавшись компонентами Timer и PaintBox.
Для начала создадим новое приложение, назовем его Clock, а главную форму, по традиции — MainFrm. Затем поместим на форму метку, чтобы видеть время в цифровом формате (назовем ее DigitalLbl), и область рисования для отображения времени в аналоговом формате (AnalogPB). Зададим для метки крупный шрифт, а для области рисования установим размер 110 на 110 точек. Ну и, разумеется, нужен еще и сам таймер — назовем его ClockTmr.
Начнем с самого простого — вывода времени в цифровом формате. Для этого в обработчике события OnTimer достаточно написать всего одну строчку:
DigitalLbl.Caption:=TimeToStr(now);
Если учитывать, что по умолчанию принят интервал в 1 секунду, а сам таймер активен, то мы получим работающие часы. Кроме того, можно добавить вывод времени непосредственно в заголовок приложения, чтобы даже в свернутом виде можно было узнать время, взглянув на панель задач. Для этого достаточно изменять значение свойства Title глобального объекта Application:
Application.Title:=DigitalLbl.Caption;
Но, разумеется, гораздо больше кода потребуется написать для того, чтобы выводить время в аналоговом формате. Создадим для этих целей отдельную функцию-метод формы, которая будет принимать значение времени и выводить его на циферблате. Соответственно, она будет находиться в части public класса TMainFrm и будет определяться следующим образом:
procedure DrawClock(t: TTime);
Теперь займемся этой процедурой вплотную. Прежде всего, нам понадобится выводить циферблат часов. В принципе, можно его нарисовать в графическом редакторе и выводить при помощи метода Draw. Тем не менее, мы пойдем другим путем и будем его чертить программными методами. Поскольку мы имеем дело с окружностью, то для начала выведем эллипс, предварительно выбрав цвет фона:
AnalogPB.Canvas.Brush.Color:=clWhite; AnalogPB.Canvas.Ellipse(3,3,108,108);
Затем начертим метки, но для начала определим интервал шага по окружности. Раз мы делаем часы, то пусть этот шаг будет равен 60. Соответственно, по известной формуле, мы получим размер шага (назовем его step, это должна быть переменная типа Double), равный 2?/60:
step:=2*pi/60;
Теперь можно вывести сами метки, начертим их в виде линий с интервалом в 5 минут или, если придерживаться более традиционной, часовой терминологии — по одной на каждый час. Для этого создадим цикл, который 12 раз будет чертить линию от центра окружности к ее периметру:
for i:=0 to 11 do begin AnalogPB.Canvas.MoveTo(55,55); x:=55+Round(52*sin(i*step*5)); //55 — центр окружности, 52 — длина линии y:=55-Round(52*cos(i*step*5)); AnalogPB.Canvas.LineTo(x,y); end;
В результате мы получим пирог, поделенный на 12 частей (рис. 15.5, слева). Скроем часть линий, нарисовав поверх них еще одну окружность меньшего диаметра, чтобы получить более похожее на настоящее часы оформление (рис. 15.5, справа):
AnalogPB.Canvas.Ellipse(7,7,104,104);
Рис. 15.5. Подготовка циферблата аналоговых часов
Итак, циферблат готов, можно приступить к стрелкам. В качестве подготовительной работы разложим полученное процедурой время (t) на составляющие — часы, минуты и секунды, для чего нам понадобятся 4 целых переменных типа Word:
var h,m,s,ms: word; … DecodeTime(t,h,m,s,ms);
Теперь нарисуем самое простое — секундную стрелку: она должна перемещаться безо всяких дополнительных коэффициентов, строго по вычисленному шагу (step). При этом она еще и должна быть самой длинной и самой тонкой. Толщину можно уменьшить визуально при помощи более светлого, по сравнению с черным цвета, например, темно-серого. В результате мы получим следующий код:
AnalogPB.Canvas.MoveTo(55,55); x:=55+Round(49*sin(s*step)); y:=55-Round(49*cos(s*step)); AnalogPB.Canvas.Pen.Color:=clGray; AnalogPB.Canvas.LineTo(x,y);
Следующей будет минутная стрелка. Поскольку минут тоже 60, то, в простейшем случае, для вычисления координат окончания линии мы могли бы воспользоваться той же формулой, что и для секунд, заменив лишь переменную s на m. Но если для секундной стрелки дискретное движение нормально, то для минутной не совсем годится (а для часовой будет вообще не приемлемо). Поэтому нам придется вычислять промежуточное значение, чтобы учитывать секунды, прошедшие с начала минуты:
c:=(m*60+s)/60;
Соответственно, после этого останется вычислить координаты на основе получившегося значения, не забыв вернуть черный цвет и сделать стрелку короче:
x:=55+Round(43*sin(c*step)); y:=55-Round(43*cos(c*step)); AnalogPB.Canvas.Pen.Color:=clBlack; AnalogPB.Canvas.LineTo(x,y);
На последок определимся с часами. Здесь, помимо уже известного решения проблемы с плавным перемещением стрелки, имеется еще 2 вопроса, а именно — необходимость повышающего коэффициента перемещения (т.к. часов меньше 60), и перевод 24-часового формата в 12-часовой. Вопрос с форматом решается просто: достаточно проверять переменную h на то, не больше ли она 12, и если да — то уменьшать ее на 12. На этом основании можно будет вводить коэффициент, равный 5 (60/12):
if h12 then h:=h-12; c:=(h*60+m)/60*5;
После этого остается вычислить значения и нарисовать короткую толстую стрелку, не забыв после этого вернуть толщину линии обратно:
x:=55+Round(33*sin(c*step)); y:=55-Round(33*cos(c*step)); AnalogPB.Canvas.Pen.Width:=2; AnalogPB.Canvas.LineTo(x,y); AnalogPB.Canvas.Pen.Width:=1;
Последним штрихом может быть изменение оформление окна — для такого приложения лучше всего будет установить свойство BorderStyle в bsToolWindow (рис. 15.6).
Рис. 15.6. Работающие аналоговые часы, написанные на Delphi
Еще одним дополнением может стать размещение окна с часами в нижнем левом углу экрана, для чего в обработчике OnCreate главной формы следует предусмотреть соответствующий код. Итоговый код программы приведен в листинге 15.2.
ListBox
Простой список, представленный компонентом ListBox, представляет собой прямоугольную область, в которой располагаются его элементы – строки. Если строк в списке больше, чем может поместиться в отведенной области, то автоматически появляется полоса прокрутки.
Класс TListBox является наследником класса TWinControl и имеет собственные свойства, представленные в таблице 12.5.
Таблица 12.5. Основные свойства ListBox | ||
Свойство | Тип | Описание |
AutoComplete | Boolean | Определяет, должен ли список реагировать на нажатие клавиш таким образом, чтобы находить и выделить совпадающий элемент |
BorderStyle | TBorderStyle | Определяет, должна или нет быть рамка вокруг списка. Допустимые значения: bsNone, bsSingle |
Columns | Integer | Определяет количество колонок, видимых без горизонтальной прокрутки |
Count | Integer | Указывает на количество элементов списка |
ItemIndex | Integer | Определяет порядковый номер выбранного элемента, начиная с 0. Если не выбрано ни одного, то устанавливается в -1 |
Items | TStrings | Содержит строки списка |
MultiSelect | Boolean | Определяет, допустимо или нет производить множественный выбор |
SelCount | Integer | Указывает на количество выбранных элементов |
Selected | array of Boolean | Определяет, выбран или нет тот или иной элемент списка |
Sorted | Boolean | Определяет, должен ли список быть отсортирован |
TopIndex | Integer | Определяет порядковый номер элемента, который является самым верхним в видимой части списка |
Отдельное пояснение следует дать для свойства Columns. Дело в том, что если оно имеет значение, отличное от принятого по умолчанию нуля, то список становится не простым вертикальным, а многоколоночным, с горизонтальной прокруткой по колонкам для случая, если все элементы не умещаются на отведенной для списка области. Поэтому, в зависимости от того, какое значение имеет параметр Columns, один и тот же список может выглядеть совершенно по-разному (рис. 12.3).
Рис. 12.3. Влияние значения свойства Columns на внешний вид списка
ПРИМЕЧАНИЕ
Следует отметить, что многоколоночные списки используются довольно-таки редко. Пожалуй, едва ли не единственное их применение – это проводник Windows в режиме списка. При этом в нем используется совершенно другой компонент, аналогом которого в VCL является ListView.
Рассмотрим также свойство MultiSelect, которое оказывает непосредственное влияние на ряд других свойств. Так, если оно установлено в истину, то свойство ItemIndex всегда будет равно нулю, а если в ложь, то свойство SelCount всегда будет иметь значение -1. Что касается его непосредственного влияния на список, то оно позволяет выделять сразу несколько элементов с использованием мышки и клавиш Shift и Ctrl.
Если рассматривать методы списка, то основная их часть предназначена для выполнения тех или иных манипуляций над выбранными элементами:
ClearSelection – снимает выделение со всех выбранных элементов;
CopySelection – копирует выбранные элементы в другой список;
DeleteSelected – удаляет все выделенные элементы из списка;
SelectAll – выделяет все элементы в списке.
Еще 2 метода выполняют работу, характерную для списков вообще. Так, метод Clear удаляет все элементы списка, а метод AddItem добавляет новый элемент, и, при необходимости, ассоциирует с ним объект:
List1.AddItem(‘Строка – новый элемент списка’,nil);
В данном случае вместо ссылки на объект был использован указатель nil, т.е. никакого объекта не было ассоциировано.
ПРИМЕЧАНИЕ
Следует учитывать, что при работе с визуальными компонентами, представляющими списки, в случаях, когда одна и та же задача решается как методами свойства Items, так и собственными методами компонента, желательно использовать методы самих компонент. Т.е. List1.Clear предпочтительнее, чем List1.Items.Clear.
Разработка диалоговых окон в Excel, часть 4. Флаги CheckBox