Давайте пробежимся по компонентам и посмотрим на их основные свойства и методы использования, чтобы впоследствии было проще с ними работать. Я разбил рассмотрение компонентов на три больших раздела — в соответствии с их поло-жением на панели Toolbox. В этом разделе речь пойдет о компонентах из группы Common Controls.
Мы будем рассматривать компоненты в том порядке, в котором они находятся в группе, т. е. по алфавиту. Некоторые из них в нашем рассмотрении могут быть пропущены по разным причинам.
Если вы впервые сталкиваетесь с программированием и впервые работаете с .NET, то я рекомендовал бы больше поиграть со свойствами рассматриваемых ком-понентов. Что я под этим понимаю? Нужно пробовать устанавливать различные значения свойств, которые мы будем рассматривать, и, запуская приложение, смот-реть на результат работы. Я мог бы расписывать вам каждое свойство очень по-дробно, но ничего не заменит реальной практики, которую вы можете получить, если сами будете пытаться, пробовать и изучать.
Мы станем рассматривать свойства в основном с точки зрения дизайна, но не забывайте, что это просто свойства компонентов, а, значит, вы можете изменять их в коде программы. Впоследствии мы будем чаще обращаться к свойствам и мето-дам компонентов именно в коде.
Создайте новый проект и перетащите на его форму кнопку Button. Расположите ее ближе к левому верхнему углу окна. Выделите кнопку и найдите свойство Text. Это свойство отвечает за текст, который будет отображаться на поверхности кноп-ки. По умолчанию свойство равно имени компонента, а имя компонента именуется как buttonN, где N — это номер компонента с таким именем, т. е. самая первая кноп-ка на форме получит имя button1.
Мы ранее договорились, что компоненты не должны иметь бессмысленных имен, поэтому давайте изменим свойство Name кнопки, чтобы оно обрело смысл. Какое разумное имя выбрать? Имя должно отражать то, что будет выполнено по нажатию кнопки, а что она будет делать, мы пока не решили, поэтому дадим ей чуть более умное, но, все еще, бессмысленное имя. Напишите в свойстве Name имя myFirstButton. Дизайнер объявит в своем модуле соответствующую переменную, и для доступа к кнопке мы должны будем писать в коде myFirstButton.
Давайте попробуем сделать в нашем примере что-нибудь действующее. Просто поставить кнопку — маловато. Щелкните по ней двойным щелчком, чтобы создать обработчик события Click, который будет вызываться каждый раз, когда вы щелк-нете мышью по кнопке. Это, наверно, самое популярное свойство такого компо-нента, и именно этот обработчик создают программисты в большинстве случаев.
Любое свойство можно изменять и во время выполнения программы. Давайте по нажатию кнопки увеличивать значение позиции кнопки, т. е. увеличивать левую и верхнюю позиции (свойства Left и Top соответственно). Итак, в методе для обра-ботчика события пишем:
private void button1_Click(object sender, EventArgs e) { myFirstButton.Left += 5; myFirstButton.Top += 5; }
Банальное увеличение целочисленного свойства объекта (объектом выступает кнопка), и больше добавить к этому коду просто нечего.
Для чего чаще всего используется кнопка? Конечно же, чтобы на нее нажимали пользователи, поэтому в реальных приложениях, как правило, приходится изме-нять свойство имени, свойство текста и отлавливать событие Click. Еще одно свой-ство, которое может вас заинтересовать: Image, через него вы можете задать кнопке какую-либо картинку.
Что ж, остановимся и посмотрим, как устанавливается картинка. Выделите свойство Image кнопки в панели Properties. Щелкните по появившейся в строке свойства кнопке, и вы должны увидеть окно загрузки изображения Select Resource (рис. 5.13).
Рис. 5.13. Окно загрузки изображения
В этом окне слева можно увидеть два переключателя:
Чтобы загрузить картинку и добавить ее в выбранный файл ресурсов (локаль-ный или выбранный), нажмите кнопку Import. Чтобы назначить выбранное/загру-женное изображение, нажмите кнопку OK.
Теперь очень важный момент — положение картинки на кнопке. По умолча-нию она окажется под текстом. Положение картинки определяет свойство TextImageRelation (отношение текста и картинки). Тут можно выбрать одно из сле-дующих значений:
Еще одно свойство, на котором я хочу остановиться: TextAlign. Если выделить его в окне свойств, то появляется кнопка вызова выпадающего списка, а если щелкнуть по ней, то появится всплывающее окно (рис. 5.14). Здесь 9 кнопок в виде прямоугольников. Щелкая по кнопкам, вы можете выбрать, по какой кромке кноп-ки будет выравниваться текст. По умолчанию выбрана самая центральная кнопка, т. е. текст будет выровнен по центру. Если выбрать правую нижнюю кнопку, то текст будет выровнен к правому нижнему углу кнопки.
Наверно, это все самое интересное при использовании кнопок.
Рис. 5.14. Окно выбора выравнивания текста относительно кноп-ки
Компонент CheckBox позволяет выбирать одно из двух состояний, не считая не-определенного состояния, которое нельзя установить программно. Этот компонент используется, когда нужно, чтобы пользователь выбрал один из двух вариантов: да или нет. Например, в окнах настройки шрифтов флажком CheckBox выбирают, должен быть шрифт полужирным или нет, должен ли он быть подчеркнутым или нет, и всегда вопрос будет: да или нет.
У компонента всего два интересных свойства, не считая общих для всех компо-нентов свойств:
Следующий пример показывает, как можно программно изменять значения этих свойств:
runAtStartupCheckBox.CheckState = CheckState.Indeterminate; deleteOnExitCheckBox.Checked = false;
В первой строке состояние компонента сбрасывается на неопределенное, а во второй строке другому компоненту свойство Checked меняется на false.
У этого компонента есть три интересных события, которые относятся именно к нему и могут пригодиться вам:
Допустим, вам нужно создать список, в котором напротив элементов пользова-тель должен будет ставить галочки. Если список маленький и не очень однород-ный, то можно обойтись несколькими элементами управления типа CheckBox. А ес-ли список состоит из 20 элементов? А если список динамический и может изме-няться? Вот тут на помощь приходит CheckedListBox. Пример такого компонента можно увидеть на рис. 5.15.
На рис. 5.15 показано окно, в котором в виде списка с флажками CheckBox при-веден список покупок. Подразумевается, что пользователь идет с ноутбуком или другим устройством по гипермаркету и отмечает по своему списку, что он купил, а что нет.
Рис. 5.15. Пример компонента CheckedListBox
Поскольку список на каждую неделю отличается (по крайней мере, у меня), то реализовывать эту динамику намного проще через компонент CheckedListBox.
Создайте новое приложение и поместите на его форму экземпляр компонента CheckedListBox. Выделите компонент и в панели Properties найдите свойство Items. Выделите его, а затем щелкните по появившейся кнопке. Появится окно небольшо-го текстового редактора, в котором можно вводить элементы списка. Каждый от-дельный элемент нужно писать в новой строке.
По умолчанию все элементы списка расположены в одну колонку. Если вы хо-тите расположить их в несколько колонок, как это сделано у меня, то нужно уста-новить свойство MultiColumn в true. Чтобы элементы в списке были отсортированы, свойство Sorted нужно установить в true.
Как работать с компонентом? Нам может понадобиться узнать, какой элемент выбран сейчас, т. е. на каком элементе сейчас стоит курсор. Это можно узнать через свойство SelectedItem следующим образом:
if (checkedListBox1.SelectedItem != null) checkedListBox1.SelectedItem.ToString();
В первой строке я проверяю, не является ли выделенный элемент нулевым. Ес-ли он нулевой, то ничего не выделено, и мы не можем работать со свойством SelectedItem, которое имеет тип Object, т. е. само по себе является объектом класса. В таком случае свойство нулевое, и для него память не распределена, а, значит, обращение к свойствам или методам SelectedItem приведет к ошибке.
Если свойство не нулевое, то мы можем привести его к строке и увидеть имя элемента, который выделен. Именно это и происходит во второй строке.
Теперь посмотрим, как можно узнать все элементы, на которых стоят галочки, т. е. все отмеченные элементы. Выделенный и отмеченные элементы — это разные вещи.
Отмеченные элементы хранятся в виде коллекции (списка) в свойстве CheckedItems. Мы можем перебрать это свойство с помощью цикла — например, foreach:
foreach (String str in checkedListBox1.CheckedItems) MessageBox.Show(str);
В этом случае в цикле просто выводятся все отмеченные элементы в окне со-общений, поэтому, если будете тестировать пример, не отмечайте слишком много элементов, чтобы много раз не нажимать кнопку OK для закрытия окна сообщений.
Вы можете управлять списком элементов в списке CheckedListBox. Список эле-ментов хранится в свойстве Items. Это свойство имеет тип класса коллекции Collection. Коллекции требуют отдельного разговора, а пока я хочу только сказать, что у этого свойства есть такие методы, как Add() (добавить новый элемент), Remove() (удалить), Clear() (очистить) и т. д. Коллекции встречаются очень часто, и в большинстве случаев работа с ними идентична, поэтому не буду торопиться и показывать вам все возможности коллекций, — мы станем знакомиться с ними постепенно.
Следующий пример показывает, как можно добавить в список новый элемент:
checkedListBox1.Items.Add("Это что-то", true);
В качестве первого параметра методу Add() коллекции Items передается строка, содержащая текст, который будет назначен в качестве заголовка новому элементу. Второй параметр определяет, должен ли новый пункт быть помеченным или нет. Чаще всего коллекции компонентов будут принимать только один параметр — тек-стовое название. Но тут не совсем обычный компонент, потому что у элементов есть свойство в виде флажка, который можно установить при создании.
Следующий интересный пример показывает один из вариантов удаления отме-ченных элементов:
foreach (int index in checkedListBox1.CheckedIndices) checkedListBox1.Items.RemoveAt(index);
В этом случае запускается цикл, перебирающий все элементы массива, который содержится в свойстве CheckedIndices. А содержится в этом свойстве массив чисел, которые являются индексами помеченных элементов. Если ничего не помечено, то это свойство содержит пустой массив.
Внутри цикла вызывается метод RemoveAt() свойства Items. Этот метод удаляет из коллекции Items элемент под индексом, указанным в качестве параметра. А это именно то, что нам нужно.
Такое удаление таит в себе опасность. Попробуйте запустить пример, выделите последние два элемента и нажмите кнопку удаления. Программа завершит работу аварийно. Допустим, что в списке 10 элементов, и вы выделили 9-й и 10-й для уда-ления. После удаления 9-го количество элементов сокращается до 9, и на следую-щем шаге удаление 10-го станет невозможным, потому что он уже сместился на 9-ю позицию.
Проблему можно решить банальным запуском удаления, начиная с максималь-ного индекса, к минимальному:
for (int i = listBox1.CheckedIndices.Count - 1; i >= 0; i--) listBox1.Items.RemoveAt(listBox1.CheckedIndices[i]);
В этом примере я запускаю цикл, начиная с последнего элемента из списка CheckedIndices, до нулевого. Свойство Count возвращает количество элементов в коллекции. Не забываем, что индексы нумеруются с нуля, а количество элементов отображает реальное количество, поэтому при инициализации переменной i я вы-читаю единицу из количества. Цикл идет с самого большого к минимальному, поэтому на этот раз, если выделить последние два элемента из 10, сначала будет удален 10-й, а потом 9-й, что абсолютно не нарушает пределы коллекции. После удаления 10-го в коллекции остается 9 элементов, а нам как раз нужно удалить 9-й.
Среди событий компонента вас могут заинтересовать следующие:
Следующий пример показывает обработчик события SelectedValueChanged. При изменении выделенного элемента его заголовок будет отображаться на поверх- ности метки selectedLabel:
private void checkedListBox1_SelectedValueChanged( object sender, EventArgs e) { selectedLabel.Text = checkedListBox1.SelectedItem.ToString(); }
Выпадающий список ComboBox удобен тогда, когда нужно выбрать одно значе-ние из целого списка. Создайте новое приложение и поместите на форму экземпляр компонента ComboBox.
Элементы выпадающего списка задаются в свойстве Items, который имеет тип коллекции, схожий с тем, что мы рассматривали в разд. 5.7.3 у компонента CheckedListBox.
Давайте посмотрим, какие самые интересные свойства добавляет класс ComboBox к базовому классу компонентов:
Рис. 5.16. Стили выпадающего списка ComboBox
Продолжим знакомиться с методами коллекции и посмотрим, как можно доба-вить сразу множество элементов. Для этого у коллекции есть метод AddRange(). Этому методу передается массив объектов класса Object, а так как это предок для любого класса, то, значит, можно передать массив любых значений. Чтобы отобра-зить в списке элементы в виде строк, компонент преобразует значения объектов в строку с помощью метода ToString(), наследуемого от Object.
Следующий пример показывает, как можно создать массив из элементов и тут же передать его методу AddRange() без сохранения в какой-либо переменной:
comboBox1.Items.AddRange(new String[] { "Отлично", "Хорошо"});
Если необходимо очистить сразу все элементы списка, то можно воспользо-ваться методом Clear() коллекции:
comboBox1.Items.Clear();
Теперь посмотрим, как можно узнать, какой элемент выделен в выпадающем списке. Чтобы узнать выделенный элемент в виде текста, можно использовать свойство SelectedItem:
if (comboBox1.SelectedItem != null) { String str = comboBox1.SelectedItem.ToString(); MessageBox.Show(str); } else MessageBox.Show("Ничего не выделено");
Прежде чем работать со свойством SelectedItem, желательно проверить его на нулевое значение. Если в выпадающем списке ничего не выделено, то SelectedItem будет равен null. При попытке привести свойство к строке с помощью метода ToString(), когда свойство равно нулю, программа сгенерирует ошибку.
Если вы хотите узнать индекс выделенного элемента из списка, то можно вос-пользоваться свойством SelectedIndex. Это свойство — число, и его проверять на null не нужно. Если ничего не выделено, то свойство вернет 1. Это же значение будет, если вы введете в поле свое значение, а не выберете что-то из списка.
Среди событий компонента вас могут заинтересовать SelectedIndexChanged и SelectedValueChanged, которые мы уже видели в разд. 5.7.3.
Этот компонент удобен в тех случаях, когда пользователю нужно вводить в программу дату или время. И то, и другое имеет отдельный тип данных, о котором мы будем говорить в разд. 6.5, а сейчас познакомимся с компонентом DateTimePicker.
Он выглядит как выпадающий список, только в правом углу вместо стрелки для открытия списка находится значок, по нажатию на который появляется всплываю-щее окно для выбора даты. Это очень удобное окно, позволяющее избежать ошибок при вводе компонентов даты.
У компонента множество интересных свойств, дающих возможность настроить внешний вид календаря на любой цвет и вкус. Но самые интересные свойства — это функциональные:
Чтобы получить в коде выбранную пользователем дату, можно использовать следующий код:
DateTime dt = dateTimePicker1.Value;
Выбранное пользователем значение сохраняется в переменную класса DateTime. Да, время в .NET представляется классом DateTime. Это не просто переменная, а класс с большим количеством свойств и методов.
Самым интересным событием компонента можно назвать ValueChanged, которое генерируется каждый раз, когда вы изменяете дату или время внутри компонента. Но пользователь далеко не всегда может выбрать новое значение даты за один раз. Он может выбрать значение, подумать, а потом снова изменить его. Если вы хотите после изменения даты или времени тут же перерисовывать что-то, то это событие будет как раз кстати.
Если вам не нужно отслеживать изменения, которые делает пользователь, и вы хотите получить только окончательный выбор, то в этом случае может быть вы-годнее обрабатывать событие Leave, которое генерируется, когда компонент теряет фокус ввода, т. е. пользователь переключился на работу с другим элементом управ-ления.
Основная цель обоих компонентов — создание подписей, а у LinkLabel есть до-полнительные возможности для задания части текста на компоненте в виде под-черкнутой ссылки, при наведении на которую курсор будет меняться на значок с рукой.
У компонента Label основными свойствами являются:
Компонент LinkLabel добавляет следующие свойства:
Несмотря на то, что компонент LinkLabel похож на интернет-ссылку всеми сво-ими фибрами, он только похож, а не работает как интернет-ссылка. Если вы введе-те в текстовое поле интернет-адрес (URL) и щелкнете по ссылке в запущенном приложении, ничего не произойдет. Вы должны сами написать код, который дол-жен выполняться по нажатию ссылки, и делать это нужно в обработчике события LinkClicked.
Хотя я убегаю сейчас в сторону, я покажу вам, как можно в обработчике собы-тия запустить браузер и загрузить страницу:
System.Diagnostics.Process.Start("http://www.flenov.info");
В качестве параметра этого метода передается строка, которую нужно запу-стить в системе. В данном случае это URL, а для обработки URL ОС Windows запу-стит браузер по умолчанию и загрузит указанную страницу. Этой же командой можно запустить файл на выполнение. Для этого нужно просто указать имя файла, который вы хотите открыть или запустить.
По своим свойствам компонент очень похож на выпадающий список CheckedListBox, потому что они очень похожи и по смыслу. Но список ListBox не имеет около каждого элемента списка компонента CheckBox, позволяющего ставить флажки.
С другой стороны, у ListBox работает свойство SelectionMode. Вы можете уста-новить его в MultiSimple или MultiExtended, чтобы дать возможность пользователю выбирать несколько элементов из списка. В CheckedListBox попытка установить любое из этих значений приведет к сообщению об ошибке.
Если нужно узнать, какой элемент сейчас выбран, можно воспользоваться свой-ством SelectedItem. Если вы разрешили возможность множественного выбора, то список выделенных элементов можно получить из свойства SelectedItems. Следу-ющий пример перебирает список выделенных компонентов и выводит каждую строку в диалоговом окне:
foreach (string str in listBox1.SelectedItems) MessageBox.Show(str);
Среди событий компонента вас могут заинтересовать SelectedIndexChanged и SelectedValueChanged, которые мы уже видели в разд. 5.7.3.
Представление списка ListView — достаточно сложный на данном этапе изуче-ния компонент и очень мощный, потому что обладает громадными возможностями. С подобным компонентом мы работаем практически каждый день, потому что в окне Мой компьютер значки и имена файлов отображаются с помощью компо-нента этого класса.
Итак, пробежимся по свойствам компонента:
Рис. 5.17. Редактор колонок
Рис. 5.18. Редактор элементов списка
Почему для свойств колонок, групп и элементов списка появляется окно редак-тора? Такое окно появляется для любых свойств нестроковых коллекций. До этого у выпадающего списка и у списка выбора мы видели строковые коллекции, и для их редактирования достаточно было простого текстового редактора. Тут у нас кол-лекции более сложных объектов, и для редактирования их свойств нужен специа-лизированный редактор, который и отображается.
Как вы уже поняли, элементы списка хранятся в свойстве Items. У этого свой-ства есть несколько перегруженных вариантов метода Add(), чтобы добавлять эле-менты в список. Самый простейший способ добавить новый элемент — передать методу текстовую строку, которая будет установлена в качестве заголовка нового элемента:
listView1.Items.Add("Заголовок элемента");
Каждый элемент списка имеет класс ListViewItem, и в данном случае элемент такого класса создается автоматически, и ему присваивается указанный заголовок.
Если нужно настроить несколько свойств нового элемента, то удобнее и эффек-тивнее создать объект класса ListViewItem и передать этот объект методу Add(). Например, как в следующем примере:
ListViewItem newItem = new ListViewItem("Test"); newItem.Group = listView1.Groups[0]; listView1.Items.Add(newItem);
Здесь явно создается экземпляр класса ListViewItem. Конструктору этого класса передается строка, которая станет заголовком элемента. Во второй строке у этого же элемента изменяется свойство Group. Группа имеет тип ListViewGroup, и уже су-ществующие группы находятся в списке свойства Groups. Чтобы не придумывать ничего сложного, я просто использую нулевую группу из этого списка (надеясь, что она там уже есть). Теперь остается только вызвать метод Add() у коллекции и передать ему созданный и настроенный объект класса ListViewItem.
А как добавить группу программно? Посмотрите на следующий пример:
// создать группу ListViewGroup newGroup = new ListViewGroup("Группа"); listView1.Groups.Add(newGroup); // создать элемент ListViewItem newItem = new ListViewItem("Test", newGroup); listView1.Items.Add(newItem);
Создание группы похоже на создание элементов списка, потому что группы — это тоже коллекции. Обратите внимание на то, как создается на этот раз элемент списка. В этом примере я передаю конструктору не только заголовок, но и объект группы, которая была создана только что.
В реальных приложениях может понадобиться также и определение выделен-ных элементов. У представления списка есть возможность выбирать несколько элементов сразу. Список этих элементов находится в свойстве SelectedItems. Это, опять же, коллекция из элементов ListViewItem, и ее можно просмотреть с помощью цикла foreach, например:
foreach (ListViewItem item in listView1.SelectedItems) MessageBox.Show(item.Text);
В этом примере в цикле перебираются все выделенные в списке элементы и с помощью диалогового окна MessageBox отображаются названия элементов.
Теперь посмотрим, как можно удалять элементы из списка:
if (listView1.Items.Count > 0) listView1.Items.Remove(listView1.Items[0]);
Прежде чем удалять элемент, нужно убедиться, что он есть. Я удаляю в этом примере нулевой элемент, поэтому достаточно удостовериться, что в списке есть хотя бы один элемент, т. е. Items.Count больше нуля. После этого вызывается метод Remove() свойства Items, которому нужно передать удаляемый элемент. Я выбираю нулевой элемент списка Items[0].
Далеко не всегда удобно удалять элемент по объекту, иногда удобнее исполь-зовать индексы. Следующий пример удаляет 5-й элемент, предварительно прове-рив, что в списке есть не менее пяти элементов:
if (listView1.Items.Count > 5) listView1.Items.RemoveAt(5);
Для удаления использовался метод RemoveAt(), которому передается индекс удаляемого элемента.
Среди событий, которые вас могут заинтересовать при работе с компонентом, можно выделить следующие:
У компонента есть очень мощная возможность — программист сам может реа-лизовать функцию рисования элементов представления списка. Таким образом, элементы списка могут быть окрашены в любой цвет, все зависит от того, какой код вы напишете и как вы захотите нарисовать элементы списка.