Компонент PictureBox позволяет отображать картинку, и его чаще используют с целью оформления в тех местах, где нужно добавить какое-то изображение. Основное свойство компонента — Image. Если вызвать редактор свойства, то по- явится окно, которое мы уже видели при рассмотрении работы со значком (см. рис. 5.13).
Вы можете указать положение картинки на диске или в Интернете. В этом случае картинка не попадет в файл ресурсов, а останется на том месте, где и была. В компоненте будет только ссылка на файл, которую вы должны указать в свойстве ImageLocation. Компонент загрузит картинку после загрузки программы.
Я рекомендую указывать всегда относительные пути к файлам, если вы хотите поставлять их вместе с программой. Например, не нужно указывать полный путь к файлу типа C:\projects\myprogram\image.jpg. Когда пользователь установит у себя вашу программу, у него, скорее всего, не будет такого же пути, а создавать его и копировать туда файл во время установки программы — это самоубийство. Вашу программу сотрут сразу же.
Чтобы этого не произошло, самый простой способ — помещать файлы в ту же папку, что и исполняемый файл, а в свойстве ImageLocation указывать только имя файла, без пути.
Если вы укажете ссылку на картинку в Интернете, то после старта программы ее загрузка потребует некоторого времени. Чтобы компонент отображал на время загрузки хоть что-то, вы можете указать начальную картинку в свойстве InitialImage. Если картинка из Интернета по каким-либо причинам не сможет быть загружена (например, нет доступа к сайту, где она лежит), то будет отображена картинка из свойства ErrorImage.
Есть еще одно свойство, которое вам может пригодиться, — SizeMode. Это перечисление, позволяющее выбрать одно из следующих значений:
Во время выполнения продолжительного действия очень важно показать пользователю, что программа не зависла, а выполняет запрошенные действия. Когда вы копируете или скачиваете из Интернета какой-нибудь большой файл, то система отображает информационное окно, в котором бежит индикатор процесса ProgressBar.
У этого компонента есть три интересных свойства:
Создайте новое приложение и поместите на форму компоненты ProgressBar и кнопку. Никакие свойства менять не будем, а просто создайте событие Click для кнопки и в нем напишите:
for (int i = 0; i < 100; i++) { progressBar1.Value = i; Thread.Sleep(100); }
В примере запускается цикл, который выполняется от 0 до 100. Внутри цикла просто присваиваем свойству Value компонента ProgressBar очередное значение переменной i, которая увеличивается на 1 на каждом шаге. Чтобы цикл работал не очень быстро, в него добавлена строка с вызовом статичного метода Sleep() класса Thread, которая создает задержку на переданное в качестве параметра значение в миллисекундах.
Этот компонент мне напоминает знаменитую фразу: "Один за всех, мы за ценой не постоим!" Видимо потому, что он очень похож на CheckBox и позволяет пользователю выбирать этот компонент, но только один из компонентов RadioButton на контейнере может быть выделен. Под контейнером понимается любой компонент, на поверхности которого можно расположить другие компоненты. Мы пока работаем только с формами, поэтому на форме может быть выделен только один из компонентов класса RadioButton. Но существуют другие контейнеры, и они расположены в разделе Containers панели Toolbox (их мы будем подробнее рассматривать в разд. 5.8).
Основные свойства компонента — это, конечно же, Text, где вы должны задавать текст, который будет отображаться на компоненте, и Checked, которое указывает на то, выделен компонент или нет.
Создайте новое приложение и поместите на его форму три компонента класса RadioButton. Запустите приложение и попробуйте пощелкать по ним. Выбор с одного компонента переходит на другой автоматически. Вам не нужно снимать выделение с текущего компонента, прежде чем выбирать новый.
Иногда бывает необходимость сделать какую-то проверку, прежде чем разрешить пользователю выбрать определенный компонент RadioButton. Как это сделать, если все выделяется автоматически? В этом случае у свойства, которое нужно про- верять, следует установить свойство AutoCheck в false. Теперь, если вы щелк- нете по этому компоненту, то он не выделится. Мы должны сами отлавливать событие Click по компоненту, производить проверку и самостоятельно изменять свойство:
private void radioButton3_Click(object sender, EventArgs e) { if (CanChange) radioButton3.Checked = !radioButton3.Checked; }
Здесь происходит проверка, и если какое-то свойство по имени CanChange равно true, то свойству Checked компонента radioButton3 присваивается противоположное значение этого же свойства этого же компонента.
Запустите приложение и попробуйте выбрать третий компонент. Обратите внимание, что на этот раз выделение с компонента, который был выделен до этого, не было снято автоматически. Компоненты, у которых свойство AutoCheck равно false, не влияют на выделение других компонентов на форме. То есть, мы можем сделать так, чтобы два и более компонента RadioButton на одном контейнере были выделены. Честно сказать, не знаю, где это можно использовать, потому что это может сбить с толку пользователя.
Этот компонент предназначен для ввода данных пользователем. Если вам нужно, чтобы пользователь ввел строку, число или еще что-либо, мы чаще всего установим на форму именно этот компонент. У него не так уж много значащих свойств:
У компонента всего пара интересных событий. Первое из них: TextChanged, оно генерируется каждый раз, когда пользователь изменил текст в компоненте.
Еще одно свойство, которое может вам пригодиться: Modified. Значение свойства автоматически изменяется на true, если пользователь внес какое-то изменение в текст компонента. Сбросить значение на false можно только вручную. Например, с помощью следующего обработчика события ModifiedChanged мы отображаем в метке label2 состояние компонента:
private void lastnameTextBox_ModifiedChanged(object sender, EventArgs e) { if (lastnameTextBox.Modified) label2.Text = "Изменен"; else label2.Text = "Сохранен"; }
Чтобы сбрасывать свойство Modified в false, можно добавить на форму кнопку и по ее нажатию написать:
lastnameTextBox.Modified = false;
Теперь, если внести изменение в текст, то свойство Modified изменится на true, а по нажатию кнопки свойство будет меняться на false.
Последний компонент из разряда общих, который мы здесь рассмотрим, — это TreeView, он позволяет выстраивать информацию в виде дерева. Какой пример дерева можно привести? А вот, например, в панели Solution Explorer структура проекта построена в виде дерева.
Самые интересные из свойств компонента TreeView:
Рис. 5.19. Редактор элементов списка дерева
Давайте создадим приложение и бросим на него компонент дерева. Теперь положим на него еще три кнопки: Добавить корневой элемент, Добавить дочерний элемент и Удалить элемент. По нажатию первой кнопки пишем следующий код:
TreeNode newNode = new TreeNode("Корневой элемент"); treeView1.Nodes.Add(newNode);
В первой строке мы объявляем переменную класса TreeNode и инициализируем ее. Что это за класс? Это класс элементов дерева. Заголовок элемента я передал в ка честве параметра конструктору при инициализации, хотя можно было написать и так:
TreeNode newNode = new TreeNode(); newNode.Text = "Корневой элемент";
В этом примере сначала инициализируется переменная newNode конструктором по умолчанию, а потом устанавливается свойство Text.
Чтобы добавить корневой элемент дерева, нужно вызвать метод Add() свойства Nodes компонента дерева. Помните, мы вызывали редактор именно свойства Nodes из панели Properties при работе с компонентом визуально?
Метод Add() имеет множество перегруженных вариантов для вашего удобства. Можете использовать тот, который больше нравится или который лучше подходит к конкретной ситуации. Например, добавить корневой элемент можно было и одной строкой кода:
treeView1.Nodes.Add("Корневой элемент");
В этом случае метод сам создаст экземпляр класса TreeNode для хранения элемента дерева и присвоит ему заголовок, который вы передали методу. К такому варианту прибегают в тех случаях, когда вам не нужно где-то дополнительно хранить переменную объекта элемента дерева или выполнять дополнительные манипуляции с элементом.
А как добавить дочерний элемент к уже существующему корню? Как вообще хранятся дочерние элементы? Если корневые элементы хранятся в виде коллекции прямо в свойстве Nodes компонента дерева, то дочерние элементы хранятся у своих родительских элементов в свойстве Nodes. Посмотрим на следующий пример:
if (treeView1.SelectedNode != null) treeView1.SelectedNode.Nodes.Add("Дочерний элемент");
В свойстве SelectedNode компонента TreeView хранится выделенный элемент дерева. Это свойство имеет тип TreeNode со всеми вытекающими отсюда последст- виями.
Здесь я сначала проверяю свойство SelectedNode на нулевое значение. Заведите и себе в привычку, прежде чем обращаться к свойствам объектной переменной, которую вы сами явно не инициализировали, всегда проверять ее на нулевое значение. В данном случае, если в дереве ничего не выбрано, свойство может равняться нулю, и попытка обратиться к Nodes приведет к сбою. Я обезопасил нас от сбоя, поэтому для добавления дочернего элемента по отношению к выделенному мы можем вызвать метод Add() свойства Nodes объекта SelectedNode. Во как получилось! Этот метод также имеет множество перегруженных вариантов, и ему тоже можно передать заранее созданный экземпляр класса TreeNode.
У нас на форме осталась еще одна кнопка для удаления элемента. Давайте напишем для нее код:
if (treeView1.SelectedNode != null) treeView1.SelectedNode.Remove();
Сначала уже по привычке проверяем свойство SelectedNode на нулевое значение, и если оно не нулевое, то вызываем метод Remove(), который удаляет текущий элемент. Если у него есть дочерние элементы, то они исчезнут вместе со своим родителем.
Если вы хотите удалить только дочерние элементы, не трогая их родителя, то можно выполнить следующую строку:
treeView1.SelectedNode.Nodes.Clear();
Как мы уже знаем, дочерние элементы находятся в свойстве Nodes, и именно у этого свойства есть методы добавления новых дочерних элементов и метод Clear() для очистки. Свойство Nodes — содержит элементы со всеми вытекающими последствиями, т. е. все, что мы говорили ранее и будем говорить в будущем про коллекции, будет затрагивать и это свойство.
Коллекции схожи с массивами, и для доступа к заголовку нулевого корневого элемента дерева нужно написать: treeView1.Nodes[0].Text, а для доступа к первому элементу: treeView1.Nodes[1].Text (на рис. 5.19 это Корневой элемент 2).
Для доступа к нулевому дочернему элементу нулевого корневого элемента нужно уже написать: treeView1.Nodes[0].Nodes[0].Text и т. д., вглубь по веткам нашего дерева.
А что, если нужно просмотреть элементы дерева, — например, для сохранения их в файл или для поиска в дереве? Тут хорошо подходит классическая задача рекурсивного вызова. Давайте посмотрим, как это реализовать в виде кода. Следующий метод принимает в качестве параметра путь и коллекцию элементов:
void ViewCollection(string caption, TreeNodeCollection collection) { if (caption != "") caption += "-"; foreach (TreeNode node in collection) { if (node.Nodes.Count > 0) ViewCollection(caption + node.Text, node.Nodes); else MessageBox.Show(caption + node.Text); } }
Чтобы просмотреть дерево с самого начала, этот метод нужно вызвать следующим образом:
ViewCollection("", treeView1.Nodes);
Первый параметр — пустая строка, т. е. начальный путь пустой. Второй параметр — элементы из свойства Nodes, где хранятся корневые элементы дерева.
Внутри метода сначала проверяем текущее содержимое переменной caption, и если оно не пустое, то добавляем разделитель в виде знака тире.
Так как коллекция схожа с массивом, то мы можем перебирать ее с помощью цикла foreach. Каждый элемент коллекции имеет тип TreeNode, и это мы уже знаем. Внутри цикла проверяем, есть ли у текущего элемента дочерние элементы, и если есть, то рекурсивно вызываем метод ViewCollection() для просмотра этих дочерних элементов. При этом значение первого параметра увеличиваем на значение заголовка текущего элемента. Если дочерних элементов нет, то выводится сообщение, в котором отображается содержимое переменной caption плюс имя текущего элемента.
Таким образом, метод будет перебирать все элементы дерева в поисках конечных пунктов. В процессе поиска через переменную caption накапливается полный путь к этим конечным элементам.
У дерева наиболее интересными являются следующие события:
AfterCheck — возникает, когда пользователь ставит флажок или убирает его с какого-то элемента дерева. У обработчика события второе свойство имеет тип TreeViewEventArgs. У этого класса есть свойство Node, в котором хранится помеченный элемент дерева:
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e) { MessageBox.Show(e.Node.Text); }
AfterCollapse — возникает, когда пользователь свернул какую-либо ветку дерева. Второй параметр имеет тип TreeViewEventArgs, свойство Node которого содержит свернутый элемент дерева;
AfterExpand — возникает, когда пользователь развернул какую-либо ветку дерева. Второй параметр имеет тип TreeViewEventArgs, свойство Node которого содержит свернутый элемент дерева;
AfterLabelEdit — событие возникает, когда пользователь завершил редактирование заголовка элемента дерева. Второй параметр события имеет тип NodeLabelEditEventArgs, который имеет следующие интересные свойства:
AfterSelect — событие возникает, когда пользователь выбирает какой-либо элемент в дереве.
Тут нужно остановиться и заметить, что для всех описанных событий есть двойники, которые вызываются перед возникновением события. Например, для события AfterSelect есть двойник с именем BeforeSelect, который генерируется перед тем, как изменить выделенный элемент. То есть, в обработчике события BeforeSelect в свойстве SelectedNode будет находиться еще старый элемент дерева, который был выделен ранее.
Но продолжим краткую экскурсию по событиям, чтобы вы знали, какие события есть и по какому событию искать дополнительную информацию в MSDN:
DrawNode — событие срабатывает при необходимости нарисовать какой-то элемент дерева. Событие генерируется, только когда дерево находится в режиме OwnerDraw;
NodeMouseClick — возникает, когда пользователь щелкнул по элементу дерева. Второй параметр события имеет тип TreeNodeMouseClickEventArgs, и через его свойства Node и Button мы можем узнать, по какому элементу дерева щелкнули и какой кнопкой мыши;
NodeMouseDoubleClick — возникает, когда пользователь щелкнул по элемен- ту дерева двойным щелчком. Второй параметр события имеет тип TreeNodeMouseClickEventArgs со всеми вытекающими последствиями;
NodeMouseHover — возникает, когда курсор мыши движется над каким-либо элементом.
Это бесплатная глава книги Библия C#. В новом издании эта глава переписана с учетом универсальных при-ложений Windows, а старая версия главы, которая не потеряла еще своей актуаль-ности стала бесплатной и доступной всем.