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

Классы Application и Form

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

Общее описание классов Application и Form

Наряду с ручным способом конструирования форм оболочка Visual Studio 2005 (и ряд других современных IDE ) содержит и визуальный редактор диалоговых окон ( Designer - конструктор форм), который обеспечивает условия комфортного программирования на этапе разработки графического интерфейса пользователя ( GUI ) оконных приложений. Конструктор форм очень облегчает эту работу, сопровождая действия программиста генерацией соответствующего кода в исходный текст приложения. Множество мастеров оболочки тоже вшивают свои изменения в исходный код.

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

Обзор пространства имен System.Windows.Forms

Библиотечная сборка System.Windows.Forms.dll содержит несколько пространств имен, основным из которых является пространство имен System.Windows.Forms.

Пространство имен System.Windows.Forms содержит сотни типов, которые позволяют создавать высокоразвитый GUI оконных приложений. Их можно разделить на следующие большие категории:

  • Базовая инфраструктура (классы Form, Application )
  • Элементы управления, позволяющие пользователю взаимодействовать с приложением в интерактивном режиме (производные от класса Control )
  • Невизуальные компоненты, дополняющие элементы управления своей функциональностью ( Timer, ToolTip )
  • Диалоговые окна общего вида ( CommonDialog, ColorDialog, FolderBrowserDialog, FontDialog, OpenFileDialog, SaveFileDialog, PageSetupDialog, PrintDialog, PrintPreviewDialog )
  • Класс Application

    Класс Application определяет множество статических членов, позволяющих управлять поведением различных низкоуровневых элементов приложения Windows Forms. Этот класс непосредственно взаимодействует с операционной системой по управлению приложением в целом. Класс Application не может иметь наследников ( sealed - запечатанный) и не может порождать объекты. Для того, чтобы исследовать класс Application, создадим новое решение. Для этого

  • Выполните команду File/New/Project и настройте окно мастера примерно так
  • В панели Solution Explorer щелкните на пиктограмме Properties и установите тип проекта Console Application
  • Командой Project/Add Reference добавьте (удерживая клавишу Ctrl ) к проекту ссылки на библиотечные сборки System.dll и System.Windows.Forms.dll
  • Командой Project/Add New Item добавьте в проект два новых пустых файла с именами Program.cs и GeneralForm.cs
  • Заполните файлы следующим кодом
  • using System;
    using System.Windows.Forms;
        
    namespace SimpleWinApp
    {
        class GeneralForm : Form
        {
            public GeneralForm()
            {
            }
        }
    }
    using System;
    using System.Windows.Forms;
        
    namespace SimpleWinApp
    {
        class Program
        {
            static void Main()
            {
                // Поддержка визуальных стилей Windows XP
                Application.EnableVisualStyles();
                // Включение формы в цикл сообщений Windows
                Application.Run(new GeneralForm());
                // Для задержки консольного окна
                Console.ReadLine();
            }
        }
    }

    В нашем простом приложении класс GeneralForm пока не делает ничего, кроме как инкапсулирует (упаковывает) базовый библиотечный класс Form.

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

    Некоторые свойства класса Application
    Свойство Описание
    StartupPath Получает путь для исполняемого файла, который запустил приложение, не включая его имя
    ExecutablePath Получает путь для исполняемого файла, который запустил приложение, включая имя файла
    ProductName Содержит значение атрибута [ AssemblyProduct ] сборки
    ProductVersion Содержит значение атрибута [ AssemblyVersion ] сборки
    CompanyName Содержит значение атрибута [ AssemblyCompany ] сборки

    Класс Application определяет ряд статических событий уровня приложения.

    Некоторые события класса Application
    Событие Описание
    ApplicationExit Генерируется непосредственно перед тем, как данное приложение завершает работу
    Idle (простой) Генерируется тогда, когда цикл сообщений приложения заканчивает обработку текущего пакета сообщений и переходит в состояние ожидания
    ThreadExit Генерируется перед тем, как очередной поток приложения завершается. Если завершается основной поток приложения, то предшествует событию ApplicationExit
  • Проверьте работу класса Application, скорректировав файл GeneralForm.cs проекта так
  • using System;
    using System.Windows.Forms;
    using System.Reflection;// Для атрибутов
        
    // Глобальные атрибуты для сборки    
    [assembly:AssemblyCompany("Рога и копыта")]
    [assembly:AssemblyProduct("Мыльные пузыри")]
    [assembly:AssemblyVersion("0.0.0.1")]
        
    namespace SimpleWinApp
    {
        class GeneralForm : Form
        {
            int count = 0;
            const int MAX_COUNT = 100;
        
            public GeneralForm()
            {
                // Настройка консольного окна
                Console.ForegroundColor = ConsoleColor.White;
                Console.CursorVisible = false;
                Console.Title = "Вспомогательное окно";
        
                // Использование свойств класса Application
                Console.WriteLine("Это приложение 
        создано компанией: "
                    + Application.CompanyName);
                Console.WriteLine("Компания производит: "
                    + Application.ProductName);
                Console.WriteLine("Версия данного продукта: "
                    + Application.ProductVersion);
        
                // Использование событий класса Application
                Application.Idle += new EventHandler(AppIdle);
                Application.ApplicationExit += new EventHandler(AppExit);
            }
        
            void AppIdle(object sender, EventArgs e)
            {
                Console.WriteLine("{0}) Простой 
        приложения", ++count);
                if (count > MAX_COUNT)
                {
                    Console.Clear();    // Очистить буфер консоли
                    count = 0;          // Сбросить счетчик
                }
            }
        
            void AppExit(object sender, EventArgs e)
            {
                Console.WriteLine("Графическое окно 
        приложения закрыто");
            }
        }
    }

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

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

  • Выполните команду Project/New Item и выберите шаблон Assembly Information File
  • Откройте файл на редактирование и скорректируйте соответствующие атрибуты сборки в добавленном файле. Явное задание атрибутов в файле GeneralForm.cs нужно удалить
  • using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
        
    [assembly: AssemblyCompany("Рога и копыта")]
    [assembly: AssemblyProduct("Мыльные пузыри")]
    [assembly: AssemblyCopyright("Copyright ©  2007")]
    [assembly: AssemblyVersion("0.0.0.1")]
        
    [assembly: AssemblyTitle("")]
    [assembly: AssemblyCulture("")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyFileVersion("1.0.0.0")]
        
    [assembly: ComVisible(false)]
        
    [assembly: Guid("01ac5f2e-a47c-424d-9754-ac4be62ae171")]

    Функциональные возможности класса Form, наследуемые от класса Control

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

    Некоторые события класса Control, наследуемые классом Form
    Событие Описание
    Click, DoubleClick, MouseEnter, MouseLeave, MouseDown, MouseUp, MouseMove, MouseHover, MouseWheel События, генерируемые состоянием мыши
    KeyDown, KeyPress, KeyUp События, генерируемые состоянием клавиатуры
    DragDrop, DragEnter, DragLeave, DragOver События, связанные с перетаскиванием объектов
    Paint Событие, позволяющее взаимодействовать с GDI + ( Graphics Device Interface - интерфейс графического устройства)

    Все события дублируются защищенными ( protected ) виртуальными ( virtual ) перегружаемыми ( override ) методами (готовыми зарегистрированными обработчиками) и даже таких методов значительно больше. Каждый из этих методов снабжен префиксом On (например, OnClick(), OnPaint() и т.д.). Перегрузив такой метод в производном классе, мы получаем возможность выполнить какие-то предварительные действия перед вызовом родительской реализации обработчика события.

    Все связанные с состоянием мыши события работают в паре с делегатом MouseEventHandler, способному адресоваться к любому методу, удовлетворяющему сигнатуре

    void MyMouseHandler(object sender, MouseEventArgs e);

    Поступающий на вход обработчика объект класса MouseEventArgs расширяет наследуемый им класс EventArgs путем добавления целого ряда членов, специально предназначенных для обработки действий мыши.

    Некоторые свойства класса MouseEventArgs
    Свойство Описание
    Button Содержит информацию о том, какая клавиша мыши была нажата в соответствии с перечислением System.Windows.Forms.MouseButtons
    Clicks Возвращает число нажатий и отпусканий клавиши мыши
    Delta Содержит цену щелчка при вращении колеса мыши
    X, Y Координаты указателя мыши относительно начала координат клиента

    Обработка событий клавиатуры подобна обработке событий мыши. Все связанные с состоянием клавиатуры события работают в паре с делегатом KeyEventHandler, способному адресоваться к любому методу, удовлетворяющему сигнатуре

    void MyKeyboardHandler(object sender, KeyEventArgs e);

    Поступающий на вход обработчика объект класса KeyEventArgs расширяет наследуемый им класс EventArgs путем добавления целого ряда членов, специально предназначенных для обработки действий клавиатуры.

    Некоторые свойства класса KeyEventArgs
    Свойство Описание
    Alt Содержит значение, являющееся индикатором нажатия клавиши Alt
    Control Содержит значение, являющееся индикатором нажатия клавиши Ctrl
    Handled Читает или устанавливает значение, являющееся индикатором полного завершения обработки события обработчиком
    KeyCode Возвращает клавишный код для событий KeyDown или KeyUp
    Modifiers Указывает, какие модифицирующие клавиши были нажаты ( Ctrl, Shift и/или Alt )
    Shift Содержит значение, являющееся индикатором нажатия клавиши Shift

    Класс Control содержит ряд и других методов, например

  • Hide() - скрывает элемент управления, присваивая свойству Visible значение false
  • Show() - делает элемент управления видимым, устанавливая для его свойства Visible значение true
  • Invalidate() - заставляет элемент управления обновить свое изображение, посылая событие Paint
  • Приведем пример приложения, в котором построим новую форму, способную обеспечить следующее

  • Отвечать на события мыши
  • Выполнять захват и обработку ввода с клавиатуры, реагируя на событие KeyUp
  • Добавьте к нашему решению MySolution4 новый пустой проект с именем MyEventHandler командой File/Add/New Project
  • Добавьте к проекту MyEventHandler ссылки на библиотечные сборки System.dll, System.Drawing.dll, System.Windows.Forms.dll
  • Добавьте два новых пустых файла с именем GeneralForm.cs и Program.cs
  • Установите тип сборки проекта Windows Application
  • Заполните файлы проекта следующим кодом
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    namespace MyEventHandler
    {
        class GeneralForm : Form
        {
            Label lblMouseMove, lblMouseWheel, lblMouseClicks;
            string lblTitleMouseMove = "Координаты мыши: ";
            string lblTitleMouseWheel = "Цена щелчка поворота колеса: ";
            string lblTitleMouseClicks = "Число щелчков мышью: ";
        
            public GeneralForm()
            {
                // Настройка формы
                this.Text = "Обработка событий Control";
                this.Width = System.Convert.ToInt32(1.2 * this.Width);
        
                Label lbl = new Label();
                // Настройка меток
                lbl.Location = new Point(20, 5);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseMove;
                lblMouseMove = lbl;// Настроили объект и отдали
        
                lbl = new Label();// Прежняя ссылка с новым адресом
                lbl.Location = new Point(20, lblMouseMove.Location.Y 
                    + lbl.Height + 10);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseWheel;
                lblMouseWheel = lbl;// Настроили объект и отдали
        
                lbl = new Label();// Прежняя ссылка с новым адресом
                lbl.Location = new Point(20, lblMouseWheel.Location.Y
                    + lbl.Height + 10);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseClicks;
                lblMouseClicks = lbl;// Настроили объект и отдали
                lbl = null; // Больше не нужен
            }
        
            protected override void OnMouseMove(MouseEventArgs e)
            {
                lblMouseMove.Text = String.Format(lblTitleMouseMove
                    + "x={0}; y={1}", e.X, e.Y);
                base.OnMouseMove(e);
            }
        
            int countClicks = 0;
            protected override void OnMouseUp(MouseEventArgs e)
            {
                countClicks += e.Clicks;
                lblMouseClicks.Text = String.Format
        (lblTitleMouseClicks + countClicks);
                string title = "Событие формы";
        
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        MessageBox.Show("Щелкнули 
          левой кнопкой", title);
                        break;
                    case MouseButtons.Right:
                        MessageBox.Show("Щелкнули 
          правой кнопкой", title);
                        break;
                    case MouseButtons.Middle:
                        MessageBox.Show("Щелкнули 
          средней кнопкой", title);
                        break;
                }
                base.OnMouseUp(e);
            }
        
            protected override void OnMouseWheel(MouseEventArgs e)
            {
                lblMouseWheel.Text = String.Format(lblTitleMouseWheel + e.Delta);
                base.OnMouseWheel(e);
            }
        
            protected override void OnKeyUp(KeyEventArgs e)
            {
                MessageBox.Show("Нажата клавиша: " 
                    + e.KeyCode.ToString(), "Нажали клавишу");
                base.OnKeyUp(e);
            }
        }
    }
    using System;
    using System.Windows.Forms;
        
    namespace MyEventHandler
    {
        class EntryPoint
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new GeneralForm());
            }
        }
    }

    Здесь мы воспользовались перегрузкой виртуальных методов-обработчиков событий, в которых выполнили предварительную обработку данных и вызвали обработчик родительского класса для корректного завершения обработки.

  • Установите проект MyEventHandler стартовым, выполнив на нем в панели Solution Explorer команду Set as StartUp Project контектсного меню
  • Запустите проект и проверьте его работу
  • Собственные функциональные возможности класса Form

    Класс Form добавляет к унаследованным свои свойства, методы и события. Рассмотрим некоторые из них.

    Некоторые добавленные свойства класса Form
    Свойство Описание
    AcceptButton Содержит ссылку на кнопку формы, которая откликнется на нажатие клавиши Enter
    ActiveForm Содержит ссылку на текущую активную форму приложения
    ActiveMdiChild, IsMdiChild, IsMdiContainer Используется в MDI - приложениях
    CancelButton Содержит ссылку на кнопку формы, которая откликнется на нажатие клавиши Esc
    ControlBox Управляет системной кнопкой закрытия окна и наличием системного меню
    FormBorderStyle Задает стиль границы формы из перечисления FormBorderStyle
    Menu Ссылается на MainMenu формы, если оно есть
    MaximizeBox, MinimizeBox Управляют кнопками сворачивания и разворачивания формы
    ShowInTaskbar Флаг видимости формы в панели задач операционной системы
    StartPosition Задает начальную позицию окна формы из перечисления FormStartPosition
    WindowState Определяет вид формы согласно перечислению FormWindowState

    Некоторые добавленные методы класса Form
    Метод Описание
    Activate() Активизирует форму и предоставляет ей фокус ввода
    Close() Закрывает форму
    CenterToScreen() Размещает форму в центре экрана
    LayoutMdi() Размещает все дочерние формы в рамках родительской формы в соответствии с перечислением MdiLayout
    ShowDialog() Отображает форму в виде модального диалогового окна

    Некоторые добавленные события класса Form
    Событие Описание
    Activated Форма получила фокус ввода
    FormClosing, FormClosed Форма закрывается ( FormClosing ) или уже закрылась ( FormClosed )
    Deactivate Форма утратила текущий фокус ввода
    Load Форма разместилась в памяти, но еще не отобразилась
    MdiChildActivate Активизировалось дочернее окно

    Цикл существования объекта класса Form

    Жизнь формы начинается тогда, когда вызывается ее конструктор, перед его передачей методу Application.Run(). После размещения объекта в динамической памяти среда генерирует событие Load. В обработчике события Load можно настроить вид и поведение формы, подготовить содержащиеся в форме дочерние элементы управления, организовать доступ к ресурсам, необходимым для работы формы - то есть все то, что можно сделать в конструкторе формы.

    Следующим генерируется событие Activated (активизированный). Оно генерируется тогда, когда форма получает фокус ввода. При потере формой фокуса ввода генерируется событие Deactivate. То и другое события происходят всякий раз, когда пользователь переходит от одного приложения рабочего стола к другому.

    Пара событий FormClosing и FormClosed генерируются, когда пользователь хочет закрыть форму. Событие FormClosing генерируется первым и дает возможность спросить пользователя, действительно ли он хочет покинуть приложение. Оно работает в паре с делегатом System.ComponentModel.FormClosingEventHandler, определяющим в качестве аргумента экземпляр класса System.ComponentModel.FormClosingEventArgs со свойством Cancel. Если в обработчике установить для этого свойства значение true, форма не закроется и возвратится к нормальному состоянию. Если установить значение false, то форма будет закрыта и сгенерируется событие FormClosed.

    Для проверки сказанного приведем пример.

  • Измените в проекте MyEventHandler код файла GeneralForm.cs на следующий
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    using System.ComponentModel;
    using EnumDialogResult = System.Windows.Forms.DialogResult;
        
    namespace MyEventHandler
    {
        class GeneralForm : Form
        {
            string lifeTimeInfo = "";   // Вспомогательное поле
            
            public GeneralForm()
            {
                // Подписка на события
                this.FormClosing += new FormClosingEventHandler(OnClosing);
                this.Load += new EventHandler(OnLoad);
                this.FormClosed += new FormClosedEventHandler(OnClosed);
                this.Activated += new EventHandler(OnActivated);
                this.Deactivate += new EventHandler(OnDeactivate);
            }
        
            void OnClosing(object sender, FormClosingEventArgs e)
            {
                EnumDialogResult result;
                result = MessageBox.Show(
                    "Вы действительно хотите закрыть приложение?",
                    "Событие Closing формы", 
                    MessageBoxButtons.YesNo);
                if (result == EnumDialogResult.No)
                    e.Cancel = true;
                else
                {
                    e.Cancel = false;// Продолжить закрытие
                    this.Hide();// Скрыть форму до закрытия
                }
                lifeTimeInfo += "Произошло событие Closing\n";
            }
        
            void OnLoad(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Load\n";
            }
        
            void OnClosed(object sender, FormClosedEventArgs e)
            {
                lifeTimeInfo += "Произошло событие Closed";
                MessageBox.Show(lifeTimeInfo, "Отчет о событиях");
            }
        
            void OnActivated(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Activated\n";
            }
        
            void OnDeactivate(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Deactivate\n";
            }
        }
    }

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

    Формы класса System.Windows.Forms.Form

    Консольное приложение - когда программа создает единственное окно ввода-вывода и принимает ввод только с клавиатуры (консольный ввод), а вывод - экран или принтер.

    Windows -приложение - когда взаимодействие с пользователем организуется через оконный интерфейс, где наряду с клавиатурой интенсивно используется мышь и другие манипуляторы. Самое существенное отличие от консольного приложения состоит в том, что программа взаимодействует с клавиатурой, мышью и другими внешними устройствами не напрямую, а через события.

    Библиотека классов позволяет создавать консольные- и Widows-приложения. Причем проект одновременно может содержать и те и другие. Тип проекта определяется настройками компилятора через оболочку VS.NET

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

    Если исполняемый файл помечен как приложение Windows, то окно командной строки не создается и программа ничего не выводит на консоль. Но разработчик может разрабатывать Widows -приложение как консольное для удобства отладки, поскольку в такой программе он может использовать операторы вывода, чтобы узнать состояние переменных в любом месте (вместо класса System.Diagnostics.Debug ). При этом отладочная информация будет выводиться в окно командной строки. На завершающем этапе перед представлением заказчику следует убрать все консольные операторы и перекомпилировать проект как Windows -приложение.

    Создание заготовки Windows-приложения для написания кода вручную

    Написание кода вручную с нуля - наш лозунг на этапе обучения !!!

    Мы для изучения C# будем стремиться писать код вручную и нам понадобятся пространства имен как минимум

    using System;
    using System.Windows.Forms;
    using System.Drawing;

    Можно вводить свои псевдонимы пространства имен, например

    using SYSTEM = System;
    using FORMS = System.Windows.Forms;
    using DRAWING = System.Drawing;

    Для поддержки проектов в ссылках компилятора должны быть ссылки на одноименные библиотеки.

  • Создайте пустое решение с именем Application
  • Добавьте новый проект
  • Тип проекта C#, имя FirstProject, пустой проект ( Empty Project ), чтобы мастер не генерировал никакой код
  • Добавьте к проекту ссылки на библиотеки, необходимые компилятору
  • Добавим самый минимум необходимых библиотек

    System.dll
    System.Drawing.dll
    System.Windows.Forms.dll

    После щелчка на кнопке OK в окне Solution Explorer появятся добавленные ссылки на соответствующие библиотеки

  • Добавьте пустой класс к проекту
  • выбрав шаблон Class и задав имя ShowForm

    Мастер сгенерирует код заготовки класса

    using System;
      
    namespace FirstProject
    {
      /// 
      /// Summary description for ShowForm.
      /// 
      public class ShowForm
      {
        public ShowForm()
        {
          //
          // TODO: Add constructor logic here
          //
        }
      }
    }
  • Попробуем откомпилировать проект и мы получим ошибку, что в приложении нет точки входа
  • Program 'FirstProject.exe' does not have an entry point defined
    (Программе 'FirstProject.exe' не определяли точку входа)

    Точкой входа в приложение Windows Forms C# является функция Main(). Эта функция обязательно должна быть членом любого класса (метод Main ), поскольку глобальных функций в C# нет. Она обязательно должна быть статической.

    Варианты метода Main()
    static void Main(string[] args) {...}
    public static void Main(string[] args) {...}
    public static void Main() {...}
    private static void Main(string[] args) {...}
    private static void Main() {...}
    static int Main(string[] args) {... return 0; }
    public static int Main(string[] args) {... return 0; }
    public static int Main() {... return 0; }
    private static int Main(string[] args) {... return 0; }
    private static int Main() {... return 0; }
  • Добавим в приложение функцию Main()
  • using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
      public ShowForm()
      {
      }
      static void Main()
      {
      }
      }
    }

    При запуске приложения система создаст пустое окно консольного вывода и завершит работу. Если выполнить запуск командой Debug/Start Without Debugging (Ctrl+F5), то приложение будет ждать нажатия любой клавиши, затем завершится.

    В традиционных Windows -приложениях окна называются окнами, а в .NET - формами (аналогично исполнимый файл и сборка ). Для создания формы понадобится класс

    System.Windows.Forms.Form

    Этот класс является наследником целой цепочки классов, которую можно увидеть в окне Object Browser

    Кстати, в C# разрешено только одиночное наследование, а множественное наследование, как это принято в C++,- запрещено.

    Отображение простой формы

    Создадим в функции Main объект формы и отобразим его с помощью следующего кода

    using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
        public ShowForm()
        {
        }
      
        static void Main()
        {
        System.Windows.Forms.Form frm;
        frm = new System.Windows.Forms.Form();
        frm.Show();
      
        frm.Text = "Ждем 5 секунд";
        System.Threading.Thread.Sleep(5000);
      
        frm.Text = "Ждем еще 5 секунд";
        System.Threading.Thread.Sleep(5000);
        }
      }
    }

    Здесь мы применили для фиксации формы статический метод Sleep класса потока Thread.Sleep(), приостанавливающий выполнение потока (программы, загруженной в оперативную память) на указанное в аргументе количество миллисекунд. Обратите внимание, форма есть, но она не получает фокуса ввода и с ней ничего нельзя сделать.

    Использование статического метода класса Application

    Чтобы оживить форму, ее нужно включить в поток как управляемую циклом сообщений Windows. Дополним код статическим методом класса System.Windows.Forms.Application

    using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
      public ShowForm()
      {
      }
      
      static void Main()
      {
      System.Windows.Forms.Form frm;
      frm = new System.Windows.Forms.Form();
      frm.Show();
      frm.Text = "Ждем 5 секунд";
      System.Threading.Thread.Sleep(5000);
      
      frm.Text = "Ждем еще 5 секунд";
      System.Threading.Thread.Sleep(5000);  
      
      frm.Text = "Находимся в цикле сообщений";
      System.Windows.Forms.Application.Run();
      }
      }
    }

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

    static void Main()
    {
    System.Windows.Forms.Form frm;
    frm = new System.Windows.Forms.Form();
      
    frm.Text = "Находимся в цикле сообщений";
    System.Windows.Forms.Application.Run(frm);
    }

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

    Добавление пространств имен для сокращения имен классов

    Добавим в начало файла пространства имен, чтобы в именах файлов не указывать полные пути. Ненужное пространство имен пока закомментируем.

    using System;
    using System.Windows.Forms;
    // using System.Drawing;

    Код проекта будет выглядеть так

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form frm;
    frm = new Form();
        
    frm.Text = "Находимся в цикле сообщений";
    Application.Run(frm);
    }
    }
    }

    Пример с двумя формами: свободной и в цикле сообщений

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

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form child = new Form(); // Ребенок
    child.Text = "Дочерняя форма";
    child.Show();
      
    Form parent = new Form(); // Родитель
    parent.Text = "Главная форма (в цикле сообщений)";
    Application.Run(parent);
      
    MessageBox.Show("Цикл сообщений формы завершен."
      + "\nУправление передано функции Main()",
      "Информационное окно MessageBox",
      MessageBoxButtons.OK,
      MessageBoxIcon.Information);
    }
    }
    }

    Свойства форм

    В классе Form определяется много своих свойств, кроме того, он наследует ряд свойств от своих предков, например, от класса Control.

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form frm = new Form();
    frm.Text = "Свойства формы изменены";
    frm.BackColor = Color.Red;
    frm.Width *= 2;
    frm.Height /= 2;
    frm.FormBorderStyle = FormBorderStyle.FixedSingle;
    frm.MaximizeBox = false;
    frm.Cursor = Cursors.Hand;
    frm.StartPosition = FormStartPosition.CenterScreen;
    Application.Run(frm);
    }
    }
    }

    Запустим приложение, оно выдаст ошибку, что не может найти класс или структуру Color. Запустим утилиту WinCV и определим, что эта структура находится в пространстве имен System.Drawing

    Раскомментируем инструкцию using, подключающую нужное пространство имен System.Drawing к коду. Запустим проект и получим результат

    Создание обработчика события Paint

    Когда пользователь стучит по клавишам, двигает мышь, нажимает кнопки, щелкает полосы прокрутки и выбирает пункты меню, то постоянно вызываются методы программы со всяческих направлений. Такая модель управления вводом кажется хаотичной, но на практике все оказывается более упорядоченным, так как все вызовы методов выполняются в одном потоке в порядке следования. Часто удобно представлять программы для Windows Forms как конечные автоматы, состояние которых целиком определяется изменениями, вносимыми событиями.

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

    Событие Paint происходит перед первым появлением формы на экране, при заслонении окна другим окном, при развертывании окна после его сворачивания. Приведем пример создания обработчика вручную, при этом уберем пока конструктор класса по-умолчанию, чтобы не засорять код лишним

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    static void Main()
    {
    Form frm = new Form();
    frm.Text = "Испытание события Paint";
    frm.BackColor = Color.Yellow;
    frm.Width *= 2;
    frm.Height /= 2;
    frm.Paint += new PaintEventHandler(frm_Paint); // Набрать вручную !!!  
    Application.Run(frm);
    }
      
    static int count = 0;
      
    private static void frm_Paint(object sender,
      PaintEventArgs e)
    {
    Color color;
      
    if(count == 0)
    {
    count++;
    return;
    }
    else if(count % 2 == 0)
      color = Color.Red;
    else
    color = Color.Blue;
      
    System.Drawing.Graphics gr = e.Graphics;
    gr.Clear(color);
    Console.WriteLine("Произошло {0} событие Paint формы",
    ++count);
    }
    }
    }

    Разделение кода на отдельные классы

    Принято (но не обязательно) каждый новый класс размещать в отдельном файле.

  • Переименуйте функцию Main() в классе ShowForm на Execute () с заменой static на public
  • Переименуйте первый класс с ShowForm на BaseClass
  • Переименуйте файл ShowForm.cs на BaseClass.cs
  • Добавьте к проекту новый класс
  • Задайте имя класса StartClass
  • Удалите конструктор по-умолчанию из класса StartClass и добавьте функцию Main(), в которой разместите код создания экземпляра ( instance ) первого класса и вызов функции Execute()
  • using System;
      
    namespace FirstProject
    {
    public class StartClass
    {
    // Создать экземпляр базового класса
    BaseClass instance = new BaseClass();
      
    // Выполнить экземпляр базового класса
    instance.Execute();
    }
    }

    Окончательный код будет таким

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class BaseClass
      {
      public void Execute()
      {
      Form frm = new Form();
      frm.Text = "Разбивка кода на два класса";
      frm.BackColor = Color.Yellow;
      frm.Width *= 2;
      frm.Height /= 2;
      frm.Paint += new PaintEventHandler(frm_Paint);
      
      Application.Run(frm);
      }
      
      static int count = 0;
      
      private static void frm_Paint(object sender,
        PaintEventArgs e)
      {
      Color color;
      
      if(count == 0)
      {
      Console.WriteLine("Произошло {0} событие Paint формы",
            ++count);
            return;
      }
      else if(count % 2 == 0)
        color = Color.Red;
      else
        color = Color.Blue;
      
      System.Drawing.Graphics gr = e.Graphics;
        gr.Clear(color);
      Console.WriteLine("Произошло {0} событие Paint формы",
          ++count);
      }
    }
    }

    Вывод текста в клиентскую область формы

    Для вывода текста в клиентскую область формы применяется функция формы DrawString. Она имеет параметры

  • Выводимый текст String
  • Объект Font - стиль текста
  • Объект Brush - заливка
  • Объект Point.x - координата привязки
  • Объект Point.y - координата привязки
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
    public class FirstClass
    {
    static void Main()
    {
    // Создаем рабочий экземпляр на управляемой куче
    new FirstClass();
    }
      
    public FirstClass()
    {
    StartFun();
    }
      
    static void StartFun()
    {
    Form frm = new Form();
    frm.Text = "Вывод текста";
    frm.BackColor = Color.Blue;
      
    frm.Paint += new PaintEventHandler(frm_Paint);
      
    Application.Run(frm);
    }
      
    static int count = 0;
    private static void frm_Paint(object sender,
      PaintEventArgs e)
    {
    System.Drawing.Graphics gr = e.Graphics;
    // Приведение типа переданной ссылки 
    // на форму - отправителя
    Control refControl = (Control) sender; 
      gr.DrawString("Привет из обработчика Paint !!!",
          refControl.Font, Brushes.White, 0, 0);
    Console.WriteLine("Перерисовка текста {0}",
        ++count);
    }
    }
    }

    Применение одного обработчика для нескольких форм

  • Измените код класса FirstClass как показано
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class FirstClass
      {
      private static Form parent, child;
      
      static void Main()
      {
      parent = new Form();
      parent.Text = "Главное окно";
      parent.Height /= 4;
      parent.BackColor = Color.Red; 
      parent.Paint += new PaintEventHandler(frm_Paint);
      
      child = new Form();
      child.Text = "Дочернее окно";
      child.Height /= 4;
      child.BackColor = Color.Yellow;
      child.Show();
      // Набрать вручную !!!
      child.Paint += new PaintEventHandler(frm_Paint);
      
      Application.Run(parent);
      }
      
      private static void frm_Paint(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      
      System.String str, strWhat;
      if(refForm == parent)
      {
      str = "Привет из главной формы";
        strWhat = "главная";
      }
      else
      {
      str = "Привет из дочерней формы";
          strWhat = "дочерняя";
      }
      
      gr.DrawString(str, refForm.Font, Brushes.Black, 0, 0);
      Console.WriteLine("Перерисована {0} форма", strWhat);
      }
    }
    }

    Применение нескольких обработчиков для одной формы

    Сейчас мы продемонстрируем интересную особенность подключенных обработчиков:

    Если для обработки одного и того же события к форме подключено несколько обработчиков, то обработчики вызываются все в порядке их создания!

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class FirstClass
      {
      static void Main()
      {
      Form frm = new Form();
      frm.Text = "Несколько обработчиков Paint";
      frm.BackColor = Color.White;
      frm.Width = 250;
      frm.Height = 100;
      frm.Paint += new PaintEventHandler(frm_Paint1);
      frm.Paint += new PaintEventHandler(frm_Paint2);
      
      Application.Run(frm);
      }
      
      private static void frm_Paint1(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      System.String str = "Привет из обработчика Paint1";
      
      System.Int32 x = 0, y = 0;
      gr.DrawString(str, refForm.Font, Brushes.Red, x, y);
      Console.WriteLine("Сработал обработчик Paint1");
      }
      
      private static void frm_Paint2(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      System.String str = "Привет из обработчика Paint2";
      
      System.Int32 x = 0, y = 40;
      gr.DrawString(str, refForm.Font, Brushes.Blue, x, y);
      Console.WriteLine("Сработал обработчик Paint2");
      }
    }
    }

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

    Наследование форм

    До сих пор мы делали все неправильно, потому, что создавали в своем классе композицию объектов Form. Поэтому мы имели возможность использовать только общедоступные ( public ) методы и свойства этого класса. Но класс Form имеет подавляющее большинство свойств и методов, доступных для наследников ( protected ). Поэтому более правильным будет подход создания нашего класса как наследника класса Form с последующим расширением его функциональности. Следует помнить, что C# поддерживает только одиночное наследование.

  • Переименуйте в панели Solution Explorer файл FirstClass в DerivedClass
  • То же самое сделайте в коде этого файла ( либо через панель Properties, предварительно выделив через Object Browser или через Class View ), переименовав имя класса в DerivedClass
  • Объявите класс DerivedClass наследующим базовому классу Form
  • public class DerivedClass : Form
  • Внесите изменения, чтобы класс выглядел так
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      static void Main()
      {
      DerivedClass frm = new DerivedClass();
      frm.Text = "Наш класс - наследник от Form";
      frm.BackColor = Color.Red;
      
      Application.Run(frm);
      }
      }
    }

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

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      int width = 600, height = 300;
      
      // Общий конструктор
      public DerivedClass(int width, int height)
      {
      Width = width;
      Height = height;
      Text = "Общий конструктор";
      BackColor = Color.Red;
      }
      
      // Конструктор по умолчанию
      public DerivedClass()
      {
      Width = width;
      Height = height;
      Text = "Конструктор по умолчанию";
      BackColor = Color.Blue;
      }
      
      static void Main()
      {
      DerivedClass frm = new DerivedClass(300, 600);
      Application.Run(frm);
      }
      }
    }

    Вынесение точки входа в отдельный класс

    Разгрузим функцию Main от лишнего кода, да и вынесем ее в другой класс (свой единоличный), чтобы не засорять пользовательский класс несвойственным ему кодом. Функция Main должна обязательно принадлежать какому-нибудь классу, поскольку глобальные функции в C# запрещены.

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      public DerivedClass()// Конструктор
      {
      // Прямое обращение к свойствам класса Form
      Text = "Наследник от Form";
      BackColor = Color.Red;
      StartPosition = FormStartPosition.CenterScreen;
      this.Height /= 2; // this - удобно для  IntelliSense
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }

    Устранение неопределенности при нескольких точках входа

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

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      int width = 600, height = 300;
      
      // Общий конструктор
      public DerivedClass(int width, int height)
      {
      Width = width;
      Height = height;
      Text = "Общий конструктор";
      BackColor = Color.Red;
      }
      
      // Конструктор по умолчанию
      public DerivedClass()
      {
      Width = width;
      Height = height;
      Text = "Конструктор по умолчанию";
      BackColor = Color.Blue;
      }
      
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass(300, 600));
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }

    При запуске приложения будет ошибка о неопределенной точке входа. Теперь нужно определить, какой из классов, содержащих точки входа, будет стартовым в приложении.

  • Выполните команду главного меню оболочки Project/Properties и установите в открывшемся окне вкладки Common Properties/General параметр Startup Object в значение класса с нужной точкой входа.
  • Отключение консольного окна

    Операционная система при запуске нашего приложения параллельно создает и консольное окно, поскольку имеет установку на то, что тип приложения является консольным. Отменим эту установку для компилятора.

  • Выполните команду главного меню оболочки Project/Properties
  • Установите параметр Output Type компилятора в значение Windows Application. Примените установку и закройте окно.
  • Запустите приложение и убедитесь, что консольное окно больше не появляется.
  • Верните таким же способом режим компилятора в значение Console Application, поскольку для испытаний средств языка C# такой режим нам еще нужен.
  • Переопределение стандартного обработчика, наследуемого из базового класса формы

    Теперь, вместо того, чтобы самому регистрировать событие Paint и конструировать его обработчик, мы воспользуемся унаследованным от класса System.Windows.Forms.Control обработчиком OnPaint(),который вызывается формой автоматически при разрушении ее вида. Мы хотим контролировать этот обработчик своим кодом. В базовом классе этот обработчик объявлен виртуальным ( virtual ), поэтому его нужно переопределить в наследнике с ключевым словом override.

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      public DerivedClass()// Конструктор
      {
      // Прямое обращение к свойствам класса Form
      Text = "Переопределение наследуемого "
           + "обработчика OnPaint";
      BackColor = Color.Blue;// Синий фон
      StartPosition = FormStartPosition.CenterScreen;
      // this - удобнее для вызова IntelliSense
      this.Height /= 2; 
      this.Width *= 2;
      }
      
      // Замещающий метод обработки события Paint
      // Переопределение наследуемого обработчика OnPaint()
      protected override void OnPaint(PaintEventArgs e) // Набрать вручную !!!
      {
      System.Drawing.Graphics gr = e.Graphics;
      System.String str = "Привет из замещенного "
           + "обработчика OnPaint";
      System.Int32 x = 0, y = 0; // то же, что int - псевдоним
        gr.DrawString(str, Font, // Применить как у владельца (формы)
        Brushes.White, // Белый текст
        x, y); // Привязка текста к клиентской области
      Console.WriteLine("Сработал замещенный "
              + "обработчик OnPaint");
      
      // Обработчик базового класса
      base.OnPaint (e);
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }
    Страницы:

    Общее описание классов Application и Form

    Наряду с ручным способом конструирования форм оболочка Visual Studio 2005 (и ряд других современных IDE ) содержит и визуальный редактор диалоговых окон ( Designer - конструктор форм), который обеспечивает условия комфортного программирования на этапе разработки графического интерфейса пользователя ( GUI ) оконных приложений. Конструктор форм очень облегчает эту работу, сопровождая действия программиста генерацией соответствующего кода в исходный текст приложения. Множество мастеров оболочки тоже вшивают свои изменения в исходный код.

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

    Обзор пространства имен System.Windows.Forms

    Библиотечная сборка System.Windows.Forms.dll содержит несколько пространств имен, основным из которых является пространство имен System.Windows.Forms.

    Пространство имен System.Windows.Forms содержит сотни типов, которые позволяют создавать высокоразвитый GUI оконных приложений. Их можно разделить на следующие большие категории:

  • Базовая инфраструктура (классы Form, Application )
  • Элементы управления, позволяющие пользователю взаимодействовать с приложением в интерактивном режиме (производные от класса Control )
  • Невизуальные компоненты, дополняющие элементы управления своей функциональностью ( Timer, ToolTip )
  • Диалоговые окна общего вида ( CommonDialog, ColorDialog, FolderBrowserDialog, FontDialog, OpenFileDialog, SaveFileDialog, PageSetupDialog, PrintDialog, PrintPreviewDialog )
  • Класс Application

    Класс Application определяет множество статических членов, позволяющих управлять поведением различных низкоуровневых элементов приложения Windows Forms. Этот класс непосредственно взаимодействует с операционной системой по управлению приложением в целом. Класс Application не может иметь наследников ( sealed - запечатанный) и не может порождать объекты. Для того, чтобы исследовать класс Application, создадим новое решение. Для этого

  • Выполните команду File/New/Project и настройте окно мастера примерно так
  • В панели Solution Explorer щелкните на пиктограмме Properties и установите тип проекта Console Application
  • Командой Project/Add Reference добавьте (удерживая клавишу Ctrl ) к проекту ссылки на библиотечные сборки System.dll и System.Windows.Forms.dll
  • Командой Project/Add New Item добавьте в проект два новых пустых файла с именами Program.cs и GeneralForm.cs
  • Заполните файлы следующим кодом
  • using System;
    using System.Windows.Forms;
        
    namespace SimpleWinApp
    {
        class GeneralForm : Form
        {
            public GeneralForm()
            {
            }
        }
    }
    using System;
    using System.Windows.Forms;
        
    namespace SimpleWinApp
    {
        class Program
        {
            static void Main()
            {
                // Поддержка визуальных стилей Windows XP
                Application.EnableVisualStyles();
                // Включение формы в цикл сообщений Windows
                Application.Run(new GeneralForm());
                // Для задержки консольного окна
                Console.ReadLine();
            }
        }
    }

    В нашем простом приложении класс GeneralForm пока не делает ничего, кроме как инкапсулирует (упаковывает) базовый библиотечный класс Form.

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

    Некоторые свойства класса Application
    Свойство Описание
    StartupPath Получает путь для исполняемого файла, который запустил приложение, не включая его имя
    ExecutablePath Получает путь для исполняемого файла, который запустил приложение, включая имя файла
    ProductName Содержит значение атрибута [ AssemblyProduct ] сборки
    ProductVersion Содержит значение атрибута [ AssemblyVersion ] сборки
    CompanyName Содержит значение атрибута [ AssemblyCompany ] сборки

    Класс Application определяет ряд статических событий уровня приложения.

    Некоторые события класса Application
    Событие Описание
    ApplicationExit Генерируется непосредственно перед тем, как данное приложение завершает работу
    Idle (простой) Генерируется тогда, когда цикл сообщений приложения заканчивает обработку текущего пакета сообщений и переходит в состояние ожидания
    ThreadExit Генерируется перед тем, как очередной поток приложения завершается. Если завершается основной поток приложения, то предшествует событию ApplicationExit
  • Проверьте работу класса Application, скорректировав файл GeneralForm.cs проекта так
  • using System;
    using System.Windows.Forms;
    using System.Reflection;// Для атрибутов
        
    // Глобальные атрибуты для сборки    
    [assembly:AssemblyCompany("Рога и копыта")]
    [assembly:AssemblyProduct("Мыльные пузыри")]
    [assembly:AssemblyVersion("0.0.0.1")]
        
    namespace SimpleWinApp
    {
        class GeneralForm : Form
        {
            int count = 0;
            const int MAX_COUNT = 100;
        
            public GeneralForm()
            {
                // Настройка консольного окна
                Console.ForegroundColor = ConsoleColor.White;
                Console.CursorVisible = false;
                Console.Title = "Вспомогательное окно";
        
                // Использование свойств класса Application
                Console.WriteLine("Это приложение 
        создано компанией: "
                    + Application.CompanyName);
                Console.WriteLine("Компания производит: "
                    + Application.ProductName);
                Console.WriteLine("Версия данного продукта: "
                    + Application.ProductVersion);
        
                // Использование событий класса Application
                Application.Idle += new EventHandler(AppIdle);
                Application.ApplicationExit += new EventHandler(AppExit);
            }
        
            void AppIdle(object sender, EventArgs e)
            {
                Console.WriteLine("{0}) Простой 
        приложения", ++count);
                if (count > MAX_COUNT)
                {
                    Console.Clear();    // Очистить буфер консоли
                    count = 0;          // Сбросить счетчик
                }
            }
        
            void AppExit(object sender, EventArgs e)
            {
                Console.WriteLine("Графическое окно 
        приложения закрыто");
            }
        }
    }

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

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

  • Выполните команду Project/New Item и выберите шаблон Assembly Information File
  • Откройте файл на редактирование и скорректируйте соответствующие атрибуты сборки в добавленном файле. Явное задание атрибутов в файле GeneralForm.cs нужно удалить
  • using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
        
    [assembly: AssemblyCompany("Рога и копыта")]
    [assembly: AssemblyProduct("Мыльные пузыри")]
    [assembly: AssemblyCopyright("Copyright ©  2007")]
    [assembly: AssemblyVersion("0.0.0.1")]
        
    [assembly: AssemblyTitle("")]
    [assembly: AssemblyCulture("")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyFileVersion("1.0.0.0")]
        
    [assembly: ComVisible(false)]
        
    [assembly: Guid("01ac5f2e-a47c-424d-9754-ac4be62ae171")]

    Функциональные возможности класса Form, наследуемые от класса Control

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

    Некоторые события класса Control, наследуемые классом Form
    Событие Описание
    Click, DoubleClick, MouseEnter, MouseLeave, MouseDown, MouseUp, MouseMove, MouseHover, MouseWheel События, генерируемые состоянием мыши
    KeyDown, KeyPress, KeyUp События, генерируемые состоянием клавиатуры
    DragDrop, DragEnter, DragLeave, DragOver События, связанные с перетаскиванием объектов
    Paint Событие, позволяющее взаимодействовать с GDI + ( Graphics Device Interface - интерфейс графического устройства)

    Все события дублируются защищенными ( protected ) виртуальными ( virtual ) перегружаемыми ( override ) методами (готовыми зарегистрированными обработчиками) и даже таких методов значительно больше. Каждый из этих методов снабжен префиксом On (например, OnClick(), OnPaint() и т.д.). Перегрузив такой метод в производном классе, мы получаем возможность выполнить какие-то предварительные действия перед вызовом родительской реализации обработчика события.

    Все связанные с состоянием мыши события работают в паре с делегатом MouseEventHandler, способному адресоваться к любому методу, удовлетворяющему сигнатуре

    void MyMouseHandler(object sender, MouseEventArgs e);

    Поступающий на вход обработчика объект класса MouseEventArgs расширяет наследуемый им класс EventArgs путем добавления целого ряда членов, специально предназначенных для обработки действий мыши.

    Некоторые свойства класса MouseEventArgs
    Свойство Описание
    Button Содержит информацию о том, какая клавиша мыши была нажата в соответствии с перечислением System.Windows.Forms.MouseButtons
    Clicks Возвращает число нажатий и отпусканий клавиши мыши
    Delta Содержит цену щелчка при вращении колеса мыши
    X, Y Координаты указателя мыши относительно начала координат клиента

    Обработка событий клавиатуры подобна обработке событий мыши. Все связанные с состоянием клавиатуры события работают в паре с делегатом KeyEventHandler, способному адресоваться к любому методу, удовлетворяющему сигнатуре

    void MyKeyboardHandler(object sender, KeyEventArgs e);

    Поступающий на вход обработчика объект класса KeyEventArgs расширяет наследуемый им класс EventArgs путем добавления целого ряда членов, специально предназначенных для обработки действий клавиатуры.

    Некоторые свойства класса KeyEventArgs
    Свойство Описание
    Alt Содержит значение, являющееся индикатором нажатия клавиши Alt
    Control Содержит значение, являющееся индикатором нажатия клавиши Ctrl
    Handled Читает или устанавливает значение, являющееся индикатором полного завершения обработки события обработчиком
    KeyCode Возвращает клавишный код для событий KeyDown или KeyUp
    Modifiers Указывает, какие модифицирующие клавиши были нажаты ( Ctrl, Shift и/или Alt )
    Shift Содержит значение, являющееся индикатором нажатия клавиши Shift

    Класс Control содержит ряд и других методов, например

  • Hide() - скрывает элемент управления, присваивая свойству Visible значение false
  • Show() - делает элемент управления видимым, устанавливая для его свойства Visible значение true
  • Invalidate() - заставляет элемент управления обновить свое изображение, посылая событие Paint
  • Приведем пример приложения, в котором построим новую форму, способную обеспечить следующее

  • Отвечать на события мыши
  • Выполнять захват и обработку ввода с клавиатуры, реагируя на событие KeyUp
  • Добавьте к нашему решению MySolution4 новый пустой проект с именем MyEventHandler командой File/Add/New Project
  • Добавьте к проекту MyEventHandler ссылки на библиотечные сборки System.dll, System.Drawing.dll, System.Windows.Forms.dll
  • Добавьте два новых пустых файла с именем GeneralForm.cs и Program.cs
  • Установите тип сборки проекта Windows Application
  • Заполните файлы проекта следующим кодом
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    namespace MyEventHandler
    {
        class GeneralForm : Form
        {
            Label lblMouseMove, lblMouseWheel, lblMouseClicks;
            string lblTitleMouseMove = "Координаты мыши: ";
            string lblTitleMouseWheel = "Цена щелчка поворота колеса: ";
            string lblTitleMouseClicks = "Число щелчков мышью: ";
        
            public GeneralForm()
            {
                // Настройка формы
                this.Text = "Обработка событий Control";
                this.Width = System.Convert.ToInt32(1.2 * this.Width);
        
                Label lbl = new Label();
                // Настройка меток
                lbl.Location = new Point(20, 5);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseMove;
                lblMouseMove = lbl;// Настроили объект и отдали
        
                lbl = new Label();// Прежняя ссылка с новым адресом
                lbl.Location = new Point(20, lblMouseMove.Location.Y 
                    + lbl.Height + 10);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseWheel;
                lblMouseWheel = lbl;// Настроили объект и отдали
        
                lbl = new Label();// Прежняя ссылка с новым адресом
                lbl.Location = new Point(20, lblMouseWheel.Location.Y
                    + lbl.Height + 10);
                lbl.AutoSize = true;
                lbl.Parent = this;
                lbl.Text = lblTitleMouseClicks;
                lblMouseClicks = lbl;// Настроили объект и отдали
                lbl = null; // Больше не нужен
            }
        
            protected override void OnMouseMove(MouseEventArgs e)
            {
                lblMouseMove.Text = String.Format(lblTitleMouseMove
                    + "x={0}; y={1}", e.X, e.Y);
                base.OnMouseMove(e);
            }
        
            int countClicks = 0;
            protected override void OnMouseUp(MouseEventArgs e)
            {
                countClicks += e.Clicks;
                lblMouseClicks.Text = String.Format
        (lblTitleMouseClicks + countClicks);
                string title = "Событие формы";
        
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        MessageBox.Show("Щелкнули 
          левой кнопкой", title);
                        break;
                    case MouseButtons.Right:
                        MessageBox.Show("Щелкнули 
          правой кнопкой", title);
                        break;
                    case MouseButtons.Middle:
                        MessageBox.Show("Щелкнули 
          средней кнопкой", title);
                        break;
                }
                base.OnMouseUp(e);
            }
        
            protected override void OnMouseWheel(MouseEventArgs e)
            {
                lblMouseWheel.Text = String.Format(lblTitleMouseWheel + e.Delta);
                base.OnMouseWheel(e);
            }
        
            protected override void OnKeyUp(KeyEventArgs e)
            {
                MessageBox.Show("Нажата клавиша: " 
                    + e.KeyCode.ToString(), "Нажали клавишу");
                base.OnKeyUp(e);
            }
        }
    }
    using System;
    using System.Windows.Forms;
        
    namespace MyEventHandler
    {
        class EntryPoint
        {
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new GeneralForm());
            }
        }
    }

    Здесь мы воспользовались перегрузкой виртуальных методов-обработчиков событий, в которых выполнили предварительную обработку данных и вызвали обработчик родительского класса для корректного завершения обработки.

  • Установите проект MyEventHandler стартовым, выполнив на нем в панели Solution Explorer команду Set as StartUp Project контектсного меню
  • Запустите проект и проверьте его работу
  • Собственные функциональные возможности класса Form

    Класс Form добавляет к унаследованным свои свойства, методы и события. Рассмотрим некоторые из них.

    Некоторые добавленные свойства класса Form
    Свойство Описание
    AcceptButton Содержит ссылку на кнопку формы, которая откликнется на нажатие клавиши Enter
    ActiveForm Содержит ссылку на текущую активную форму приложения
    ActiveMdiChild, IsMdiChild, IsMdiContainer Используется в MDI - приложениях
    CancelButton Содержит ссылку на кнопку формы, которая откликнется на нажатие клавиши Esc
    ControlBox Управляет системной кнопкой закрытия окна и наличием системного меню
    FormBorderStyle Задает стиль границы формы из перечисления FormBorderStyle
    Menu Ссылается на MainMenu формы, если оно есть
    MaximizeBox, MinimizeBox Управляют кнопками сворачивания и разворачивания формы
    ShowInTaskbar Флаг видимости формы в панели задач операционной системы
    StartPosition Задает начальную позицию окна формы из перечисления FormStartPosition
    WindowState Определяет вид формы согласно перечислению FormWindowState

    Некоторые добавленные методы класса Form
    Метод Описание
    Activate() Активизирует форму и предоставляет ей фокус ввода
    Close() Закрывает форму
    CenterToScreen() Размещает форму в центре экрана
    LayoutMdi() Размещает все дочерние формы в рамках родительской формы в соответствии с перечислением MdiLayout
    ShowDialog() Отображает форму в виде модального диалогового окна

    Некоторые добавленные события класса Form
    Событие Описание
    Activated Форма получила фокус ввода
    FormClosing, FormClosed Форма закрывается ( FormClosing ) или уже закрылась ( FormClosed )
    Deactivate Форма утратила текущий фокус ввода
    Load Форма разместилась в памяти, но еще не отобразилась
    MdiChildActivate Активизировалось дочернее окно

    Цикл существования объекта класса Form

    Жизнь формы начинается тогда, когда вызывается ее конструктор, перед его передачей методу Application.Run(). После размещения объекта в динамической памяти среда генерирует событие Load. В обработчике события Load можно настроить вид и поведение формы, подготовить содержащиеся в форме дочерние элементы управления, организовать доступ к ресурсам, необходимым для работы формы - то есть все то, что можно сделать в конструкторе формы.

    Следующим генерируется событие Activated (активизированный). Оно генерируется тогда, когда форма получает фокус ввода. При потере формой фокуса ввода генерируется событие Deactivate. То и другое события происходят всякий раз, когда пользователь переходит от одного приложения рабочего стола к другому.

    Пара событий FormClosing и FormClosed генерируются, когда пользователь хочет закрыть форму. Событие FormClosing генерируется первым и дает возможность спросить пользователя, действительно ли он хочет покинуть приложение. Оно работает в паре с делегатом System.ComponentModel.FormClosingEventHandler, определяющим в качестве аргумента экземпляр класса System.ComponentModel.FormClosingEventArgs со свойством Cancel. Если в обработчике установить для этого свойства значение true, форма не закроется и возвратится к нормальному состоянию. Если установить значение false, то форма будет закрыта и сгенерируется событие FormClosed.

    Для проверки сказанного приведем пример.

  • Измените в проекте MyEventHandler код файла GeneralForm.cs на следующий
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
        
    using System.ComponentModel;
    using EnumDialogResult = System.Windows.Forms.DialogResult;
        
    namespace MyEventHandler
    {
        class GeneralForm : Form
        {
            string lifeTimeInfo = "";   // Вспомогательное поле
            
            public GeneralForm()
            {
                // Подписка на события
                this.FormClosing += new FormClosingEventHandler(OnClosing);
                this.Load += new EventHandler(OnLoad);
                this.FormClosed += new FormClosedEventHandler(OnClosed);
                this.Activated += new EventHandler(OnActivated);
                this.Deactivate += new EventHandler(OnDeactivate);
            }
        
            void OnClosing(object sender, FormClosingEventArgs e)
            {
                EnumDialogResult result;
                result = MessageBox.Show(
                    "Вы действительно хотите закрыть приложение?",
                    "Событие Closing формы", 
                    MessageBoxButtons.YesNo);
                if (result == EnumDialogResult.No)
                    e.Cancel = true;
                else
                {
                    e.Cancel = false;// Продолжить закрытие
                    this.Hide();// Скрыть форму до закрытия
                }
                lifeTimeInfo += "Произошло событие Closing\n";
            }
        
            void OnLoad(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Load\n";
            }
        
            void OnClosed(object sender, FormClosedEventArgs e)
            {
                lifeTimeInfo += "Произошло событие Closed";
                MessageBox.Show(lifeTimeInfo, "Отчет о событиях");
            }
        
            void OnActivated(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Activated\n";
            }
        
            void OnDeactivate(object sender, EventArgs e)
            {
                lifeTimeInfo += "Произошло событие Deactivate\n";
            }
        }
    }

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

    Формы класса System.Windows.Forms.Form

    Консольное приложение - когда программа создает единственное окно ввода-вывода и принимает ввод только с клавиатуры (консольный ввод), а вывод - экран или принтер.

    Windows -приложение - когда взаимодействие с пользователем организуется через оконный интерфейс, где наряду с клавиатурой интенсивно используется мышь и другие манипуляторы. Самое существенное отличие от консольного приложения состоит в том, что программа взаимодействует с клавиатурой, мышью и другими внешними устройствами не напрямую, а через события.

    Библиотека классов позволяет создавать консольные- и Widows-приложения. Причем проект одновременно может содержать и те и другие. Тип проекта определяется настройками компилятора через оболочку VS.NET

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

    Если исполняемый файл помечен как приложение Windows, то окно командной строки не создается и программа ничего не выводит на консоль. Но разработчик может разрабатывать Widows -приложение как консольное для удобства отладки, поскольку в такой программе он может использовать операторы вывода, чтобы узнать состояние переменных в любом месте (вместо класса System.Diagnostics.Debug ). При этом отладочная информация будет выводиться в окно командной строки. На завершающем этапе перед представлением заказчику следует убрать все консольные операторы и перекомпилировать проект как Windows -приложение.

    Создание заготовки Windows-приложения для написания кода вручную

    Написание кода вручную с нуля - наш лозунг на этапе обучения !!!

    Мы для изучения C# будем стремиться писать код вручную и нам понадобятся пространства имен как минимум

    using System;
    using System.Windows.Forms;
    using System.Drawing;

    Можно вводить свои псевдонимы пространства имен, например

    using SYSTEM = System;
    using FORMS = System.Windows.Forms;
    using DRAWING = System.Drawing;

    Для поддержки проектов в ссылках компилятора должны быть ссылки на одноименные библиотеки.

  • Создайте пустое решение с именем Application
  • Добавьте новый проект
  • Тип проекта C#, имя FirstProject, пустой проект ( Empty Project ), чтобы мастер не генерировал никакой код
  • Добавьте к проекту ссылки на библиотеки, необходимые компилятору
  • Добавим самый минимум необходимых библиотек

    System.dll
    System.Drawing.dll
    System.Windows.Forms.dll

    После щелчка на кнопке OK в окне Solution Explorer появятся добавленные ссылки на соответствующие библиотеки

  • Добавьте пустой класс к проекту
  • выбрав шаблон Class и задав имя ShowForm

    Мастер сгенерирует код заготовки класса

    using System;
      
    namespace FirstProject
    {
      /// 
      /// Summary description for ShowForm.
      /// 
      public class ShowForm
      {
        public ShowForm()
        {
          //
          // TODO: Add constructor logic here
          //
        }
      }
    }
  • Попробуем откомпилировать проект и мы получим ошибку, что в приложении нет точки входа
  • Program 'FirstProject.exe' does not have an entry point defined
    (Программе 'FirstProject.exe' не определяли точку входа)

    Точкой входа в приложение Windows Forms C# является функция Main(). Эта функция обязательно должна быть членом любого класса (метод Main ), поскольку глобальных функций в C# нет. Она обязательно должна быть статической.

    Варианты метода Main()
    static void Main(string[] args) {...}
    public static void Main(string[] args) {...}
    public static void Main() {...}
    private static void Main(string[] args) {...}
    private static void Main() {...}
    static int Main(string[] args) {... return 0; }
    public static int Main(string[] args) {... return 0; }
    public static int Main() {... return 0; }
    private static int Main(string[] args) {... return 0; }
    private static int Main() {... return 0; }
  • Добавим в приложение функцию Main()
  • using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
      public ShowForm()
      {
      }
      static void Main()
      {
      }
      }
    }

    При запуске приложения система создаст пустое окно консольного вывода и завершит работу. Если выполнить запуск командой Debug/Start Without Debugging (Ctrl+F5), то приложение будет ждать нажатия любой клавиши, затем завершится.

    В традиционных Windows -приложениях окна называются окнами, а в .NET - формами (аналогично исполнимый файл и сборка ). Для создания формы понадобится класс

    System.Windows.Forms.Form

    Этот класс является наследником целой цепочки классов, которую можно увидеть в окне Object Browser

    Кстати, в C# разрешено только одиночное наследование, а множественное наследование, как это принято в C++,- запрещено.

    Отображение простой формы

    Создадим в функции Main объект формы и отобразим его с помощью следующего кода

    using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
        public ShowForm()
        {
        }
      
        static void Main()
        {
        System.Windows.Forms.Form frm;
        frm = new System.Windows.Forms.Form();
        frm.Show();
      
        frm.Text = "Ждем 5 секунд";
        System.Threading.Thread.Sleep(5000);
      
        frm.Text = "Ждем еще 5 секунд";
        System.Threading.Thread.Sleep(5000);
        }
      }
    }

    Здесь мы применили для фиксации формы статический метод Sleep класса потока Thread.Sleep(), приостанавливающий выполнение потока (программы, загруженной в оперативную память) на указанное в аргументе количество миллисекунд. Обратите внимание, форма есть, но она не получает фокуса ввода и с ней ничего нельзя сделать.

    Использование статического метода класса Application

    Чтобы оживить форму, ее нужно включить в поток как управляемую циклом сообщений Windows. Дополним код статическим методом класса System.Windows.Forms.Application

    using System;
      
    namespace FirstProject
    {
      public class ShowForm
      {
      public ShowForm()
      {
      }
      
      static void Main()
      {
      System.Windows.Forms.Form frm;
      frm = new System.Windows.Forms.Form();
      frm.Show();
      frm.Text = "Ждем 5 секунд";
      System.Threading.Thread.Sleep(5000);
      
      frm.Text = "Ждем еще 5 секунд";
      System.Threading.Thread.Sleep(5000);  
      
      frm.Text = "Находимся в цикле сообщений";
      System.Windows.Forms.Application.Run();
      }
      }
    }

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

    static void Main()
    {
    System.Windows.Forms.Form frm;
    frm = new System.Windows.Forms.Form();
      
    frm.Text = "Находимся в цикле сообщений";
    System.Windows.Forms.Application.Run(frm);
    }

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

    Добавление пространств имен для сокращения имен классов

    Добавим в начало файла пространства имен, чтобы в именах файлов не указывать полные пути. Ненужное пространство имен пока закомментируем.

    using System;
    using System.Windows.Forms;
    // using System.Drawing;

    Код проекта будет выглядеть так

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form frm;
    frm = new Form();
        
    frm.Text = "Находимся в цикле сообщений";
    Application.Run(frm);
    }
    }
    }

    Пример с двумя формами: свободной и в цикле сообщений

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

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form child = new Form(); // Ребенок
    child.Text = "Дочерняя форма";
    child.Show();
      
    Form parent = new Form(); // Родитель
    parent.Text = "Главная форма (в цикле сообщений)";
    Application.Run(parent);
      
    MessageBox.Show("Цикл сообщений формы завершен."
      + "\nУправление передано функции Main()",
      "Информационное окно MessageBox",
      MessageBoxButtons.OK,
      MessageBoxIcon.Information);
    }
    }
    }

    Свойства форм

    В классе Form определяется много своих свойств, кроме того, он наследует ряд свойств от своих предков, например, от класса Control.

    using System;
    using System.Windows.Forms;
    //using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    public ShowForm()
    {
    }
      
    static void Main()
    {
    Form frm = new Form();
    frm.Text = "Свойства формы изменены";
    frm.BackColor = Color.Red;
    frm.Width *= 2;
    frm.Height /= 2;
    frm.FormBorderStyle = FormBorderStyle.FixedSingle;
    frm.MaximizeBox = false;
    frm.Cursor = Cursors.Hand;
    frm.StartPosition = FormStartPosition.CenterScreen;
    Application.Run(frm);
    }
    }
    }

    Запустим приложение, оно выдаст ошибку, что не может найти класс или структуру Color. Запустим утилиту WinCV и определим, что эта структура находится в пространстве имен System.Drawing

    Раскомментируем инструкцию using, подключающую нужное пространство имен System.Drawing к коду. Запустим проект и получим результат

    Создание обработчика события Paint

    Когда пользователь стучит по клавишам, двигает мышь, нажимает кнопки, щелкает полосы прокрутки и выбирает пункты меню, то постоянно вызываются методы программы со всяческих направлений. Такая модель управления вводом кажется хаотичной, но на практике все оказывается более упорядоченным, так как все вызовы методов выполняются в одном потоке в порядке следования. Часто удобно представлять программы для Windows Forms как конечные автоматы, состояние которых целиком определяется изменениями, вносимыми событиями.

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

    Событие Paint происходит перед первым появлением формы на экране, при заслонении окна другим окном, при развертывании окна после его сворачивания. Приведем пример создания обработчика вручную, при этом уберем пока конструктор класса по-умолчанию, чтобы не засорять код лишним

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
    public class ShowForm
    {
    static void Main()
    {
    Form frm = new Form();
    frm.Text = "Испытание события Paint";
    frm.BackColor = Color.Yellow;
    frm.Width *= 2;
    frm.Height /= 2;
    frm.Paint += new PaintEventHandler(frm_Paint); // Набрать вручную !!!  
    Application.Run(frm);
    }
      
    static int count = 0;
      
    private static void frm_Paint(object sender,
      PaintEventArgs e)
    {
    Color color;
      
    if(count == 0)
    {
    count++;
    return;
    }
    else if(count % 2 == 0)
      color = Color.Red;
    else
    color = Color.Blue;
      
    System.Drawing.Graphics gr = e.Graphics;
    gr.Clear(color);
    Console.WriteLine("Произошло {0} событие Paint формы",
    ++count);
    }
    }
    }

    Разделение кода на отдельные классы

    Принято (но не обязательно) каждый новый класс размещать в отдельном файле.

  • Переименуйте функцию Main() в классе ShowForm на Execute () с заменой static на public
  • Переименуйте первый класс с ShowForm на BaseClass
  • Переименуйте файл ShowForm.cs на BaseClass.cs
  • Добавьте к проекту новый класс
  • Задайте имя класса StartClass
  • Удалите конструктор по-умолчанию из класса StartClass и добавьте функцию Main(), в которой разместите код создания экземпляра ( instance ) первого класса и вызов функции Execute()
  • using System;
      
    namespace FirstProject
    {
    public class StartClass
    {
    // Создать экземпляр базового класса
    BaseClass instance = new BaseClass();
      
    // Выполнить экземпляр базового класса
    instance.Execute();
    }
    }

    Окончательный код будет таким

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class BaseClass
      {
      public void Execute()
      {
      Form frm = new Form();
      frm.Text = "Разбивка кода на два класса";
      frm.BackColor = Color.Yellow;
      frm.Width *= 2;
      frm.Height /= 2;
      frm.Paint += new PaintEventHandler(frm_Paint);
      
      Application.Run(frm);
      }
      
      static int count = 0;
      
      private static void frm_Paint(object sender,
        PaintEventArgs e)
      {
      Color color;
      
      if(count == 0)
      {
      Console.WriteLine("Произошло {0} событие Paint формы",
            ++count);
            return;
      }
      else if(count % 2 == 0)
        color = Color.Red;
      else
        color = Color.Blue;
      
      System.Drawing.Graphics gr = e.Graphics;
        gr.Clear(color);
      Console.WriteLine("Произошло {0} событие Paint формы",
          ++count);
      }
    }
    }

    Вывод текста в клиентскую область формы

    Для вывода текста в клиентскую область формы применяется функция формы DrawString. Она имеет параметры

  • Выводимый текст String
  • Объект Font - стиль текста
  • Объект Brush - заливка
  • Объект Point.x - координата привязки
  • Объект Point.y - координата привязки
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
    public class FirstClass
    {
    static void Main()
    {
    // Создаем рабочий экземпляр на управляемой куче
    new FirstClass();
    }
      
    public FirstClass()
    {
    StartFun();
    }
      
    static void StartFun()
    {
    Form frm = new Form();
    frm.Text = "Вывод текста";
    frm.BackColor = Color.Blue;
      
    frm.Paint += new PaintEventHandler(frm_Paint);
      
    Application.Run(frm);
    }
      
    static int count = 0;
    private static void frm_Paint(object sender,
      PaintEventArgs e)
    {
    System.Drawing.Graphics gr = e.Graphics;
    // Приведение типа переданной ссылки 
    // на форму - отправителя
    Control refControl = (Control) sender; 
      gr.DrawString("Привет из обработчика Paint !!!",
          refControl.Font, Brushes.White, 0, 0);
    Console.WriteLine("Перерисовка текста {0}",
        ++count);
    }
    }
    }

    Применение одного обработчика для нескольких форм

  • Измените код класса FirstClass как показано
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class FirstClass
      {
      private static Form parent, child;
      
      static void Main()
      {
      parent = new Form();
      parent.Text = "Главное окно";
      parent.Height /= 4;
      parent.BackColor = Color.Red; 
      parent.Paint += new PaintEventHandler(frm_Paint);
      
      child = new Form();
      child.Text = "Дочернее окно";
      child.Height /= 4;
      child.BackColor = Color.Yellow;
      child.Show();
      // Набрать вручную !!!
      child.Paint += new PaintEventHandler(frm_Paint);
      
      Application.Run(parent);
      }
      
      private static void frm_Paint(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      
      System.String str, strWhat;
      if(refForm == parent)
      {
      str = "Привет из главной формы";
        strWhat = "главная";
      }
      else
      {
      str = "Привет из дочерней формы";
          strWhat = "дочерняя";
      }
      
      gr.DrawString(str, refForm.Font, Brushes.Black, 0, 0);
      Console.WriteLine("Перерисована {0} форма", strWhat);
      }
    }
    }

    Применение нескольких обработчиков для одной формы

    Сейчас мы продемонстрируем интересную особенность подключенных обработчиков:

    Если для обработки одного и того же события к форме подключено несколько обработчиков, то обработчики вызываются все в порядке их создания!

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class FirstClass
      {
      static void Main()
      {
      Form frm = new Form();
      frm.Text = "Несколько обработчиков Paint";
      frm.BackColor = Color.White;
      frm.Width = 250;
      frm.Height = 100;
      frm.Paint += new PaintEventHandler(frm_Paint1);
      frm.Paint += new PaintEventHandler(frm_Paint2);
      
      Application.Run(frm);
      }
      
      private static void frm_Paint1(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      System.String str = "Привет из обработчика Paint1";
      
      System.Int32 x = 0, y = 0;
      gr.DrawString(str, refForm.Font, Brushes.Red, x, y);
      Console.WriteLine("Сработал обработчик Paint1");
      }
      
      private static void frm_Paint2(object sender, PaintEventArgs e)
      {
      System.Drawing.Graphics gr = e.Graphics;
      // Приведение типа переданной ссылки на форму
      Form refForm = (Form) sender; 
      System.String str = "Привет из обработчика Paint2";
      
      System.Int32 x = 0, y = 40;
      gr.DrawString(str, refForm.Font, Brushes.Blue, x, y);
      Console.WriteLine("Сработал обработчик Paint2");
      }
    }
    }

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

    Наследование форм

    До сих пор мы делали все неправильно, потому, что создавали в своем классе композицию объектов Form. Поэтому мы имели возможность использовать только общедоступные ( public ) методы и свойства этого класса. Но класс Form имеет подавляющее большинство свойств и методов, доступных для наследников ( protected ). Поэтому более правильным будет подход создания нашего класса как наследника класса Form с последующим расширением его функциональности. Следует помнить, что C# поддерживает только одиночное наследование.

  • Переименуйте в панели Solution Explorer файл FirstClass в DerivedClass
  • То же самое сделайте в коде этого файла ( либо через панель Properties, предварительно выделив через Object Browser или через Class View ), переименовав имя класса в DerivedClass
  • Объявите класс DerivedClass наследующим базовому классу Form
  • public class DerivedClass : Form
  • Внесите изменения, чтобы класс выглядел так
  • using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      static void Main()
      {
      DerivedClass frm = new DerivedClass();
      frm.Text = "Наш класс - наследник от Form";
      frm.BackColor = Color.Red;
      
      Application.Run(frm);
      }
      }
    }

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

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      int width = 600, height = 300;
      
      // Общий конструктор
      public DerivedClass(int width, int height)
      {
      Width = width;
      Height = height;
      Text = "Общий конструктор";
      BackColor = Color.Red;
      }
      
      // Конструктор по умолчанию
      public DerivedClass()
      {
      Width = width;
      Height = height;
      Text = "Конструктор по умолчанию";
      BackColor = Color.Blue;
      }
      
      static void Main()
      {
      DerivedClass frm = new DerivedClass(300, 600);
      Application.Run(frm);
      }
      }
    }

    Вынесение точки входа в отдельный класс

    Разгрузим функцию Main от лишнего кода, да и вынесем ее в другой класс (свой единоличный), чтобы не засорять пользовательский класс несвойственным ему кодом. Функция Main должна обязательно принадлежать какому-нибудь классу, поскольку глобальные функции в C# запрещены.

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      public DerivedClass()// Конструктор
      {
      // Прямое обращение к свойствам класса Form
      Text = "Наследник от Form";
      BackColor = Color.Red;
      StartPosition = FormStartPosition.CenterScreen;
      this.Height /= 2; // this - удобно для  IntelliSense
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }

    Устранение неопределенности при нескольких точках входа

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

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      int width = 600, height = 300;
      
      // Общий конструктор
      public DerivedClass(int width, int height)
      {
      Width = width;
      Height = height;
      Text = "Общий конструктор";
      BackColor = Color.Red;
      }
      
      // Конструктор по умолчанию
      public DerivedClass()
      {
      Width = width;
      Height = height;
      Text = "Конструктор по умолчанию";
      BackColor = Color.Blue;
      }
      
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass(300, 600));
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }

    При запуске приложения будет ошибка о неопределенной точке входа. Теперь нужно определить, какой из классов, содержащих точки входа, будет стартовым в приложении.

  • Выполните команду главного меню оболочки Project/Properties и установите в открывшемся окне вкладки Common Properties/General параметр Startup Object в значение класса с нужной точкой входа.
  • Отключение консольного окна

    Операционная система при запуске нашего приложения параллельно создает и консольное окно, поскольку имеет установку на то, что тип приложения является консольным. Отменим эту установку для компилятора.

  • Выполните команду главного меню оболочки Project/Properties
  • Установите параметр Output Type компилятора в значение Windows Application. Примените установку и закройте окно.
  • Запустите приложение и убедитесь, что консольное окно больше не появляется.
  • Верните таким же способом режим компилятора в значение Console Application, поскольку для испытаний средств языка C# такой режим нам еще нужен.
  • Переопределение стандартного обработчика, наследуемого из базового класса формы

    Теперь, вместо того, чтобы самому регистрировать событие Paint и конструировать его обработчик, мы воспользуемся унаследованным от класса System.Windows.Forms.Control обработчиком OnPaint(),который вызывается формой автоматически при разрушении ее вида. Мы хотим контролировать этот обработчик своим кодом. В базовом классе этот обработчик объявлен виртуальным ( virtual ), поэтому его нужно переопределить в наследнике с ключевым словом override.

    using System;
    using System.Windows.Forms;
    using System.Drawing;
      
    namespace FirstProject
    {
      public class DerivedClass : Form
      {
      public DerivedClass()// Конструктор
      {
      // Прямое обращение к свойствам класса Form
      Text = "Переопределение наследуемого "
           + "обработчика OnPaint";
      BackColor = Color.Blue;// Синий фон
      StartPosition = FormStartPosition.CenterScreen;
      // this - удобнее для вызова IntelliSense
      this.Height /= 2; 
      this.Width *= 2;
      }
      
      // Замещающий метод обработки события Paint
      // Переопределение наследуемого обработчика OnPaint()
      protected override void OnPaint(PaintEventArgs e) // Набрать вручную !!!
      {
      System.Drawing.Graphics gr = e.Graphics;
      System.String str = "Привет из замещенного "
           + "обработчика OnPaint";
      System.Int32 x = 0, y = 0; // то же, что int - псевдоним
        gr.DrawString(str, Font, // Применить как у владельца (формы)
        Brushes.White, // Белый текст
        x, y); // Привязка текста к клиентской области
      Console.WriteLine("Сработал замещенный "
              + "обработчик OnPaint");
      
      // Обработчик базового класса
      base.OnPaint (e);
      }
      }
      
      class EntryPoint
      {
      static void Main()
      {
      // Создание экземпляра и передача 
      // его в главный цикл сообщений
      // Так все и делают !!!
      Application.Run(new DerivedClass());
      }
      }
    }
    Вернуться к учебному плану