Разработка приложений на C# в среде Visual Studio 2005

Класс Control и стандартные элементы управления (обзор)

Разбить на страницы
Показывать лекцию целиком

Объектно - ориентированное программирование различает два способа агрегирования (укрупнения) кода: наследование (сцепление) и композиция (включение). В первом случае создается новый класс, в который передаются все данные и методы базового класса так, как будто-бы они были созданы в производном классе. Производный класс считается расширением базового класса. Во втором случае в новом классе создаются объекты других классов как члены-данные этого класса. Класс, который разрабатывается на основе включения других объектов, называется контейнером (родителем), а включенные в него объекты являются дочерними. Чаще всего при разработке новых класов применяют сочетание этих двух способов.

Объект Form по умолчанию содержит строку заголовка с системным меню слева и системными кнопками справа. По периметру окна проходит рамка определения размеров окна. Внутри окна расположена клиентская область - в ней отображается информация и содержатся инструменты для пользователя. Окна могут отображать текст и графику, получать команды с клавиатуры и мыши. Но в большинстве современных окон клиентская область размещает дочерние элементы управления, с помощью которых пользователь управляет программой.

Элементы управления могут быть стандартными (библиотечными, базовыми) или разработанными самим программистом (пользовательскими). В этой части мы изучим стандартные элементы управления, обеспечивающие требуемый графический интерфейс пользователя ( GUI ).

Все элементы, которыми визуальная среда проектирования позволяет манипулировать, размещая их на форме, называются компонентами. Те компоненты, которые остаются видимыми и во время выполнения программы, называются визуальными, и это, как правило, элементы управления пользовательского интерфейса. Те компоненты, которые не видимы во время выполнения - называются невизуальными, или просто - компонентами.

Обычно элементы управления занимают прямоугольную область, хотя могут быть и непрямоугольными и даже скрытыми, тем не менее исправно выполнять свои обязанности. Все элементы управления чувствительны к событиям мыши и клавиатуры и сами способны генерировать ряд событий. Превращая информацию о действиях пользователя в простые события, элементы управления позволяют программисту контролировать эти действия и реагировать на них соответствующим образом.

Дочерние элементы и родительские контейнеры (дочки - матери: семейные разборки)

Все элементы управления должны иметь своего родителя, определяемого свойством Parent. Только элементы управления, имеющие родителя, будут отображены на экране. Родителем может быть не только форма, но и другой элемент управления (это случается сравнительно редко). Положения элемента управления всегда задается относительно родителя и отображается на его поверхности. Часть элемента управления, выходящая за края родителя, не видна.

Класс Form, как наследник класса Control, тоже имеет свойство Parent, но оно обычно имеет значение null. Считается, что родителем формы по умолчанию является рабочий стол, и в этом случае свойство Form.TopLevel (верхний уровень) имеет значение true. Но форма может выступать потомком любого другого элемента управления при значении TopLevel = false, в том числе каркаса MDI - приложения.

Если в свойстве Form.Owner определена ссылка на другую форму, то форма имеет владельца, располагается всегда поверх него и сворачивается или закрывается вместе с ним. Такое поведение полезно при создании немодальных диалоговых окон.

Для того, чтобы элемент управления размещался на форме, сначала нужно создать саму форму как объект, затем создать как объект элемент управления, затем присвоить его свойству Parent ссылку на объект формы (назначить родителя) или выполнить метод свойства Controls.Add() объекта формы. Эти действия по регистрации элемента управления в форме являются равнозначными.

Например, при создании кнопки на форме в конструкторе формы нужно прописать такой код

Button dtn = new Button();
btn.Parent = this;
или
this.Controls.Add(btn);

Свойство Controls наследуется формой от класса Control и является объектом класса ControlCollection. Оно содержит в себе все дочерние элементы формы. Методы Controls.Add() и Controls.Remove() управляют составом этого набора. Коды this.Controls.Remove(btn) и btn.Parent = null эквивалентны. Свойство Controls допускает применение синтаксиса массива.

Приведем пример.

  • Командой File/New/Project создайте пустой проект с именем MyProject
  • Командой Project/Add Reference добавьте к проекту ссылки на базовые сборки System.dll и System.Windows.Forms.dll
  • Командой Project/Add New Item добавьте два пустых файла MainForm.cs и Program.cs
  • Заполните файлы следующим кодом
  • using System;
    using System.Windows.Forms;
        
    namespace MyProject
    {
        class MainForm : Form
        {
            const int MAX_BUTTON = 5;   // Число кнопок
        
            public MainForm()
            {
                // Настраиваем консольное окно
                Console.ForegroundColor = ConsoleColor.White;
        
                // Регистрируем событие загрузки формы
                this.Load += new EventHandler(MainForm_Load);
            }
        
            // Создаем элементы управления в обработчике события Load формы
            void MainForm_Load(object sender, EventArgs e)
            {
                // Создаем кнопки
                for (int i = 0; i < MAX_BUTTON; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    this.Controls.Add(btn);// Отдали кнопку форме
                }
                Console.WriteLine("Создано кнопок: 
        {0}", this.Controls.Count);
        
                // Уничтожаем кнопки
                int count = 0; // Локальная переменная
                Control[] control = new Control[this.Controls.Count];
                // Проводим предварительное обследование коллекции в foreach
                foreach (Control ctrl in this.Controls)
                {
                    if (ctrl is Button)
                    {
                        control[count] = ctrl;// Сохраняем ссылку в массиве
                        count++;
                    }
                }
                // Уничтожаем кнопки по найденым ссылкам
                for (int i = 0; i < count; i++)
                    this.Controls.Remove(control[i]);
                Console.WriteLine("Удалено кнопок: {0}", count);
        
                // Еще раз создаем кнопки
                for (int i = 0; i < MAX_BUTTON + 1; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Parent = this;  // Отдали форме другим способом
                }
                Console.WriteLine("\nСоздано кнопок: 
        {0}", this.Controls.Count);
        
                // Уничтожаем кнопки
                count = 0;  // Сбросили счетчик
                control = new Control[this.Controls.Count];// Переназначили массив
                // Проводим предварительное обследование коллекции в for
                for (int i = 0; i < this.Controls.Count; i++)
                {
                    Control ctrl = this.Controls[i];
                    if (ctrl is Button)
                    {
                        control[count] = ctrl;
                        count++;
                    }
                }
                // Уничтожаем кнопки по найденым ссылкам
                for (int i = 0; i < count; i++)
                    control[i].Parent = null;// Уничтожаем другим способом
                Console.WriteLine("Удалено кнопок: {0}", count);
        
                for (int i = 0; i < MAX_BUTTON + 2; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Click += btn_Click;// Зарегистрировали общий обработчик
                    btn.Parent = this;  // Отдали форме
                }
                Console.WriteLine("\nСоздано кнопок: 
        {0}", this.Controls.Count);
            }
        
            // Уничтожение кнопок интерактивно
            int count = 0; // Поле
            void btn_Click(object sender, EventArgs e)
            {
                this.Controls.Remove((Control)sender);
                Console.WriteLine("{0}) Удалили кнопку 
        щелчком на ней", ++count);
            }
        }
    }

    using System;
    using System.Windows.Forms;
        
    namespace MyProject
    {
        class EntryPoint
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MainForm());
            }
        }
    }

    Результат работы приведенного кода будет таким

    Поскольку все элементы управления произошли от класса Control, а он определяет свойство Controls, то любой элемент управления имеет это родительское свойство и может быть родителем. Но не многие наследники Control используются программистами в качестве родителей. Обычно элементы управления используются в качестве дочерних элементов формы.

    Class Control имеет булево свойство TabStop, которое позволяет контейнерным элементам управлять фокусом ввода своих дочерних элементов с помощью клавиши Tab. Фокус ввода способны получать только те дочерние объекты, у которых свойство TabStop установлено в true. Порядок получения фокуса определяется свойством Control.TabIndex дочерних элементов контейнера или выполняется в порядке их расположения в коллекции (в порядке их добавления в набор, Z -порядок: последний добавленный видится верхним). Элемент управления сдвигается в Z -последовательности на первое или последнее место своими методами BringToFront() или SendToBack() соответственно.

    Дочерние элементы управления одного контейнера (родителя) называются родственными.

    По умолчанию все элементы управления рождаются видимыми и доступными пользователю. Видимость элемента управления определяется свойством Visible, а доступность - Enabled. Те же свойства родителя (контейнера) влияют сразу на все дочерние элементы. Невидимым или недоступным элемент становится только для пользователя, но в составе программы остается полноценным элементом и продолжает работать под ее контролем.

    Привязка и размер дочерних элементов

    Каждому элементу управления может задаваться положение его левого верхнего угла относительно левого верхнего угла клиентской области родителя. Оно определяется свойством Location элемента управления, которое ожидает ссылку на экземпляр класса Point(x, y), где x и y означают количество пикселов смещения. Размер элемента задается через свойство Size, которое ожидает ссылку на объект класса Size(width, height). Ширина и высота также означают количество пикселов относительно точки привязки элемента управления.

    Если в элементе управления (например, кнопке) установить булево свойство AutoSize = true, то установленный размер Size игнорируется и элемент управления подгоняет свои размеры под размер надписи, определяемой свойством Text.

    Свойство Dock (причал) элемента управления используется для растягивания и выравнивания по внутренней стороне родителя. Его значение определяется перечислением DockStyle: None, Left, Right, Top, Bottom, Fill. Свойство Anchor (якорь) заставляет элемент управления замораживать привязку к сторонам родителя (при изменении размеров последнего) на том расстоянии, которое он занимал до изменения размеров родителя. Свойство Anchor принимает значения перечисления AnchorStyles: : None, Left, Right, Top, Bottom и допускает их побитовое сложение ( по умолчанию Anchor = Left | Top).

    Шрифт и цвет дочерних элементов

    Порядок создания объектов в программе следующий: вначале создается главный контейнер, затем создаются все дочерние элементы и присоединяются к контейнеру через ссылки. Если дочерний элемент, в свою очередь, является контейнером, то он создается вперед своих дочерних элементов и ждет, когда к нему будут присоединены ссылки на них. При создании дочерних элементов им неявно передаются значения одноименных свойств контейнера. Главный контейнер приложения получает значения этих свойств от операционной системы и передает их вниз по иерархической цепочке. Если в любом контейнере этой цепочки свойствам назначить другие значения, то новые значения одноименных свойств получат все дочерние элементы.

    Класс Color передает элементам управления свойство Text, которые используют его соответствующим их специализации способом. Например, в форме это заголовок окна, в кнопке это надпись, а полоса прокрутки вообще не использует это свойство. Наследники класса Control получают от него, также, свойства Font, ForeColor и BackColor, которые задают шрифт, его размер, стиль и цвет, а также цвет клиентской области. Если дочерний элемент явно не задает значение этого свойства, то оно перенимает значение контейнера.

    Для иллюстрации сказанного переделаем предыдущий пример.

  • Замените в последнем проекте MyProject код файла MainForm.cs на следующий
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    namespace MyProject
    {
        class MainForm : Form
        {
            const int MAX_BUTTON = 5;   // Число кнопок
        
            public MainForm()
            {
                // Настраиваем консольное окно
                Console.ForegroundColor = ConsoleColor.White;
        
                // Регистрируем событие загрузки формы
                this.Load += new EventHandler(MainForm_Load);
            }
        
            // Создаем элементы управления в обработчике события Load формы
            void MainForm_Load(object sender, EventArgs e)
            {
                this.Text = "Щелкайте на кнопках!";
                // Автоматически показывать полосу прокрутки,
                // когда не помещаются дочерние элементы
                this.AutoScroll = true;
                // При запуске с консольным окном сделать верхней
                this.Activate();
        
                // Переопределяем свойство контейнера,
                // которое неявно передастся дочерним элементам
                this.ForeColor = Color.Red;
        
                // Создаем и настраиваем дочерние элементы
                int x = 0, y = 0; // Размещение кнопок
                for (int i = 0; i < MAX_BUTTON; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Click += btn_Click;// Зарегистрировали общий обработчик
                    btn.Parent = this;  // Отдали форме
                    // Продолжаем настройку, пока не потеряли адрес
                    btn.Text = "Кнопка " + (i + 1).ToString();
                    btn.AutoSize = true;
                    // Настраиваем плоский стиль кнопки (для Flat)
                    btn.FlatStyle = FlatStyle.Flat;
                    btn.FlatAppearance.BorderColor = Color.Blue;
                    btn.FlatAppearance.BorderSize = 5; // В пикселах
                    // Вычисляем точку размещения
                    if ((i + 3) % 2 == 1) // Первая колонка нечетные
                    {
                        x = 0;
                        if (i != 0)// Начальные не смещать
                            y += btn.Size.Height * 2;
                    }
                    else // Вторая колонка четные
                    {
                        x = btn.Size.Width * 2;
                        btn.TabStop = false; // Четные лишаем фокуса ввода 
                    }
                    // Позиционируем дочерний элемент относительно родителя
                    btn.Location = new Point(x, y);
                    // Переопределяем свойство дочернего элемента.
                    // Стиль шрифта побитово суммируем
                    btn.Font = new System.Drawing.Font(
                        "Times New Roman", 14,
                        FontStyle.Italic | FontStyle.Bold);
                    btn.BackColor = Color.Yellow;
                }
        
                // Назначение фокуса ввода
                int indexFocus, count = this.Controls.Count;
                indexFocus = (count + 2) % 2 == 1 ? count - 1 : count;
                if (indexFocus >= 0) // Проверка на случай пустого контейнера
                    this.Controls[indexFocus].Focus(); 
        // Активируем последний в первой колонке
        
                Console.WriteLine("Создано кнопок: 
        {0}", this.Controls.Count);
            }
        
            // Уничтожение пользователем кнопок интерактивно
            void btn_Click(object sender, EventArgs e)
            {
                Control ctrl = sender as Control;
                this.Controls.Remove(ctrl);
                Console.WriteLine("\"" + 
        ctrl.Text + "\" удалена!");
                // Меняем заголовок окна, когда кончились кнопки
                if (this.Controls.Count == 0)
                {
                    this.Text = "Все кнопки удалены !!!!!!";
                }
            }
        }
    }

    Графическое окно, созданное классом-контейнером MainForm, будет выглядеть так

    Цвет надписи кнопка получила от родителя, а шрифт и цвет фона были для нее установлены явно. Обратите внимание на то, как мы программно установили для коллекции дочерних элементов фокус ввода и порядок управления фокусом ввода по клавише Tab. Кнопка, имеющая фокус ввода, реагирует на нажатие клавиш Enter или Space ( пробела - такая са-а-амая длинная!) как на щелчок мыши.

  • Запустите и опробуйте это ...
  • Адресация к элементам дочерних контейнеров

    Форма может иметь много дочерних элементов управления. Если это каркас приложения, то взаимодействовать с элементом можно через его события. Но когда это диалоговое окно со сложными настройками, которое при закрытии должно передавать выбор пользователя со всех своих элементов, то здесь может потребоваться программный опрос всех элементов. Естественно, что вызывающей стороне нужен доступ ко всем элементам дочерней формы.

    Существует много способов для решения такой задачи. Можно, например, объявить в дочернем контейнере именные ссылки на свои элементы как общедоступные поля или свойства и адресоваться через них. Но чаще применяют другой способ, учитывая, что все ссылки на элементы хранятся в коллекции Controls контейнера. Если в дочернем контейнере промаркировать его элементы в свойствах Name (ожидает тип string ) или Tag (принимает любой тип, поскольку объявлено как object ), то найти такой элемент в коллекции по его уникальному признаку не составит труда.

    Коллекция Controls, начиная с версии .NET Framework 2.0, способна вести себя как ассоциативный массив, в котором ключ доступа к элементам представляет собой пару " целое_число-строка ". Строка представляет собой значение, заданное в свойстве Name дочернего элемента. Это означает, что адресоваться к элементу в контейнере можно либо по индексу, либо по имени, которое присвоено свойству Name (при условии, что оно уникально в рамках одной коллекции).

    Свойство Controls является экземпляром класса Control.ControlCollection, который имеет методы Contains() и Find(). Метод Contains() возвращает true, если проверяемый объект является членом коллекции. Метод Find() проверяет свойство Name всех элементов коллекции на соответствие заданному значению и возвращает массив ссылок найденных элементов. Вторым аргументом метода Find() является булев флаг, устанавливающий режим поиска: искать до первого найденного или продолжать поиск до конца.

    Чтобы проиллюстрировать рассмотренные способы адресации к элементам коллекции, создадим проект, в котором для облегчения проектирования пользовательского интерфейса форм воспользуемся средствами оболочки Visual Studio 2005.

    Прежде, чем приступать к использованию визуальных средств IDE, уместно сделать одно важное замечание. Когда программист создает интерфейс пользователя через визуальный конструктор оболочки, то последний генерирует код по своей собственной схеме. И желательно своими действиями по прямому редактированию кода интерфейса в этот порядок не вмешиваться, а все делать через графический редактор и вспомогательные панели среды проектирования. Хотя разбираться в этом вопросе следует (но не обязательно).

    В IDE C++Builder код компоновки интерфейса формы хранится в текстовом файле с расширением frm, а рабочий код размещен в паре h - и cpp -файлов. В Visual Studio 2005 код формы хранится в текстовом файле с расширением Designer.cs, а рабочий код размещен в файле с расширением cs и файле с точкой входа Program.cs.

    Создадим приложение, в котором из главного окна будет вызываться диалоговое окно с элементами управления. После закрытия диалогового окна выполним опрос его элементов и выведем полученную информацию в клиентской области главного окна. Требуемые элементы управления будем переносить на форму проекта из панели инструментов Toolbox оболочки.

  • Выполните команду File/New/Project и создайте проект типа Windows Application с именем FirstWinApp
  • Мастер создания заготовки приложения создаст дерево каталогов проекта со вспомогательными файлами и сгенерирует в них необходимый код

    Сгенерированная мастером форма будет главной формой нашего примера

  • В панели Solution Explorer переименуйте файл Form1.cs в MainForm.cs
  • Обратите внимание, что оболочка переименовала вспомогательные файлы и класс главной формы.

  • Перенесите из панели Toolbox на главную форму и настройте через панель Properties элементы управления в соответствии с приведенной ниже таблицей свойств
  • Таблица свойств главной формы MainForm
    Элемент Свойство Значение Описание
    Form Text Главное окно приложения Текст строки заголовка
    FormBorderStyle Sizable С изменяемым интерактивно размером
    Size 600, 450 Размеры окна в пикселах с соотношением 4/3 (640/480, 800/600, 1024/768)
    Icon Иконка системного меню в заголовке окна (прилагается)
    StartPosition CenterScreen Начальная позиция при запуске
    Button Name btnCreateDialogBox Программное имя кнопки вызова диалогового окна
    Anchor Bottom Сохранять постоянным растояние до нижнего края родителя
    AutoSize True Подстраивать размер кнопки под надпись
    Text Диалог Надпись на кнопке
    TextBox Name txtShowProperties Имя объекта для отображения свойств диалогового окна
    Dock Top Приклеиться к верхнему краю клиентской области родителя по всей ширине
    Multiline True Многострочный техт
    ScrollBars Vertical Отображать вертикальную полосу прокрутки

    На этом проектирование интерфейсной части главного окна приложения завершена. Приступим к проектированию диалогового окна.

  • Выполните команду Window/Close All Documents, чтобы закрыть все окна редактируемых файлов проекта в рабочей области оболочки. При необходимости сохраните изменения
  • Выполните команду Project/Add Windows Form, чтобы создать форму диалогового окна с именем MyModalDialogBox
  • Разместите на форме будущего диалогового окна элементы управления и настройте их в соответствии с таблицей свойств (дочерние элементы внутренних контейнеров в таблице отображены составными именами)
  • Таблица свойств формы диалогового окна MyModalDialogBox
    Элемент Свойство Значение Описание
    Form Text Модальное диалоговое окно Текст строки заголовка
    FormBorderStyle FixedDialog С неизменяемыми размерами и без иконки системного меню в строке заголовка
    Size 600, 450 Размеры окна в пикселах со стандартным соотношением 4/3
    AutoScaleMode Font Для подстройки размеров под величину шрифта
    ControlBox False Без системных кнопок в строке заголовка
    ShowInTaskbar False Не отображать в панели задач рабочего стола Windows
    StartPosition CenterParent Начальная позиция при запуске в центре родителя
    GroupBox Name grBoxRadioButton Контейнер для радиокнопок
    Text Выберите радиокнопку Заголовок контейнера переключателей
    GroupBox.RadioButton Text Радиопереключатель1 Надпись радиокнопки
    Checked True Изначально включена
    GroupBox.RadioButton Text Радиопереключатель2 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель3 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель4 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель5 Надпись радиокнопки
    Label Text Укажите свои ФИО Надпись текстовой метки
    Font.Bold True Полужирный шрифт надписи
    Font.Size 10 Размер шрифта надписи
    ForeColor Red Цвет текста
    TextBox Name Фамилия Идентификатор русский !!!
    Text Иванов
    TextBox Name Имя Идентификатор русский !!!
    Text Петр
    TextBox Name Отчество Идентификатор русский !!!
    Text Сидорович
    Panel Name panelCheckBox
    BackColor White Фон белый
    Panel.CheckBox Text Флажок 1
    Panel.CheckBox Text Флажок 2
    Tag Важный Наша метка
    Panel.CheckBox Text Флажок 3
    Tag Важный Наша метка
    CheckedListBox Name checkedListBox Идентификатор списка флажков
    Items

    Списковый флажок 1

    Списковый флажок 2

    Списковый флажок 3

    Списковый флажок 4

    Списковый флажок 5

    Списковый флажок 6

    Списковый флажок 7

    Элементы списка
    Button Name btnOK
    AutoSize True Подстройка под размер надписи
    DialogResult OK Устанавливает значение свойства DialogResult=OK диалогового окна и закрывает окно
    Text OK Надпись на кнопке
    Button Name btnCancel
    AutoSize True Подстройка под размер надписи
    DialogResult Cancel Устанавливает значение свойства DialogResult=Cancel диалогового окна и закрывает окно
    Text Cancel Надпись на кнопке

    После проведенных настроек форма диалогового окна должна стать такой

    Теперь нужно написать код, который свяжет разработанные формы вместе и позволит опросить состояние всех элементов диалоговой формы после ее использования.

  • Создайте обработчик события Click кнопки в главном диалоговом окне и заполните его так
  • private void btnCreateDialogBox_Click(object sender, EventArgs e)
    {
    txtShowProperties.Text = "";
        
    MyModalDialogBox dlg = new MyModalDialogBox();
    if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
      {
      // Диалоговое окно еще существует, опрашиваем его
      foreach (Control ctrl0 in dlg.Controls)
        {
        ShowProperties("", ctrl0);
        if (ctrl0.Controls.Count > 0)
          foreach (Control ctrl1 in ctrl0.Controls)
            {
            ShowProperties("\t", ctrl1);
            }
        }
      }
    else
      txtShowProperties.Text = "Пользователь установки не менял";
        
    dlg.Dispose(); // Освободили явно ссылку на диалоговое окно
    }
        
    private void ShowProperties(string str, Control ctrl)
    {
    txtShowProperties.Text += str + "Name: " + 
      ctrl.Name + "\r\n";
    if (!(ctrl is Panel)  !(ctrl is CheckedListBox))
      txtShowProperties.Text += str + "Text: " + 
      ctrl.Text + "\r\n";
    if(ctrl is CheckedListBox)
      {
      CheckedListBox check = (CheckedListBox)ctrl;
      for (int i = 0; i < check.Items.Count; i++)
        txtShowProperties.Text += "\t" + 
      check.Items[i].ToString() + "\r\n";
      }
    if ((String)ctrl.Tag == "Важный")
      txtShowProperties.Text += "!!!Помеченный нами в 
      свойстве Tag!!!" + "\r\n";
    }

    Контейнер изображений ImageList

    Многие классы не являются элементами управления как таковыми, а предназначены для хранения множества объектов и управления ими. Такие классы представляют собой контейнеры объектов и являются специализированными массивами.

    ImageList - это набор объектов Image, включающий в себя растровые рисунки и и метафайлы (Иконки) одного размера и глубины цвета. Основным свойством ImageList является объект Images типа ImageList.ImageCollection, который имеет ряд методов, в том числе Add() для добавления рисунков в коллекцию.

    Пусть в текущем с приложением каталоге имеются два растровых изображения MyPicture1.bmp и MyPicture2.bmp, которые нужно упаковать в контейнер. Фрагмент кода программы может выглядеть так

    using System.Windows.Forms;
    ...
    // Создаем пустую коллекцию рисунков
    ImageList imglst = new ImageList();
    // Наполняем коллекцию рисунками
    Bitmap bmp = new Bitmap("MyPicture1.bmp");// Загрузили первый рисунок
    imglst.Images.Add("key1", bmp); // Пометили и отдали в коллекцию
    bmp = new Bitmap("MyPicture2.bmp");// Загрузили второй рисунок
    imglst.Images.Add("key2", bmp); // Пометили и отдали в коллекцию
    ...      
    // Где-то в программе используем рисунки, сохраненные в коллекции
    Bitmap bmp1, bmp2;
    bmp1 = (Bitmap)imglst.Images[0]; // Извлекли по индексу
    bmp2 = (Bitmap)imglst.Images["key2"];// Извлекли по ключу

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        ImageList imageList;
        
        public MyClass()
        {
            this.Icon = new Icon("1.ICO");
            this.Text = "Графическое окно";
        
            imageList = new ImageList();
            imageList.Images.Add("bmp", 
        Image.FromFile("Photo.bmp"));
            imageList.Images.Add("jpg", 
        Image.FromFile("Photo.jpg"));
            imageList.Images.Add("png", 
        Image.FromFile("Photo.png"));
            int width = 250;
            imageList.ImageSize = new Size(width, width / 4 * 3);
        
            this.Click += new EventHandler(MyClass_Click);
        }
        
        void MyClass_Click(object sender, EventArgs e)
        {
            // Получаем указатель на холст формы
            Graphics graphics = Graphics.FromHwnd(this.Handle);
        
            // Цикл для листания рисунков
            for (int count = 0; count < imageList.Images.Count; count++)
            {
                // Рисуем изображение на холсте формы
                imageList.Draw(graphics, new Point(0, 0), count);
        
                // Инициируем перерисовку формы (одно из предложенных)
                Application.DoEvents();
                //this.Invalidate();
        
                // Усыпляем поток на две секунды
                System.Threading.Thread.Sleep(2000);
            }
        }
    }

    Контейнер всплывающих подсказок ToolTip

    Класс ToolTip не является элементом управления, но тесно взаимодействует с ними. Он хранит всплывающие подсказки для элементов управления, которые обычно появляются, когда на элемент управления наводится курсор мыши.

    Сначала в конструкторе формы нужно создать экземпляр класса ToolTip, затем каждому (если нужно) элементу управления привязать всплывающую подсказку с помощью экземплярного метода ToolTip.SetToolTip().

    // Создаем элементы управления
    Button btn1 = new Button();
    Button btn2 = new Button();
    CheckBox check1 = new CheckBox();
    CheckBox check2 = new CheckBox();
    TextBox txt = new TextBox();
        
    // Настраиваем элементы управления
    btn1.Location = new Point(5, 50);
    btn2.Location = new Point(20, 50);
    check1.Text = "Флажок 1";
    //..........................
        
    // Создаем экземпляр контейнера ToolTip
    ToolTip tips = new ToolTip();
    // Привязываем посказки к одному 
    // или нескольким элементам управления
    tips.SetToolTip(btn1, "Это кнопка OK");
    tips.SetToolTip(btn2, "Это кнопка Cancel");
    tips.SetToolTip(check1, "Это флажок 1");
    tips.SetToolTip(check2, "Это флажок 2");
    tips.SetToolTip(txt, "Это текстовое поле");
    //...........................

    В классе ToolTip имеется несколько свойств, настраивающих контейнер всплывающих подсказок. Вот некоторые из них

  • Active - устанавливает или получает булево значение текущей активности подсказки (включает/отключает подсказку)
  • AutomaticDelay - устанавливает или получает время (в миллисекундах), которое подсказка при появлении держится на экране
  • InitialDelay - устанавливает или получает время (в миллисекундах), в течение которого курсор должен находиться над элементом управления, чтобы подсказка появилась
  • AutoPopDelay - если в течение этого времени курсор задержится на элементе управления, то подсказка для этого элемента больше появляться не будет
  • ReshowDelay - устанавливает или получает отрезок времени, который должен пройти, чтобы новая подсказка могла активизироваться при перемещении курсора с одного элемента управления на другой
  • Контейнер радиокнопок GroupBox

    Этот контейнер наследует класс Control и сам является видимым элементом управления (рамка с надписью в свойстве Text ), используемым при формировании пользовательского графического интерфейса. Главное его применение - организовать согласованную работу радиокнопок. Он объединяет их в группы и все переключатели, принадлежащие одной группе, становятся взаимоисключающими. Класс GroupBox не наследует, в отличие от Form, класс ContainerControl, где переход между дочерними элементами выполняется нажатием клавиши Tab. Навигация между радиокнопками внутри GroupBox выполняется клавишами-стрелками.

    Общий контейнер Panel

    Этот элемент управления является (как и Form ) потомком класса ScrollableControl, поэтому имеет полосы прокрутки. Он также имеет визуальное представление и может служить контейнером элементов управления при формировании пользовательского интерфейса.

    Метка Label

    Она отображает нередактируемый текст, заданный в свойстве Text. Хотя метка может выводить многострочный текст, если увеличить ее размер по высоте, но она не выводит полос прокрутки, если текст не умещается в элементе управления.

    Кроме текста метка может отображать объекты типа Image. Один из способов состоит в присвоении свойству Image загруженного файла, например (проект Test )

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        class MyClass : Form
        {
            public MyClass()
            {
                // Создание и настройка элементов управления
                Label lblBmp = new Label();
                lblBmp.Parent = this;
                lblBmp.Dock = DockStyle.Fill;
                lblBmp.Image = Image.FromFile("Photo.bmp");
                lblBmp.Tag = "Рисунок bmp";
        
                Label lblJpg = new Label();
                lblJpg.Parent = this;
                lblJpg.Dock = DockStyle.Fill;
                lblJpg.Image = Image.FromFile("Photo.jpg");
                lblJpg.Tag = "Рисунок jpg";
        
                Label lblPng = new Label();
                lblPng.Parent = this;
                lblPng.Dock = DockStyle.Fill;
                lblPng.Image = Image.FromFile("Photo.png");
                lblPng.Tag = "Рисунок png";
        
                lblBmp.BringToFront();// Меняем Z-последовательность
                this.Text = (string)lblBmp.Tag;// Заголовок окна
        
                // Подписываем на событие Click
                lblBmp.Click += new EventHandler(lbl_Click);
                lblJpg.Click += lbl_Click;
                lblPng.Click += lbl_Click;
            }
        
            void lbl_Click(object sender, EventArgs e)
            {
                Control ctrl = sender as Control;
                if (ctrl == null)
                {
                    MessageBox.Show("Не могу привести типы");
                    return;
                }
        
                this.Controls.Remove(ctrl);
                if (this.Controls.Count > 0)
                {
                    this.Text = (string)this.Controls[0].Tag;
                    this.Controls[0].BringToFront();
                }
                else
                    this.Text = "Меток больше нет";
            }
        }
    }

    Файлы с картинками должны быть помещены в один каталог со сборкой, иначе нужно указать путь у файлу (абсолютный или относительный). Одна из картинок, выводимых этим кодом, будет такой (это, конечно же, опять я - любимый!)

    Кроме того, в метке можно использовать изображения, созданные в качестве ресурсов и прикрепленные к исполняемому файлу программы.

    Метка-ссылка LinkLabel

    Этот элемент обладает свойствами традиционной HTML -гиперссылки Hyperlink. Помимо наследуемых от Control свойств BackColor и ForeColor метка-ссылка имеет характерные для Hyperlink свойства:

  • LinkColor - цвет исходного отображения непосещенной ссылки (по умолчанию синий)
  • DisabledLinkColor - цвет недоступной ссылки (по умолчанию серый)
  • ActiveLinkColor - цвет ссылки при щелчке на ней (по умолчанию красный)
  • VisitedLinkColor - цвет посещенной ссылки (по умолчанию сиреневый)
  • Метка-ссылка определяется свойством Text, но активная ее часть, воспринимаемая как гиперссылка, задается свойством LinkArea. Это свойство представляет собой структуру LinkArea с общедоступными полями Start и Length типа int. Например

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        public MyClass()
        {
            this.Font = new Font("Arial", 14);
            LinkLabel linkLabel = new LinkLabel();
            this.Controls.Add(linkLabel);
            linkLabel.Dock = DockStyle.Fill;
            linkLabel.Text = "Щелкните здесь 
        для появления сообщения";
            linkLabel.LinkArea = new LinkArea(9, 5);
            linkLabel.LinkBehavior = LinkBehavior.SystemDefault;
            linkLabel.LinkClicked += new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
        }
        
        void linkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            MessageBox.Show("Вы щелкнули на метке-ссылке");
        }
    }

    Поведение ссылки задается свойством LinkBehavior, ожидающим одно из значений одноименного перечисления System.Windows.Forms.LinkBehavior. По умолчанию установлено LinkBehavior=SystemDefault, определяющее поведение ссылки как в Microsoft Internet Explorer.

    Одной метке можно назначить несколько ссылок с помощью свойства Links, которое является объектом составного класса LinkLabel.LinkCollection. Щелчок на ссылке LinkLabel инициирует событие LinkClicked, обработчик которого определяется делегатом LinkLabelLinkClickedEventHandler. Объект типа LinkLabelLinkClickedEventArgs, сопровождающий обработчик, является экземпляром составного класса LinkLabel.Link и несет данные активированной ссылки ( URL или имя файла) в свойстве LinkData типа Object.

    Приведем пример, в котором программа Windows Forms отвечает на нажатие ссылки запуском другого процесса, используя метод System.Diagnostics.Process.Start().

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using Return = System.Environment;// Псевдоним класса
        
    class MyClass : Form
    {
        public MyClass()
        {
            this.Font = new Font("Arial", 14);
            this.Width = 2 * this.Height;
            this.Text = "Элемент управления LinkLabel";
        
            LinkLabel linkLabel = new LinkLabel();
            linkLabel.Parent = this;
            linkLabel.Dock = DockStyle.Fill;
            linkLabel.Text = "Из этого приложения 
        Вы можете:" + Return.NewLine
                   + "\n1. Подключиться к сайту корпорации 
        \"Майкрософт\"" + Return.NewLine
        + "2. Запустить графический редактор \"
        Microsoft Paint\"" + Return.NewLine
        + "3. Запустить игру \"
        Косынка\"" + Return.NewLine
        + "4. Запустить текстовый редактор \"
        Блокнот\"" + Return.NewLine;
            string subStr = "Майкрософт";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "http://www.microsoft.com");
            subStr = "Microsoft Paint";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "msPaint");
            subStr = "Косынка";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "Sol");
            subStr = "Блокнот";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "Notepad");
        
            linkLabel.LinkClicked += 
                new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
        }
        
        void linkLabel_LinkClicked(object sender, 
            LinkLabelLinkClickedEventArgs e)
        {
            string strLink = e.Link.LinkData as string;
            if (strLink != null)
                try
                {
                    System.Diagnostics.Process.Start(strLink);
                }
                catch (Exception err)
                {
                    string strError = "Не могу выполнить процесс !!!\n"
                    + "Ошибка: " + err.Message;
                    MessageBox.Show(strError);
                }
        }
    }

    Результат выполнения примера будет таким

    Щелчки на ссылках запускают соответствующие программы. Для подстраховки мы отлавливаем возможные исключения Windows.

    Графическое окно PictureBox

    Этот элемент служит для отображения растров или метафайлов. Изображение задается свойством Image. Свойство SizeMode задает режим отображения с помощью перечисления System.Windows.Forms.PictureBoxSizeMode, которое имеет следующие значения

  • AutoSize - графическое окно подстраивается под размер изображения
  • CenterImage - изображение размещается по центру графического окна
  • Normal - изображение размещается в левом верхнем углу графического окна
  • StretchImage - изображение масштабируется под размер графического окна
  • Zoom - изображение масштабируется внутри графического окна без искажений
  • Вот пример, для выполнения которого нужно разместить рисунки (прилагаются в каталоге Pictures ) в одной папке с исполнимым файлом.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        PictureBox pictureBox;
        string[] photo = {
                          "Photo.bmp",
                          "Photo.jpg",
                          "Photo.png"
                         };
        
        public MyClass()
        {
            // Загрузили иконку
            this.Icon = new Icon("1.ICO");
            // Текст заголовка окна
            this.Text = "Испытание элемента PictureBox";
            // Подписались на события формы
            this.Click += FormClick;
            this.Paint += BeginPaint;
        
            pictureBox = new PictureBox();// Создали элемент
            pictureBox.Parent = this;// Назначили родителя
            // Распахнули на всю форму
            pictureBox.Dock = DockStyle.Fill;
            // Масштабировать без искажений
            pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
            // Чтобы не заслонял текстовое сообщение на форме
            pictureBox.Visible = false;
            // Подписываем на щелчки элемента тот же обработчик
            pictureBox.Click += FormClick;
        }
        
        void BeginPaint(object sender, PaintEventArgs e)
        {
            // Создаем и настраиваем точку привязки текстового блока
            StringFormat strfrm = new StringFormat();
            strfrm.Alignment = StringAlignment.Center;// Центр по горизонтали
            strfrm.LineAlignment = StringAlignment.Center;// Центр по вертикали
        
            // Создаем временный объект для доступа к контексту устройства
            Graphics gr = Graphics.FromHwnd(this.Handle);
            gr.DrawString("Щелкайте на форме",  // Текст сообщения
                new Font("Arial", 14, FontStyle.Bold), // Шрифт сообщения
                new SolidBrush(Color.Red),  // Цвет шрифта
                this.ClientSize.Width / 2,   // Центр клиентской области по горизонтали
                this.ClientSize.Height / 2,  // Центр клиентской области по вертикали
                strfrm); // Передаем точку привязки текстового блока  
            gr.Dispose();// Освободили контекст устройства
        }
        
        int count = 0;// Счетчик
        bool clickFlag = false; // Устанавливается при первом щелчке
        void FormClick(object sender, EventArgs e)
        {
            if (!clickFlag)
            {
                pictureBox.Visible = true;
                this.Paint -= BeginPaint;// Разрегистрируем обработчик
                clickFlag = true;
            }
        
            //pictureBox.Image = Image.FromFile(photo[count++]);
            pictureBox.Load(photo[count++]);
            count = count < photo.Length ? count : 0;// Однострочное условие
        }
    }

    Метод Load() принимает URL или имя файла. Метод LoadAsync() выполняет загрузку в фоновом режиме, создавая дополнительный поток, и информирует о завершении событием LoadCompleted, например,

    pictureBox.LoadAsync(@"http://localhost/print.jpg");

    Если изображение загружается долго, то можно задать дежурную картинку в свойстве InitialImage, а также картинку в свойстве ErrorImage, выводимую в случае ошибки загрузки. Событие LoadProgressChanged в своем аргументе типа ProgressChangedEventArgs передает информацию о загрузке изображения в процентах. Эту информацию можно использовать в обработчике для отображения с помощью индикатора выполнения ProgressBar, например,

    void pictureBox_LoadProgressChanged(object sender,    
      ProgressChangedEventArgs e)
    {    progressBar.Value = e.ProgressPercentage;}

    Индикатор хода процесса ProgressBar

    Основные свойства ProgressBar - это целочисленные Minimum, Maximum, Value. Индикатор процесса напрямую связан со свойством Value, значение которого можно увеличивать несколькими способами:

  • Напрямую , например, Value++
  • Методом PerformStep(), увеличивающим при каждом вызове на шаг, заданный в свойстве Step
  • Методом Increment(int value), увеличивающим на величину целочисленного аргумента
  • Приведем пример, в котором движком процесса является таймер. Код является несколько избыточным, чтобы продемонстрировать побольше из бесконечного числа особенностей объектно-ориентированного программирования.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        class MyClass : Form
        {
            ProgressBar progressBar = new ProgressBar();
            Timer timer;
            const int INTERVAL_TIME = 1000; // Миллисекунды
        
            public MyClass()
            {
                // Настройка формы
                this.Icon = new Icon("1.ICO");
                this.Text = "Испытание элемента ProgressBar ";
        
                // Настройка индикатора
                progressBar.Parent = this;
                progressBar.Width = this.ClientSize.Width;
                int y = (this.ClientSize.Height - progressBar.Height) / 2;
                progressBar.Location = new Point(0, y);
                progressBar.Minimum = 0;
                progressBar.Maximum = 20;
                progressBar.Step = 1;
                progressBar.Style = ProgressBarStyle.Continuous;
        
                // Настройка кнопки
                Button btn = new Button();
                btn.Parent = this;
                btn.Text = "Start/Stop";
                btn.AutoSize = true;
                btn.Font = new Font("Arial", 12, FontStyle.Italic);
                int x = (this.ClientSize.Width - btn.Width) / 2;
                y = this.ClientSize.Height - btn.Height - 20;
                btn.Location = new Point(x, y);
                btn.Click += new EventHandler(btn_Click);
        
                // Настройка таймера
                timer = new Timer();
                timer.Interval = INTERVAL_TIME;
                timer.Tick += new EventHandler(timer_Tick);
            }
        
            // Обработчик кнопки управления таймером
            bool startTimerFlag = false;
            void btn_Click(object sender, EventArgs e)
            {
                if (!startTimerFlag)
                    timer.Start();
                else
                    timer.Stop();
        
                startTimerFlag = !startTimerFlag;
            }
        
            // Обработчик тиков таймера 
            void timer_Tick(object sender, EventArgs e)
            {
                if (progressBar.Value < progressBar.Maximum)
                {
                    progressBar.PerformStep();
                }
                else
                {
                    timer.Stop();
                }
            }
        
            // Деструктор, доступ не управляется
            ~MyClass()
            {
                if (startTimerFlag)
                    timer.Stop();
            }
        }
    }
    Страницы:

    Объектно - ориентированное программирование различает два способа агрегирования (укрупнения) кода: наследование (сцепление) и композиция (включение). В первом случае создается новый класс, в который передаются все данные и методы базового класса так, как будто-бы они были созданы в производном классе. Производный класс считается расширением базового класса. Во втором случае в новом классе создаются объекты других классов как члены-данные этого класса. Класс, который разрабатывается на основе включения других объектов, называется контейнером (родителем), а включенные в него объекты являются дочерними. Чаще всего при разработке новых класов применяют сочетание этих двух способов.

    Объект Form по умолчанию содержит строку заголовка с системным меню слева и системными кнопками справа. По периметру окна проходит рамка определения размеров окна. Внутри окна расположена клиентская область - в ней отображается информация и содержатся инструменты для пользователя. Окна могут отображать текст и графику, получать команды с клавиатуры и мыши. Но в большинстве современных окон клиентская область размещает дочерние элементы управления, с помощью которых пользователь управляет программой.

    Элементы управления могут быть стандартными (библиотечными, базовыми) или разработанными самим программистом (пользовательскими). В этой части мы изучим стандартные элементы управления, обеспечивающие требуемый графический интерфейс пользователя ( GUI ).

    Все элементы, которыми визуальная среда проектирования позволяет манипулировать, размещая их на форме, называются компонентами. Те компоненты, которые остаются видимыми и во время выполнения программы, называются визуальными, и это, как правило, элементы управления пользовательского интерфейса. Те компоненты, которые не видимы во время выполнения - называются невизуальными, или просто - компонентами.

    Обычно элементы управления занимают прямоугольную область, хотя могут быть и непрямоугольными и даже скрытыми, тем не менее исправно выполнять свои обязанности. Все элементы управления чувствительны к событиям мыши и клавиатуры и сами способны генерировать ряд событий. Превращая информацию о действиях пользователя в простые события, элементы управления позволяют программисту контролировать эти действия и реагировать на них соответствующим образом.

    Дочерние элементы и родительские контейнеры (дочки - матери: семейные разборки)

    Все элементы управления должны иметь своего родителя, определяемого свойством Parent. Только элементы управления, имеющие родителя, будут отображены на экране. Родителем может быть не только форма, но и другой элемент управления (это случается сравнительно редко). Положения элемента управления всегда задается относительно родителя и отображается на его поверхности. Часть элемента управления, выходящая за края родителя, не видна.

    Класс Form, как наследник класса Control, тоже имеет свойство Parent, но оно обычно имеет значение null. Считается, что родителем формы по умолчанию является рабочий стол, и в этом случае свойство Form.TopLevel (верхний уровень) имеет значение true. Но форма может выступать потомком любого другого элемента управления при значении TopLevel = false, в том числе каркаса MDI - приложения.

    Если в свойстве Form.Owner определена ссылка на другую форму, то форма имеет владельца, располагается всегда поверх него и сворачивается или закрывается вместе с ним. Такое поведение полезно при создании немодальных диалоговых окон.

    Для того, чтобы элемент управления размещался на форме, сначала нужно создать саму форму как объект, затем создать как объект элемент управления, затем присвоить его свойству Parent ссылку на объект формы (назначить родителя) или выполнить метод свойства Controls.Add() объекта формы. Эти действия по регистрации элемента управления в форме являются равнозначными.

    Например, при создании кнопки на форме в конструкторе формы нужно прописать такой код

    Button dtn = new Button();
    btn.Parent = this;
    или
    this.Controls.Add(btn);

    Свойство Controls наследуется формой от класса Control и является объектом класса ControlCollection. Оно содержит в себе все дочерние элементы формы. Методы Controls.Add() и Controls.Remove() управляют составом этого набора. Коды this.Controls.Remove(btn) и btn.Parent = null эквивалентны. Свойство Controls допускает применение синтаксиса массива.

    Приведем пример.

  • Командой File/New/Project создайте пустой проект с именем MyProject
  • Командой Project/Add Reference добавьте к проекту ссылки на базовые сборки System.dll и System.Windows.Forms.dll
  • Командой Project/Add New Item добавьте два пустых файла MainForm.cs и Program.cs
  • Заполните файлы следующим кодом
  • using System;
    using System.Windows.Forms;
        
    namespace MyProject
    {
        class MainForm : Form
        {
            const int MAX_BUTTON = 5;   // Число кнопок
        
            public MainForm()
            {
                // Настраиваем консольное окно
                Console.ForegroundColor = ConsoleColor.White;
        
                // Регистрируем событие загрузки формы
                this.Load += new EventHandler(MainForm_Load);
            }
        
            // Создаем элементы управления в обработчике события Load формы
            void MainForm_Load(object sender, EventArgs e)
            {
                // Создаем кнопки
                for (int i = 0; i < MAX_BUTTON; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    this.Controls.Add(btn);// Отдали кнопку форме
                }
                Console.WriteLine("Создано кнопок: 
        {0}", this.Controls.Count);
        
                // Уничтожаем кнопки
                int count = 0; // Локальная переменная
                Control[] control = new Control[this.Controls.Count];
                // Проводим предварительное обследование коллекции в foreach
                foreach (Control ctrl in this.Controls)
                {
                    if (ctrl is Button)
                    {
                        control[count] = ctrl;// Сохраняем ссылку в массиве
                        count++;
                    }
                }
                // Уничтожаем кнопки по найденым ссылкам
                for (int i = 0; i < count; i++)
                    this.Controls.Remove(control[i]);
                Console.WriteLine("Удалено кнопок: {0}", count);
        
                // Еще раз создаем кнопки
                for (int i = 0; i < MAX_BUTTON + 1; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Parent = this;  // Отдали форме другим способом
                }
                Console.WriteLine("\nСоздано кнопок: 
        {0}", this.Controls.Count);
        
                // Уничтожаем кнопки
                count = 0;  // Сбросили счетчик
                control = new Control[this.Controls.Count];// Переназначили массив
                // Проводим предварительное обследование коллекции в for
                for (int i = 0; i < this.Controls.Count; i++)
                {
                    Control ctrl = this.Controls[i];
                    if (ctrl is Button)
                    {
                        control[count] = ctrl;
                        count++;
                    }
                }
                // Уничтожаем кнопки по найденым ссылкам
                for (int i = 0; i < count; i++)
                    control[i].Parent = null;// Уничтожаем другим способом
                Console.WriteLine("Удалено кнопок: {0}", count);
        
                for (int i = 0; i < MAX_BUTTON + 2; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Click += btn_Click;// Зарегистрировали общий обработчик
                    btn.Parent = this;  // Отдали форме
                }
                Console.WriteLine("\nСоздано кнопок: 
        {0}", this.Controls.Count);
            }
        
            // Уничтожение кнопок интерактивно
            int count = 0; // Поле
            void btn_Click(object sender, EventArgs e)
            {
                this.Controls.Remove((Control)sender);
                Console.WriteLine("{0}) Удалили кнопку 
        щелчком на ней", ++count);
            }
        }
    }

    using System;
    using System.Windows.Forms;
        
    namespace MyProject
    {
        class EntryPoint
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MainForm());
            }
        }
    }

    Результат работы приведенного кода будет таким

    Поскольку все элементы управления произошли от класса Control, а он определяет свойство Controls, то любой элемент управления имеет это родительское свойство и может быть родителем. Но не многие наследники Control используются программистами в качестве родителей. Обычно элементы управления используются в качестве дочерних элементов формы.

    Class Control имеет булево свойство TabStop, которое позволяет контейнерным элементам управлять фокусом ввода своих дочерних элементов с помощью клавиши Tab. Фокус ввода способны получать только те дочерние объекты, у которых свойство TabStop установлено в true. Порядок получения фокуса определяется свойством Control.TabIndex дочерних элементов контейнера или выполняется в порядке их расположения в коллекции (в порядке их добавления в набор, Z -порядок: последний добавленный видится верхним). Элемент управления сдвигается в Z -последовательности на первое или последнее место своими методами BringToFront() или SendToBack() соответственно.

    Дочерние элементы управления одного контейнера (родителя) называются родственными.

    По умолчанию все элементы управления рождаются видимыми и доступными пользователю. Видимость элемента управления определяется свойством Visible, а доступность - Enabled. Те же свойства родителя (контейнера) влияют сразу на все дочерние элементы. Невидимым или недоступным элемент становится только для пользователя, но в составе программы остается полноценным элементом и продолжает работать под ее контролем.

    Привязка и размер дочерних элементов

    Каждому элементу управления может задаваться положение его левого верхнего угла относительно левого верхнего угла клиентской области родителя. Оно определяется свойством Location элемента управления, которое ожидает ссылку на экземпляр класса Point(x, y), где x и y означают количество пикселов смещения. Размер элемента задается через свойство Size, которое ожидает ссылку на объект класса Size(width, height). Ширина и высота также означают количество пикселов относительно точки привязки элемента управления.

    Если в элементе управления (например, кнопке) установить булево свойство AutoSize = true, то установленный размер Size игнорируется и элемент управления подгоняет свои размеры под размер надписи, определяемой свойством Text.

    Свойство Dock (причал) элемента управления используется для растягивания и выравнивания по внутренней стороне родителя. Его значение определяется перечислением DockStyle: None, Left, Right, Top, Bottom, Fill. Свойство Anchor (якорь) заставляет элемент управления замораживать привязку к сторонам родителя (при изменении размеров последнего) на том расстоянии, которое он занимал до изменения размеров родителя. Свойство Anchor принимает значения перечисления AnchorStyles: : None, Left, Right, Top, Bottom и допускает их побитовое сложение ( по умолчанию Anchor = Left | Top).

    Шрифт и цвет дочерних элементов

    Порядок создания объектов в программе следующий: вначале создается главный контейнер, затем создаются все дочерние элементы и присоединяются к контейнеру через ссылки. Если дочерний элемент, в свою очередь, является контейнером, то он создается вперед своих дочерних элементов и ждет, когда к нему будут присоединены ссылки на них. При создании дочерних элементов им неявно передаются значения одноименных свойств контейнера. Главный контейнер приложения получает значения этих свойств от операционной системы и передает их вниз по иерархической цепочке. Если в любом контейнере этой цепочки свойствам назначить другие значения, то новые значения одноименных свойств получат все дочерние элементы.

    Класс Color передает элементам управления свойство Text, которые используют его соответствующим их специализации способом. Например, в форме это заголовок окна, в кнопке это надпись, а полоса прокрутки вообще не использует это свойство. Наследники класса Control получают от него, также, свойства Font, ForeColor и BackColor, которые задают шрифт, его размер, стиль и цвет, а также цвет клиентской области. Если дочерний элемент явно не задает значение этого свойства, то оно перенимает значение контейнера.

    Для иллюстрации сказанного переделаем предыдущий пример.

  • Замените в последнем проекте MyProject код файла MainForm.cs на следующий
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    namespace MyProject
    {
        class MainForm : Form
        {
            const int MAX_BUTTON = 5;   // Число кнопок
        
            public MainForm()
            {
                // Настраиваем консольное окно
                Console.ForegroundColor = ConsoleColor.White;
        
                // Регистрируем событие загрузки формы
                this.Load += new EventHandler(MainForm_Load);
            }
        
            // Создаем элементы управления в обработчике события Load формы
            void MainForm_Load(object sender, EventArgs e)
            {
                this.Text = "Щелкайте на кнопках!";
                // Автоматически показывать полосу прокрутки,
                // когда не помещаются дочерние элементы
                this.AutoScroll = true;
                // При запуске с консольным окном сделать верхней
                this.Activate();
        
                // Переопределяем свойство контейнера,
                // которое неявно передастся дочерним элементам
                this.ForeColor = Color.Red;
        
                // Создаем и настраиваем дочерние элементы
                int x = 0, y = 0; // Размещение кнопок
                for (int i = 0; i < MAX_BUTTON; i++)
                {
                    Button btn = new Button();  // Создали кнопку
                    btn.Click += btn_Click;// Зарегистрировали общий обработчик
                    btn.Parent = this;  // Отдали форме
                    // Продолжаем настройку, пока не потеряли адрес
                    btn.Text = "Кнопка " + (i + 1).ToString();
                    btn.AutoSize = true;
                    // Настраиваем плоский стиль кнопки (для Flat)
                    btn.FlatStyle = FlatStyle.Flat;
                    btn.FlatAppearance.BorderColor = Color.Blue;
                    btn.FlatAppearance.BorderSize = 5; // В пикселах
                    // Вычисляем точку размещения
                    if ((i + 3) % 2 == 1) // Первая колонка нечетные
                    {
                        x = 0;
                        if (i != 0)// Начальные не смещать
                            y += btn.Size.Height * 2;
                    }
                    else // Вторая колонка четные
                    {
                        x = btn.Size.Width * 2;
                        btn.TabStop = false; // Четные лишаем фокуса ввода 
                    }
                    // Позиционируем дочерний элемент относительно родителя
                    btn.Location = new Point(x, y);
                    // Переопределяем свойство дочернего элемента.
                    // Стиль шрифта побитово суммируем
                    btn.Font = new System.Drawing.Font(
                        "Times New Roman", 14,
                        FontStyle.Italic | FontStyle.Bold);
                    btn.BackColor = Color.Yellow;
                }
        
                // Назначение фокуса ввода
                int indexFocus, count = this.Controls.Count;
                indexFocus = (count + 2) % 2 == 1 ? count - 1 : count;
                if (indexFocus >= 0) // Проверка на случай пустого контейнера
                    this.Controls[indexFocus].Focus(); 
        // Активируем последний в первой колонке
        
                Console.WriteLine("Создано кнопок: 
        {0}", this.Controls.Count);
            }
        
            // Уничтожение пользователем кнопок интерактивно
            void btn_Click(object sender, EventArgs e)
            {
                Control ctrl = sender as Control;
                this.Controls.Remove(ctrl);
                Console.WriteLine("\"" + 
        ctrl.Text + "\" удалена!");
                // Меняем заголовок окна, когда кончились кнопки
                if (this.Controls.Count == 0)
                {
                    this.Text = "Все кнопки удалены !!!!!!";
                }
            }
        }
    }

    Графическое окно, созданное классом-контейнером MainForm, будет выглядеть так

    Цвет надписи кнопка получила от родителя, а шрифт и цвет фона были для нее установлены явно. Обратите внимание на то, как мы программно установили для коллекции дочерних элементов фокус ввода и порядок управления фокусом ввода по клавише Tab. Кнопка, имеющая фокус ввода, реагирует на нажатие клавиш Enter или Space ( пробела - такая са-а-амая длинная!) как на щелчок мыши.

  • Запустите и опробуйте это ...
  • Адресация к элементам дочерних контейнеров

    Форма может иметь много дочерних элементов управления. Если это каркас приложения, то взаимодействовать с элементом можно через его события. Но когда это диалоговое окно со сложными настройками, которое при закрытии должно передавать выбор пользователя со всех своих элементов, то здесь может потребоваться программный опрос всех элементов. Естественно, что вызывающей стороне нужен доступ ко всем элементам дочерней формы.

    Существует много способов для решения такой задачи. Можно, например, объявить в дочернем контейнере именные ссылки на свои элементы как общедоступные поля или свойства и адресоваться через них. Но чаще применяют другой способ, учитывая, что все ссылки на элементы хранятся в коллекции Controls контейнера. Если в дочернем контейнере промаркировать его элементы в свойствах Name (ожидает тип string ) или Tag (принимает любой тип, поскольку объявлено как object ), то найти такой элемент в коллекции по его уникальному признаку не составит труда.

    Коллекция Controls, начиная с версии .NET Framework 2.0, способна вести себя как ассоциативный массив, в котором ключ доступа к элементам представляет собой пару " целое_число-строка ". Строка представляет собой значение, заданное в свойстве Name дочернего элемента. Это означает, что адресоваться к элементу в контейнере можно либо по индексу, либо по имени, которое присвоено свойству Name (при условии, что оно уникально в рамках одной коллекции).

    Свойство Controls является экземпляром класса Control.ControlCollection, который имеет методы Contains() и Find(). Метод Contains() возвращает true, если проверяемый объект является членом коллекции. Метод Find() проверяет свойство Name всех элементов коллекции на соответствие заданному значению и возвращает массив ссылок найденных элементов. Вторым аргументом метода Find() является булев флаг, устанавливающий режим поиска: искать до первого найденного или продолжать поиск до конца.

    Чтобы проиллюстрировать рассмотренные способы адресации к элементам коллекции, создадим проект, в котором для облегчения проектирования пользовательского интерфейса форм воспользуемся средствами оболочки Visual Studio 2005.

    Прежде, чем приступать к использованию визуальных средств IDE, уместно сделать одно важное замечание. Когда программист создает интерфейс пользователя через визуальный конструктор оболочки, то последний генерирует код по своей собственной схеме. И желательно своими действиями по прямому редактированию кода интерфейса в этот порядок не вмешиваться, а все делать через графический редактор и вспомогательные панели среды проектирования. Хотя разбираться в этом вопросе следует (но не обязательно).

    В IDE C++Builder код компоновки интерфейса формы хранится в текстовом файле с расширением frm, а рабочий код размещен в паре h - и cpp -файлов. В Visual Studio 2005 код формы хранится в текстовом файле с расширением Designer.cs, а рабочий код размещен в файле с расширением cs и файле с точкой входа Program.cs.

    Создадим приложение, в котором из главного окна будет вызываться диалоговое окно с элементами управления. После закрытия диалогового окна выполним опрос его элементов и выведем полученную информацию в клиентской области главного окна. Требуемые элементы управления будем переносить на форму проекта из панели инструментов Toolbox оболочки.

  • Выполните команду File/New/Project и создайте проект типа Windows Application с именем FirstWinApp
  • Мастер создания заготовки приложения создаст дерево каталогов проекта со вспомогательными файлами и сгенерирует в них необходимый код

    Сгенерированная мастером форма будет главной формой нашего примера

  • В панели Solution Explorer переименуйте файл Form1.cs в MainForm.cs
  • Обратите внимание, что оболочка переименовала вспомогательные файлы и класс главной формы.

  • Перенесите из панели Toolbox на главную форму и настройте через панель Properties элементы управления в соответствии с приведенной ниже таблицей свойств
  • Таблица свойств главной формы MainForm
    Элемент Свойство Значение Описание
    Form Text Главное окно приложения Текст строки заголовка
    FormBorderStyle Sizable С изменяемым интерактивно размером
    Size 600, 450 Размеры окна в пикселах с соотношением 4/3 (640/480, 800/600, 1024/768)
    Icon Иконка системного меню в заголовке окна (прилагается)
    StartPosition CenterScreen Начальная позиция при запуске
    Button Name btnCreateDialogBox Программное имя кнопки вызова диалогового окна
    Anchor Bottom Сохранять постоянным растояние до нижнего края родителя
    AutoSize True Подстраивать размер кнопки под надпись
    Text Диалог Надпись на кнопке
    TextBox Name txtShowProperties Имя объекта для отображения свойств диалогового окна
    Dock Top Приклеиться к верхнему краю клиентской области родителя по всей ширине
    Multiline True Многострочный техт
    ScrollBars Vertical Отображать вертикальную полосу прокрутки

    На этом проектирование интерфейсной части главного окна приложения завершена. Приступим к проектированию диалогового окна.

  • Выполните команду Window/Close All Documents, чтобы закрыть все окна редактируемых файлов проекта в рабочей области оболочки. При необходимости сохраните изменения
  • Выполните команду Project/Add Windows Form, чтобы создать форму диалогового окна с именем MyModalDialogBox
  • Разместите на форме будущего диалогового окна элементы управления и настройте их в соответствии с таблицей свойств (дочерние элементы внутренних контейнеров в таблице отображены составными именами)
  • Таблица свойств формы диалогового окна MyModalDialogBox
    Элемент Свойство Значение Описание
    Form Text Модальное диалоговое окно Текст строки заголовка
    FormBorderStyle FixedDialog С неизменяемыми размерами и без иконки системного меню в строке заголовка
    Size 600, 450 Размеры окна в пикселах со стандартным соотношением 4/3
    AutoScaleMode Font Для подстройки размеров под величину шрифта
    ControlBox False Без системных кнопок в строке заголовка
    ShowInTaskbar False Не отображать в панели задач рабочего стола Windows
    StartPosition CenterParent Начальная позиция при запуске в центре родителя
    GroupBox Name grBoxRadioButton Контейнер для радиокнопок
    Text Выберите радиокнопку Заголовок контейнера переключателей
    GroupBox.RadioButton Text Радиопереключатель1 Надпись радиокнопки
    Checked True Изначально включена
    GroupBox.RadioButton Text Радиопереключатель2 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель3 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель4 Надпись радиокнопки
    GroupBox.RadioButton Text Радиопереключатель5 Надпись радиокнопки
    Label Text Укажите свои ФИО Надпись текстовой метки
    Font.Bold True Полужирный шрифт надписи
    Font.Size 10 Размер шрифта надписи
    ForeColor Red Цвет текста
    TextBox Name Фамилия Идентификатор русский !!!
    Text Иванов
    TextBox Name Имя Идентификатор русский !!!
    Text Петр
    TextBox Name Отчество Идентификатор русский !!!
    Text Сидорович
    Panel Name panelCheckBox
    BackColor White Фон белый
    Panel.CheckBox Text Флажок 1
    Panel.CheckBox Text Флажок 2
    Tag Важный Наша метка
    Panel.CheckBox Text Флажок 3
    Tag Важный Наша метка
    CheckedListBox Name checkedListBox Идентификатор списка флажков
    Items

    Списковый флажок 1

    Списковый флажок 2

    Списковый флажок 3

    Списковый флажок 4

    Списковый флажок 5

    Списковый флажок 6

    Списковый флажок 7

    Элементы списка
    Button Name btnOK
    AutoSize True Подстройка под размер надписи
    DialogResult OK Устанавливает значение свойства DialogResult=OK диалогового окна и закрывает окно
    Text OK Надпись на кнопке
    Button Name btnCancel
    AutoSize True Подстройка под размер надписи
    DialogResult Cancel Устанавливает значение свойства DialogResult=Cancel диалогового окна и закрывает окно
    Text Cancel Надпись на кнопке

    После проведенных настроек форма диалогового окна должна стать такой

    Теперь нужно написать код, который свяжет разработанные формы вместе и позволит опросить состояние всех элементов диалоговой формы после ее использования.

  • Создайте обработчик события Click кнопки в главном диалоговом окне и заполните его так
  • private void btnCreateDialogBox_Click(object sender, EventArgs e)
    {
    txtShowProperties.Text = "";
        
    MyModalDialogBox dlg = new MyModalDialogBox();
    if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
      {
      // Диалоговое окно еще существует, опрашиваем его
      foreach (Control ctrl0 in dlg.Controls)
        {
        ShowProperties("", ctrl0);
        if (ctrl0.Controls.Count > 0)
          foreach (Control ctrl1 in ctrl0.Controls)
            {
            ShowProperties("\t", ctrl1);
            }
        }
      }
    else
      txtShowProperties.Text = "Пользователь установки не менял";
        
    dlg.Dispose(); // Освободили явно ссылку на диалоговое окно
    }
        
    private void ShowProperties(string str, Control ctrl)
    {
    txtShowProperties.Text += str + "Name: " + 
      ctrl.Name + "\r\n";
    if (!(ctrl is Panel)  !(ctrl is CheckedListBox))
      txtShowProperties.Text += str + "Text: " + 
      ctrl.Text + "\r\n";
    if(ctrl is CheckedListBox)
      {
      CheckedListBox check = (CheckedListBox)ctrl;
      for (int i = 0; i < check.Items.Count; i++)
        txtShowProperties.Text += "\t" + 
      check.Items[i].ToString() + "\r\n";
      }
    if ((String)ctrl.Tag == "Важный")
      txtShowProperties.Text += "!!!Помеченный нами в 
      свойстве Tag!!!" + "\r\n";
    }

    Контейнер изображений ImageList

    Многие классы не являются элементами управления как таковыми, а предназначены для хранения множества объектов и управления ими. Такие классы представляют собой контейнеры объектов и являются специализированными массивами.

    ImageList - это набор объектов Image, включающий в себя растровые рисунки и и метафайлы (Иконки) одного размера и глубины цвета. Основным свойством ImageList является объект Images типа ImageList.ImageCollection, который имеет ряд методов, в том числе Add() для добавления рисунков в коллекцию.

    Пусть в текущем с приложением каталоге имеются два растровых изображения MyPicture1.bmp и MyPicture2.bmp, которые нужно упаковать в контейнер. Фрагмент кода программы может выглядеть так

    using System.Windows.Forms;
    ...
    // Создаем пустую коллекцию рисунков
    ImageList imglst = new ImageList();
    // Наполняем коллекцию рисунками
    Bitmap bmp = new Bitmap("MyPicture1.bmp");// Загрузили первый рисунок
    imglst.Images.Add("key1", bmp); // Пометили и отдали в коллекцию
    bmp = new Bitmap("MyPicture2.bmp");// Загрузили второй рисунок
    imglst.Images.Add("key2", bmp); // Пометили и отдали в коллекцию
    ...      
    // Где-то в программе используем рисунки, сохраненные в коллекции
    Bitmap bmp1, bmp2;
    bmp1 = (Bitmap)imglst.Images[0]; // Извлекли по индексу
    bmp2 = (Bitmap)imglst.Images["key2"];// Извлекли по ключу

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        ImageList imageList;
        
        public MyClass()
        {
            this.Icon = new Icon("1.ICO");
            this.Text = "Графическое окно";
        
            imageList = new ImageList();
            imageList.Images.Add("bmp", 
        Image.FromFile("Photo.bmp"));
            imageList.Images.Add("jpg", 
        Image.FromFile("Photo.jpg"));
            imageList.Images.Add("png", 
        Image.FromFile("Photo.png"));
            int width = 250;
            imageList.ImageSize = new Size(width, width / 4 * 3);
        
            this.Click += new EventHandler(MyClass_Click);
        }
        
        void MyClass_Click(object sender, EventArgs e)
        {
            // Получаем указатель на холст формы
            Graphics graphics = Graphics.FromHwnd(this.Handle);
        
            // Цикл для листания рисунков
            for (int count = 0; count < imageList.Images.Count; count++)
            {
                // Рисуем изображение на холсте формы
                imageList.Draw(graphics, new Point(0, 0), count);
        
                // Инициируем перерисовку формы (одно из предложенных)
                Application.DoEvents();
                //this.Invalidate();
        
                // Усыпляем поток на две секунды
                System.Threading.Thread.Sleep(2000);
            }
        }
    }

    Контейнер всплывающих подсказок ToolTip

    Класс ToolTip не является элементом управления, но тесно взаимодействует с ними. Он хранит всплывающие подсказки для элементов управления, которые обычно появляются, когда на элемент управления наводится курсор мыши.

    Сначала в конструкторе формы нужно создать экземпляр класса ToolTip, затем каждому (если нужно) элементу управления привязать всплывающую подсказку с помощью экземплярного метода ToolTip.SetToolTip().

    // Создаем элементы управления
    Button btn1 = new Button();
    Button btn2 = new Button();
    CheckBox check1 = new CheckBox();
    CheckBox check2 = new CheckBox();
    TextBox txt = new TextBox();
        
    // Настраиваем элементы управления
    btn1.Location = new Point(5, 50);
    btn2.Location = new Point(20, 50);
    check1.Text = "Флажок 1";
    //..........................
        
    // Создаем экземпляр контейнера ToolTip
    ToolTip tips = new ToolTip();
    // Привязываем посказки к одному 
    // или нескольким элементам управления
    tips.SetToolTip(btn1, "Это кнопка OK");
    tips.SetToolTip(btn2, "Это кнопка Cancel");
    tips.SetToolTip(check1, "Это флажок 1");
    tips.SetToolTip(check2, "Это флажок 2");
    tips.SetToolTip(txt, "Это текстовое поле");
    //...........................

    В классе ToolTip имеется несколько свойств, настраивающих контейнер всплывающих подсказок. Вот некоторые из них

  • Active - устанавливает или получает булево значение текущей активности подсказки (включает/отключает подсказку)
  • AutomaticDelay - устанавливает или получает время (в миллисекундах), которое подсказка при появлении держится на экране
  • InitialDelay - устанавливает или получает время (в миллисекундах), в течение которого курсор должен находиться над элементом управления, чтобы подсказка появилась
  • AutoPopDelay - если в течение этого времени курсор задержится на элементе управления, то подсказка для этого элемента больше появляться не будет
  • ReshowDelay - устанавливает или получает отрезок времени, который должен пройти, чтобы новая подсказка могла активизироваться при перемещении курсора с одного элемента управления на другой
  • Контейнер радиокнопок GroupBox

    Этот контейнер наследует класс Control и сам является видимым элементом управления (рамка с надписью в свойстве Text ), используемым при формировании пользовательского графического интерфейса. Главное его применение - организовать согласованную работу радиокнопок. Он объединяет их в группы и все переключатели, принадлежащие одной группе, становятся взаимоисключающими. Класс GroupBox не наследует, в отличие от Form, класс ContainerControl, где переход между дочерними элементами выполняется нажатием клавиши Tab. Навигация между радиокнопками внутри GroupBox выполняется клавишами-стрелками.

    Общий контейнер Panel

    Этот элемент управления является (как и Form ) потомком класса ScrollableControl, поэтому имеет полосы прокрутки. Он также имеет визуальное представление и может служить контейнером элементов управления при формировании пользовательского интерфейса.

    Метка Label

    Она отображает нередактируемый текст, заданный в свойстве Text. Хотя метка может выводить многострочный текст, если увеличить ее размер по высоте, но она не выводит полос прокрутки, если текст не умещается в элементе управления.

    Кроме текста метка может отображать объекты типа Image. Один из способов состоит в присвоении свойству Image загруженного файла, например (проект Test )

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        class MyClass : Form
        {
            public MyClass()
            {
                // Создание и настройка элементов управления
                Label lblBmp = new Label();
                lblBmp.Parent = this;
                lblBmp.Dock = DockStyle.Fill;
                lblBmp.Image = Image.FromFile("Photo.bmp");
                lblBmp.Tag = "Рисунок bmp";
        
                Label lblJpg = new Label();
                lblJpg.Parent = this;
                lblJpg.Dock = DockStyle.Fill;
                lblJpg.Image = Image.FromFile("Photo.jpg");
                lblJpg.Tag = "Рисунок jpg";
        
                Label lblPng = new Label();
                lblPng.Parent = this;
                lblPng.Dock = DockStyle.Fill;
                lblPng.Image = Image.FromFile("Photo.png");
                lblPng.Tag = "Рисунок png";
        
                lblBmp.BringToFront();// Меняем Z-последовательность
                this.Text = (string)lblBmp.Tag;// Заголовок окна
        
                // Подписываем на событие Click
                lblBmp.Click += new EventHandler(lbl_Click);
                lblJpg.Click += lbl_Click;
                lblPng.Click += lbl_Click;
            }
        
            void lbl_Click(object sender, EventArgs e)
            {
                Control ctrl = sender as Control;
                if (ctrl == null)
                {
                    MessageBox.Show("Не могу привести типы");
                    return;
                }
        
                this.Controls.Remove(ctrl);
                if (this.Controls.Count > 0)
                {
                    this.Text = (string)this.Controls[0].Tag;
                    this.Controls[0].BringToFront();
                }
                else
                    this.Text = "Меток больше нет";
            }
        }
    }

    Файлы с картинками должны быть помещены в один каталог со сборкой, иначе нужно указать путь у файлу (абсолютный или относительный). Одна из картинок, выводимых этим кодом, будет такой (это, конечно же, опять я - любимый!)

    Кроме того, в метке можно использовать изображения, созданные в качестве ресурсов и прикрепленные к исполняемому файлу программы.

    Метка-ссылка LinkLabel

    Этот элемент обладает свойствами традиционной HTML -гиперссылки Hyperlink. Помимо наследуемых от Control свойств BackColor и ForeColor метка-ссылка имеет характерные для Hyperlink свойства:

  • LinkColor - цвет исходного отображения непосещенной ссылки (по умолчанию синий)
  • DisabledLinkColor - цвет недоступной ссылки (по умолчанию серый)
  • ActiveLinkColor - цвет ссылки при щелчке на ней (по умолчанию красный)
  • VisitedLinkColor - цвет посещенной ссылки (по умолчанию сиреневый)
  • Метка-ссылка определяется свойством Text, но активная ее часть, воспринимаемая как гиперссылка, задается свойством LinkArea. Это свойство представляет собой структуру LinkArea с общедоступными полями Start и Length типа int. Например

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        public MyClass()
        {
            this.Font = new Font("Arial", 14);
            LinkLabel linkLabel = new LinkLabel();
            this.Controls.Add(linkLabel);
            linkLabel.Dock = DockStyle.Fill;
            linkLabel.Text = "Щелкните здесь 
        для появления сообщения";
            linkLabel.LinkArea = new LinkArea(9, 5);
            linkLabel.LinkBehavior = LinkBehavior.SystemDefault;
            linkLabel.LinkClicked += new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
        }
        
        void linkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            MessageBox.Show("Вы щелкнули на метке-ссылке");
        }
    }

    Поведение ссылки задается свойством LinkBehavior, ожидающим одно из значений одноименного перечисления System.Windows.Forms.LinkBehavior. По умолчанию установлено LinkBehavior=SystemDefault, определяющее поведение ссылки как в Microsoft Internet Explorer.

    Одной метке можно назначить несколько ссылок с помощью свойства Links, которое является объектом составного класса LinkLabel.LinkCollection. Щелчок на ссылке LinkLabel инициирует событие LinkClicked, обработчик которого определяется делегатом LinkLabelLinkClickedEventHandler. Объект типа LinkLabelLinkClickedEventArgs, сопровождающий обработчик, является экземпляром составного класса LinkLabel.Link и несет данные активированной ссылки ( URL или имя файла) в свойстве LinkData типа Object.

    Приведем пример, в котором программа Windows Forms отвечает на нажатие ссылки запуском другого процесса, используя метод System.Diagnostics.Process.Start().

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using Return = System.Environment;// Псевдоним класса
        
    class MyClass : Form
    {
        public MyClass()
        {
            this.Font = new Font("Arial", 14);
            this.Width = 2 * this.Height;
            this.Text = "Элемент управления LinkLabel";
        
            LinkLabel linkLabel = new LinkLabel();
            linkLabel.Parent = this;
            linkLabel.Dock = DockStyle.Fill;
            linkLabel.Text = "Из этого приложения 
        Вы можете:" + Return.NewLine
                   + "\n1. Подключиться к сайту корпорации 
        \"Майкрософт\"" + Return.NewLine
        + "2. Запустить графический редактор \"
        Microsoft Paint\"" + Return.NewLine
        + "3. Запустить игру \"
        Косынка\"" + Return.NewLine
        + "4. Запустить текстовый редактор \"
        Блокнот\"" + Return.NewLine;
            string subStr = "Майкрософт";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "http://www.microsoft.com");
            subStr = "Microsoft Paint";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "msPaint");
            subStr = "Косынка";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "Sol");
            subStr = "Блокнот";
            linkLabel.Links.Add(linkLabel.Text.IndexOf(subStr),
                subStr.Length, "Notepad");
        
            linkLabel.LinkClicked += 
                new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
        }
        
        void linkLabel_LinkClicked(object sender, 
            LinkLabelLinkClickedEventArgs e)
        {
            string strLink = e.Link.LinkData as string;
            if (strLink != null)
                try
                {
                    System.Diagnostics.Process.Start(strLink);
                }
                catch (Exception err)
                {
                    string strError = "Не могу выполнить процесс !!!\n"
                    + "Ошибка: " + err.Message;
                    MessageBox.Show(strError);
                }
        }
    }

    Результат выполнения примера будет таким

    Щелчки на ссылках запускают соответствующие программы. Для подстраховки мы отлавливаем возможные исключения Windows.

    Графическое окно PictureBox

    Этот элемент служит для отображения растров или метафайлов. Изображение задается свойством Image. Свойство SizeMode задает режим отображения с помощью перечисления System.Windows.Forms.PictureBoxSizeMode, которое имеет следующие значения

  • AutoSize - графическое окно подстраивается под размер изображения
  • CenterImage - изображение размещается по центру графического окна
  • Normal - изображение размещается в левом верхнем углу графического окна
  • StretchImage - изображение масштабируется под размер графического окна
  • Zoom - изображение масштабируется внутри графического окна без искажений
  • Вот пример, для выполнения которого нужно разместить рисунки (прилагаются в каталоге Pictures ) в одной папке с исполнимым файлом.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    class MyClass : Form
    {
        PictureBox pictureBox;
        string[] photo = {
                          "Photo.bmp",
                          "Photo.jpg",
                          "Photo.png"
                         };
        
        public MyClass()
        {
            // Загрузили иконку
            this.Icon = new Icon("1.ICO");
            // Текст заголовка окна
            this.Text = "Испытание элемента PictureBox";
            // Подписались на события формы
            this.Click += FormClick;
            this.Paint += BeginPaint;
        
            pictureBox = new PictureBox();// Создали элемент
            pictureBox.Parent = this;// Назначили родителя
            // Распахнули на всю форму
            pictureBox.Dock = DockStyle.Fill;
            // Масштабировать без искажений
            pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
            // Чтобы не заслонял текстовое сообщение на форме
            pictureBox.Visible = false;
            // Подписываем на щелчки элемента тот же обработчик
            pictureBox.Click += FormClick;
        }
        
        void BeginPaint(object sender, PaintEventArgs e)
        {
            // Создаем и настраиваем точку привязки текстового блока
            StringFormat strfrm = new StringFormat();
            strfrm.Alignment = StringAlignment.Center;// Центр по горизонтали
            strfrm.LineAlignment = StringAlignment.Center;// Центр по вертикали
        
            // Создаем временный объект для доступа к контексту устройства
            Graphics gr = Graphics.FromHwnd(this.Handle);
            gr.DrawString("Щелкайте на форме",  // Текст сообщения
                new Font("Arial", 14, FontStyle.Bold), // Шрифт сообщения
                new SolidBrush(Color.Red),  // Цвет шрифта
                this.ClientSize.Width / 2,   // Центр клиентской области по горизонтали
                this.ClientSize.Height / 2,  // Центр клиентской области по вертикали
                strfrm); // Передаем точку привязки текстового блока  
            gr.Dispose();// Освободили контекст устройства
        }
        
        int count = 0;// Счетчик
        bool clickFlag = false; // Устанавливается при первом щелчке
        void FormClick(object sender, EventArgs e)
        {
            if (!clickFlag)
            {
                pictureBox.Visible = true;
                this.Paint -= BeginPaint;// Разрегистрируем обработчик
                clickFlag = true;
            }
        
            //pictureBox.Image = Image.FromFile(photo[count++]);
            pictureBox.Load(photo[count++]);
            count = count < photo.Length ? count : 0;// Однострочное условие
        }
    }

    Метод Load() принимает URL или имя файла. Метод LoadAsync() выполняет загрузку в фоновом режиме, создавая дополнительный поток, и информирует о завершении событием LoadCompleted, например,

    pictureBox.LoadAsync(@"http://localhost/print.jpg");

    Если изображение загружается долго, то можно задать дежурную картинку в свойстве InitialImage, а также картинку в свойстве ErrorImage, выводимую в случае ошибки загрузки. Событие LoadProgressChanged в своем аргументе типа ProgressChangedEventArgs передает информацию о загрузке изображения в процентах. Эту информацию можно использовать в обработчике для отображения с помощью индикатора выполнения ProgressBar, например,

    void pictureBox_LoadProgressChanged(object sender,    
      ProgressChangedEventArgs e)
    {    progressBar.Value = e.ProgressPercentage;}

    Индикатор хода процесса ProgressBar

    Основные свойства ProgressBar - это целочисленные Minimum, Maximum, Value. Индикатор процесса напрямую связан со свойством Value, значение которого можно увеличивать несколькими способами:

  • Напрямую , например, Value++
  • Методом PerformStep(), увеличивающим при каждом вызове на шаг, заданный в свойстве Step
  • Методом Increment(int value), увеличивающим на величину целочисленного аргумента
  • Приведем пример, в котором движком процесса является таймер. Код является несколько избыточным, чтобы продемонстрировать побольше из бесконечного числа особенностей объектно-ориентированного программирования.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
        
    namespace Test
    {
        class MyClass : Form
        {
            ProgressBar progressBar = new ProgressBar();
            Timer timer;
            const int INTERVAL_TIME = 1000; // Миллисекунды
        
            public MyClass()
            {
                // Настройка формы
                this.Icon = new Icon("1.ICO");
                this.Text = "Испытание элемента ProgressBar ";
        
                // Настройка индикатора
                progressBar.Parent = this;
                progressBar.Width = this.ClientSize.Width;
                int y = (this.ClientSize.Height - progressBar.Height) / 2;
                progressBar.Location = new Point(0, y);
                progressBar.Minimum = 0;
                progressBar.Maximum = 20;
                progressBar.Step = 1;
                progressBar.Style = ProgressBarStyle.Continuous;
        
                // Настройка кнопки
                Button btn = new Button();
                btn.Parent = this;
                btn.Text = "Start/Stop";
                btn.AutoSize = true;
                btn.Font = new Font("Arial", 12, FontStyle.Italic);
                int x = (this.ClientSize.Width - btn.Width) / 2;
                y = this.ClientSize.Height - btn.Height - 20;
                btn.Location = new Point(x, y);
                btn.Click += new EventHandler(btn_Click);
        
                // Настройка таймера
                timer = new Timer();
                timer.Interval = INTERVAL_TIME;
                timer.Tick += new EventHandler(timer_Tick);
            }
        
            // Обработчик кнопки управления таймером
            bool startTimerFlag = false;
            void btn_Click(object sender, EventArgs e)
            {
                if (!startTimerFlag)
                    timer.Start();
                else
                    timer.Stop();
        
                startTimerFlag = !startTimerFlag;
            }
        
            // Обработчик тиков таймера 
            void timer_Tick(object sender, EventArgs e)
            {
                if (progressBar.Value < progressBar.Maximum)
                {
                    progressBar.PerformStep();
                }
                else
                {
                    timer.Stop();
                }
            }
        
            // Деструктор, доступ не управляется
            ~MyClass()
            {
                if (startTimerFlag)
                    timer.Stop();
            }
        }
    }
    Вернуться к учебному плану