Введение в программирование на C# 2.0

Форма

Показывать лекцию целиком

Форма — это класс. Форма предназначена для реализации интерфейса пользователя приложения. Содержит большой набор свойств, методов, событий для реализации различных вариантов пользовательского интерфейса. Является окном и наследует классу Control.

Объект-представитель класса Form — это окно, которое появляется в Windows- приложении. Это означает, что объект-представитель класса Form поддерживает механизмы управления, реализованные на основе обмена сообщениями Windows. Структура сообщений и стандартные механизмы управления здесь не рассматриваются. Достаточно знать, что класс формы содержит объявление множества событий, для которых на основе стандартного интерфейса (сигнатуры) могут быть подключены и реализованы функции обработки событий.

Этот класс можно использовать как основу для создания различных вариантов окошек:

  • стандартных;
  • инструментальных;
  • всплывающих;
  • borderless;
  • диалоговых.
  • Создается "окно", а класс называется формой – поскольку в окне приложения можно разместить элементы управления, обеспечивающие интерактивное взаимодействие приложения и пользователя (заполните форму, please).
  • Известна особая категория форм – формы MDI – формы с многодокументным интерфейсом (the multiple document interface).

    Эти формы могут содержать другие формы, которые в этом случае называются MDI child forms. MDI-форма создается после установки в true свойства IsMdiContainer.

    Используя доступные в классе Формы свойства, можно определять внешний вид, размер, цвет и особенности управления создаваемого диалога:

  • свойство Text позволяет специфицировать надпись в заголовке окна приложения;
  • свойства Size и DesktopLocation позволяют определять размеры и положение окна в момент его появления на экране монитора;
  • свойство ForeColor позволяет изменить предопределенный foreground цвет всех расположенных на форме элементов управления;
  • свойства FormBorderStyle, MinimizeBox и MaximizeBox позволяют изменять размеры окна приложения во время его выполнения.
  • Методы класса обеспечивают управление формой:

  • например, метод ShowDialog обеспечивает представление формы как модального диалога;
  • метод Show показывает форму как немодальный диалог;
  • метод SetDesktopLocation используется для позиционирования формы на поверхности Рабочего стола.
  • Форма может использоваться как стартовый класс приложения. При этом класс формы должен содержать точку входа – статический метод Main. В теле этого метода обычно размещается код, обеспечивающий создание и формирование внешнего вида формы.

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

    using System;
     using System.Drawing;
     using System.Collections;
     using System.ComponentModel;
     using System.Windows.Forms;
     using System.Data;
    
     namespace FormByWizard
     {
    
     // Summary description for Form1.
    
     public class Form1 : System.Windows.Forms.Form
     {
    
     // Required designer variable.
    
     private System.ComponentModel.Container components = null;
    
     public Form1()
     {
     //
     // Required for Windows Form Designer support
     //
     InitializeComponent();
     //
     // TODO: Add any constructor code after InitializeComponent call
     //
     }
     // Clean up any resources being used.
    
     protected override void Dispose( bool disposing )
     {
     if( disposing )
     {
     if (components != null) 
     {
     components.Dispose();
     }
     }
     base.Dispose( disposing );
     }
    
     #region Windows Form Designer generated code
    
     // Required method for Designer support – do not modify
     // the contents of this method with the code editor.
    
     private void InitializeComponent()
     {
     this.components = new System.ComponentModel.Container();
     this.Size = new System.Drawing.Size(300,300);
     this.Text = "Form1";
     }
     #endregion
    
     // The main entry Point for the application.
    
     static void Main() 
     {
     Application.Run(new Form1());
     }
     }
     }

    Главная проблема – метод InitializeComponent(). Это зона ответственности мастера приложений. Категорически не рекомендуется в этом методе делать что-либо самостоятельно — во избежание потери всего того, что там может быть построено, поскольку Мастер приложения может изменить содержимое тела метода в соответствии с изменениями внешнего вида приложения.

    Важна строчка в теле функции Main

    Application.Run(new Form1());

    В принципе, эта строчка и отвечает за создание, "запуск" в потоке приложения и возможное появление на экране дисплея формы.

    Форма: управление и события жизненного цикла

    Управление жизненным циклом и отображением форм осуществляется следующими методами:

    Form.Show(),
     Form.ShowDialog(),
     Form.Activate(),
     Form.Hide(),
     Form.Close().

    Имеет смысл рассмотреть события, связанные с созданием, функционированием и уничтожением формы. К их числу относятся:

  • Load. Генерируется ОДИН РАЗ, непосредственно после первого вызова метода Form.Show() или Form.ShowDialog(). Это событие можно использовать для первоначальной инициализации переменных и для подготовки формы к работе. Сколько в приложении форм, столько раз будет генерироваться это событие. Назначение максимальных и минимальных размеров формы – для этого более подходящего места, нежели обработчик события OnLoad, не найти!
  • Activated. Многократно генерируется в течение жизни формы, когда Windows активизирует данную форму. Связано с получением и потерей фокуса. Все необходимые мероприятия выполняются здесь. Методы Form.Show(), Form.ShowDialog(), Form.Activate() (передача фокуса, реализованная программно!) способствуют этому. Передача фокуса элементу управления (кнопка – это тоже окно) сопровождается автоматическим изменением цвета элемента управления.
  • VisibleChanged. Генерируется всякий раз при изменении свойства Visible -формы — когда она становится видимой или невидимой. Событию способствуют методы Form.Show(), Form.ShowDialog(), Form.Hide(), Form.Close().
  • Deactivated. Возникает при потере фокуса формой в результате взаимодействия с пользовательским интерфейсом либо в результате вызова методов Form.Hide() или Form.Close() – но только для активной формы. Если закрывать неактивную форму, событие не произойдет! Сказано, что Activated и Deactivated возбуждаются только при перемещении фокуса в пределах приложения. При переключении с одного приложения на другое эти события не генерируются.
  • Closing. Непосредственно перед закрытием формы. В этот момент процесс закрытия формы может быть приостановлен и вообще отменен, чему способствует размещаемый в теле обработчика события следующий программный код: e.Cancel = true; // e – событие типа CancelEventArgs.
  • Closed. Уже после закрытия формы. Назад пути нет. В обработчике этого события размещается любой код для "очистки" (освобождения ресурсов) после закрытия формы.
  • Форма: контейнер как элемент управления

    Container controls – место для группировки элементов пользовательского интерфейса. И это не простая группировка! Контейнер может управлять доступом к размещаемым на его поверхности элементам управления. Это обеспечивается за счет свойства Enable, которое, будучи установленным в false, закрывает доступ к размещаемым в контейнере компонентам. "Поверхность" контейнера у некоторых его разновидностей не ограничивается непосредственно видимой областью. Здесь могут быть полосы прокрутки.

    Каждый контейнер поддерживает набор элементов, который состоит из всех вложенных в него элементов управления. У контейнера имеется свойство Count, которое возвращает количество элементов управления, свойство [ИНДЕКСАТОР], методы Add и Remove.

    К числу контейнеров относятся:

  • Form... Без комментариев!
  • Panel. Элемент управления, содержащий другие элементы управления. Может быть использован для группировки множества элементов управления, как, например, группа для множества RadioButton. По умолчанию внешний вид панели НЕ ИМЕЕТ рамок. Нет у ПАНЕЛИ и заголовка. Рамки можно заказать. Для этого в классе панели используется свойство BorderStyle. ПАНЕЛЬ является производным классом от ScrollableControl, а это значит, что можно заказать для нее собственные полосы прокрутки путем изменения значения соответствующего свойства.
  • GroupBox. Предполагается для размещения радиокнопок и прочих переключателей. Рисует рамочку вокруг элементов управления, которые группируются по какому-либо признаку. В отличие от панели, имеет заголовок, который, впрочем, можно не использовать. Не имеет полос прокрутки.
  • TabControl. Содержит tab pages, они представлены TabPage -объектами, которые могут добавляться через свойство Controls. Каждая TabPage -страница подобна листку записной книжки: страница с закладкой. В зависимости от того, подцеплены ли tab pages к объекту – представителю класса TabControl, будут генерироваться следующие события: Control.Click, Control.DoubleClick, Control.MouseDown, Control.MouseUp, Control.MouseHover, Control.MouseEnter, Control. MouseLeave и Control.MouseMove. Следует иметь в виду, что при взаимодействии пользователя со страницей генерируются одни события, при взаимодействии с "корешком" элемента управления – другие.
  • Разница между элементами управления и компонентами

    Элемент управления имеет видимое представление и непосредственно используется в процессе управления формой. Компоненты (таймер, провайдеры дополнительных свойств) видимого представления не имеют и поэтому в управлении формой участвуют опосредованно.

    Свойства элементов управления. Anchor и Dock

    Это средства, которые обеспечивают стыковку и фиксацию элементов управления в условиях изменяемых размеров контейнера.

    Anchor позволяет автоматически выдерживать постоянное расстояние между границами элемента управления, для которого оно определено, и границами контейнера. И это обеспечивается за счет автоматического изменения размеров "зацепленного" элемента управления вслед за изменениями размеров контейнера. Таким образом обеспечивается реакция элемента управления на изменения контейнера.

    Dock – стыковка, прикрепление элемента управления к границе контейнера. Dock – свойство элемента управления, а не контейнера. Центральный прямоугольник приводит к тому, что элемент управления заполнит всю поверхность контейнера.

    Extender providers. Провайдеры дополнительных свойств

    Провайдер дополнительных свойств – компонент (не элемент управления, а именно компонент!), который "предоставляет" свойства другим компонентам. Провайдеры дополнительных свойств обеспечивают дополнительные возможности при реализации элементов управления.

    К числу провайдеров относятся:

  • ToolTipProvider ;
  • HelpProvider ;
  • ErrorProvider.
  • Когда ToolTip Component на этапе разработки добавляется к форме, все остальные компоненты, размещенные на этой форме, получают новое свойство, которое даже можно просматривать и устанавливать (редактировать) в окне Properties непосредственно в процессе разработки формы. И это новое свойство, называемое ToolTip, может быть предоставлено для каждого элемента управления данной формы. Однако не надо обольщаться. Множество свойств элементов управления при этом не изменяется. При выполнении приложения дополнительное свойство остается недоступным через данный конкретный элемент управления.

    В следующем примере кода форма была построена с кнопочкой, которая была названа MyButton, и элементом управления ToolTip, названным MyToolTip и "представляющим" кнопочке свойство ToolTip.

    // И вот так просто и наивно значение свойства кнопочки не получить,
     // поскольку это свойство для данного элемента управления – не родное! 
     string myString;
     myString = MyButton.ToolTip;

    Подобное обращение приведет к ошибке еще на стадии компиляции. А вот как надо обращаться к этому самому дополнительному свойству, установленному для кнопочки MyButton, для получения ранее назначенного этому элементу управления совета (просто форменное надувательство):

    string myString;
     myString = MyToolTip.GetToolTip(MyButton); // Объект MyToolTip предоставляет
     // ToolTip (совет!) для элемента управления MyButton.

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

    Следующий программный код демонстрирует использование провайдеров.

    using System;
     using System.Drawing;
     using System.Collections;
     using System.ComponentModel;
     using System.Windows.Forms;
    
     namespace Rolls01
     {
    
     // Summary description for numPointsForm.
    
     public class numPointsForm : System.Windows.Forms.Form
     {
      Form1 f1;
      int nPoints;
      private System.Windows.Forms.TextBox numBox;
      private System.Windows.Forms.Button button1;
      private System.ComponentModel.IContainer components;
     		
      private System.Windows.Forms.ToolTip toolTip;
      // Ссылка на объект - представитель класса ErrorProvider
      private System.Windows.Forms.ErrorProvider errorProvider;
     		
      private string[] errMess;
    
      public numPointsForm(Form1 f1Key)
      {
       f1 = f1Key;
       InitializeComponent();
       errMess = new string[] {
                                 "Больше двух тараканов!",
                                 "Целое и больше двух!"
                              };
      }
    
    
     // Clean up any resources being used.
    
     protected override void Dispose( bool disposing )
     {
      if( disposing )
      {
      if(components != null)
      {
      components.Dispose();
      }
     }
      base.Dispose( disposing );
     }
    
     #region Windows Form Designer generated code
    
     // Required method for Designer support – do not modify
     // the contents of this method with the code editor.
    
     private void InitializeComponent()
     {
      this.components = new System.ComponentModel.Container();
      this.numBox = new System.Windows.Forms.TextBox();
      this.button1 = new System.Windows.Forms.Button();
      this.toolTip = new System.Windows.Forms.ToolTip(this.components);
      this.SuspendLayout();
      // 
      // numBox
      // 
      this.numBox.Location = new System.Drawing.Point(8, 11);
      this.numBox.Name = "numBox";
      this.numBox.Size = new System.Drawing.Size(184, 20);
      this.numBox.TabIndex = 0;
      this.numBox.Text = "";
      this.numBox.Validating +=
      new System.ComponentModel.CancelEventHandler(this.numBox_Validating);
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(208, 8);
      this.button1.Name = "button1";
      this.button1.TabIndex = 1;
      this.button1.Text = "OK";
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // numPointsForm
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(288, 45);
      this.Controls.Add(this.button1);
      this.Controls.Add(this.numBox);
      this.MaximizeBox = false;
      this.MinimizeBox = false;
      this.Name = "numPointsForm";
      this.Text = "numPointsForm";
      this.Load += new System.EventHandler(this.numPointsForm_Load);
      this.ResumeLayout(false);
      }
      #endregion
    
     private void numBox_Validating
                (object sender, System.ComponentModel.CancelEventArgs e)
     {
     int x;
     try
     {
      x = int.Parse(numBox.Text);
      if (x <= 1) 
      {
      numBox.Text = nPoints.ToString();
      e.Cancel = true;
      // Обращение к Error-провайдеру.
      errorProvider.SetError(numBox, this.errMess[0]);
      }
     else
     {
      nPoints = x;
     }
     }
     catch (Exception e2)
     {
     numBox.Text = nPoints.ToString();
     e.Cancel = true;
     // Обращение к Error-провайдеру.
     errorProvider.SetError(numBox, this.errMess[1]);
     }
     }
    
     private void numPointsForm_Load(object sender, System.EventArgs e)
     {
     nPoints = f1.nPoints;
     numBox.Text = nPoints.ToString();
     toolTip.SetToolTip(numBox,"Количество тараканчиков. Больше двух.");
     errorProvider = new ErrorProvider();
     }
    
     private void button1_Click(object sender, System.EventArgs e)
     {
     f1.nPoints = nPoints;
     this.Close();
     }
     }
     }

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

  • BackColor — задает цвет фона выводимой информации;
  • ForeColor — задает цвет текста выводимой информации;
  • ToolTipIcon — сопутствующая пиктограмма;
  • ToolTipTitle — дополнительный заголовок;
  • IsBalloon — совет размещается в рамочке, в стиле прямой речи в комиксах.
  • Для ToolTip провайдера объявляются два события:

  • Draw — это событие возникает при условии установления в "true" свойства OwnerDraw (особая тема для исследования),
  • Popup — возникает непосредственно в момент "появления" совета.
  • Следующий фрагмент программного кода демонстрирует вариант обработки события Popup:

    using System;
     using System.Collections.Generic;
     using System.ComponentModel;
     using System.Data;
     using System.Drawing;
     using System.Text;
     using System.Windows.Forms;
    
     namespace WindowsApplication2
     {
     public partial class Form1 : Form
     {
     Random rnd;
     float rp;
    
     // Массив советов!
     string[] tipsForPlusButton = 
     {
       "Эта кнопочка используется для вычисления суммы пары слагаемых.",
       "Жми на эту кнопочку, дружок!",
       "Не робей, эта кнопочка специально для тебя...",
       "Нажимать с осторожностью.",
       "Кнопка ПЛЮС"
     };
    
     // Специально объявляемые заголовки для выдаваемых советов.
     string[] tipsTitles =
     {
      "Страшные случаи из жизни",
      "Совет по заполнению первого окна",
      "Совет по заполнению второго окна",
      "Сумма"
     }; 
    
     public Form1()
     {
         rnd = new Random();
         InitializeComponent();
     }
    
    
     // При загрузке формы элементы управления "получают" собственные советы. 
     private void Form1_Load(object sender, EventArgs e)
     {
         this.xToolTip.SetToolTip
             (this.plusButton, tipsForPlusButton[0]);
         this.xToolTip.SetToolTip
             (
              this.textBox1,
              "Текстовое окно для ввода значения первого слагаемого"
              );
         this.xToolTip.SetToolTip
             (
              this.textBox2,
              "Текстовое окно для ввода значения второго слагаемого"
             );
         this.xToolTip.SetToolTip
             (
              this.textBox3,
              "Текстовое окно для вывода значения суммы слагаемых"
             );
    
         this.textBox1.Text = "0";
         this.textBox2.Text = "0";
     }
    
     // При нажатии кнопочки в провайдере заменяется совет. 
     private void plusButton_Click(object sender, EventArgs e)
     {
         x = int.Parse(this.textBox1.Text) + int.Parse(this.textBox2.Text);
         this.textBox3.Text = x.ToString();
         this.xToolTip.SetToolTip
             (
              this.plusButton,
              tipsForPlusButton[rnd.Next(0, tipsForPlusButton.Length)]
             );
     }
    
     // В обработчике события можно определить элемент управления, 
     // для которого выдается совет. При этом изменяется заголовок совета.
     private void xToolTip_Popup(object sender, PopupEventArgs e)
     {
         if (e.AssociatedControl.Name.Equals("plusButton"))
         {
             xToolTip.ToolTipTitle = tipsTitles[0];
             this.Text = "tip for plusButton";
         }
         else if (e.AssociatedControl.Name.Equals("textBox1"))
         {
             xToolTip.ToolTipTitle = tipsTitles[1];
         }
         else if (e.AssociatedControl.Name.Equals("textBox2"))
         {
             xToolTip.ToolTipTitle = tipsTitles[2];
         }
         else if (e.AssociatedControl.Name.Equals("textBox3"))
         {
             xToolTip.ToolTipTitle = tipsTitles[3];
         }
     }
     }
     }

    Validating и Validated элементов управления

    Предполагается, что свойство CausesValidation элементов управления, для которых будет проводиться проверка, установлено в true. Это позволяет отработать обработчику событие Validating, которое возникает в момент потери фокуса элементом управления. У обработчика события Validating имеется аргумент – объект – представитель класса CancelEventArgs, обычно с именем e. У него есть поле Cancel, которое в случае ошибки можно установить в true, что приводит к возвращению фокуса.

    Validated генерируется после Validating. Разница между ними заключается в следующем.

    Validating активизируется для данного элемента управления непосредственно после потери фокуса. Перехват этого события позволяет, например, оперативно проверить правильность заполнения данного поля ввода и в случае некорректного заполнения вернуть фокус в это поле. При этом можно предпринять некоторые шаги по коррекции неправильного значения. Например, если в поле ввода должна располагаться последовательность символов, преобразуемая к целочисленному значению, а туда была записана " qwerty ", то можно восстановить последнее корректное значение или вписать туда строку " 0 ".

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

    using System;
     using System.Drawing;
     using System.Collections;
     using System.ComponentModel;
     using System.Windows.Forms;
     using System.Data;
    
     namespace PropertiesProviders
     {
     public class Form1 : System.Windows.Forms.Form
      {
     private System.Windows.Forms.Button BigButton;
     private System.Windows.Forms.ToolTip toolTip01;
     private System.ComponentModel.IContainer components;
    
     private System.Windows.Forms.Button RedButton;
     private System.Windows.Forms.ErrorProvider errorProvider1;
     int nTip = 0;
     string[] Tips = {
     "Не торопись...",
     "Попробуй еще раз... " ,
     "Зри в корень... "
     };
    
     int nErr = 0;
     private System.Windows.Forms.TextBox textBox1;
     private System.Windows.Forms.HelpProvider helpProvider1;
     string[] ErrMess = {
     "Не надо было этого делать... ",
     "Какого хрена... ",
     "Ну все... ",
      "" 
      };
    
    
     public Form1()
     {
     InitializeComponent();
     }
    
     protected override void Dispose( bool disposing )
     {
     if( disposing )
     {
     if (components != null) 
     {
     components.Dispose();
     }
     }
     base.Dispose( disposing );
     }
    
     #region Windows Form Designer generated code
    
     // Required method for Designer support – do not modify
     // the contents of this method with the code editor.
    
     private void InitializeComponent()
     {
     this.components = new System.ComponentModel.Container();
     this.BigButton = new System.Windows.Forms.Button();
     this.toolTip01 = new System.Windows.Forms.ToolTip(this.components);
     this.RedButton = new System.Windows.Forms.Button();
     this.errorProvider1 = new System.Windows.Forms.ErrorProvider();
     this.textBox1 = new System.Windows.Forms.TextBox();
     this.helpProvider1 = new System.Windows.Forms.HelpProvider();
     this.SuspendLayout();
     // 
     // BigButton
     // 
     this.BigButton.Location = new System.Drawing.Point(8, 40);
     this.BigButton.Name = "BigButton";
     this.BigButton.TabIndex = 0;
     this.BigButton.Text = "BigButton";
     this.toolTip01.SetToolTip(this.BigButton, "Жми на эту кнопку, дружок!");
     this.BigButton.Click += new System.EventHandler(this.BigButton_Click);
     // 
     // RedButton
     // 
     this.RedButton.Location = new System.Drawing.Point(112, 40);
     this.RedButton.Name = "RedButton";
     this.RedButton.Size = new System.Drawing.Size(80, 23);
     this.RedButton.TabIndex = 1;
     this.RedButton.Text = "RedButton";
     this.toolTip01.SetToolTip(this.RedButton,"А на эту кнопку нажимать не надо!");
     this.RedButton.Click += new System.EventHandler(this.RedButton_Click);
     // 
     // errorProvider1
     // 
     this.errorProvider1.ContainerControl = this;
     // 
     // textBox1
     // 
     this.helpProvider1.SetHelpString(this.textBox1, "int values only...");
     this.textBox1.Location = new System.Drawing.Point(272, 40);
     this.textBox1.Name = "textBox1";
     this.helpProvider1.SetShowHelp(this.textBox1, true);
     this.textBox1.TabIndex = 2;
     this.textBox1.Text = "";
     this.textBox1.Validating += new
    
    System.ComponentModel.CancelEventHandler(this.textBox1_Validating);
     // 
     // Form1
     // 
     this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
     this.ClientSize = new System.Drawing.Size(536, 168);
     this.Controls.Add(this.textBox1);
     this.Controls.Add(this.RedButton);
     this.Controls.Add(this.BigButton);
     this.Name = "Form1";
     this.Text = "Form1";
     this.ResumeLayout(false);
     }
     #endregion
    
     static void Main() 
     {
     Application.Run(new Form1());
     }
     private void BigButton_Click(object sender, System.EventArgs e)
      {
     this.toolTip01.SetToolTip(this.BigButton, Tips[nTip]);
     if (nTip < 2) nTip++;
     else nTip = 0;
      }
    
     private void RedButton_Click(object sender, System.EventArgs e)
      {
     errorProvider1.SetError(RedButton, ErrMess[nErr]);
     if (nErr < 3) nErr++;
     else nErr=0;
      }
     private void textBox1_Validating
                          (object sender, System.ComponentModel.CancelEventArgs e)
     {
     int val = 0;
     try
     {
     if (this.textBox1.Text != "")
     val = int.Parse(textBox1.Text);
     errorProvider1.SetError(textBox1, ""); 
     }
     catch
     {
     e.Cancel = true;
     errorProvider1.SetError(textBox1, "Ну целое же...");
     }
     }
     }
     }

    Управление посредством сообщений

    Когда при программировании Windows-приложений еще не использовались элементы MFC, когда еще не было карт сообщений, оконная процедура WinMain определялась явным образом, и в ней содержался ЦИКЛ, и можно было наблюдать устройство механизма непрерывного "прослушивания" и интерпретациии перехватываемых сообщений системы, передаваемых данному Windows-приложению.

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

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

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

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

    Стандартный делегат

    Существует соглашение, по которому обработчик событий в .NET Framework не возвращает значений (тип возвращаемого значения void) и принимает два параметра.

    Первый параметр – ссылка на источник события (объект-издатель), второй параметр – ссылка на объект, производный от класса EventArgs. Сам по себе базовый класс НЕ НЕСЕТ никакой "полезной" информации о конкретных событиях. Назначение данного класса заключается в поддержке универсального стандартного механизма реализации событий (в том числе и передачи параметров). Забота о представлении информации о событии возлагается на разработчика производного класса.

    Делегат EventHandler

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

    Объявляется следующим образом:

    public delegate void EventHandler(object sender, EventArgs e);

    Параметры

    object sender — представляет ссылку на объект – источник события.

    EventArgs e — таким образом кодируется информация о событии.

    Модель событий в .NET Framework основывается на механизме ссылок на функции ( events – разновидности класса-делегата), которые обеспечивают стандартную стыковку события с обработчиком. Для возбуждения события необходимы два элемента:

  • Класс – носитель информации о событии. Должен наследовать от базового класса EventArgs.
  • Делегат, который настроен на метод, обеспечивающий реакцию на данное событие. Когда создается делегат – представитель класса-делегата EventHandler, прежде всего определяется соответствующий метод, выполнение которого обеспечивает реакцию на событие.
  • Таким образом, для реализации перехвата события достаточно использовать:

  • базовый класс EventArgs, если уведомление о произошедшем событии не связано с генерацией дополнительной информации, или производный от данного класса класс, если необходимо передавать дополнительную информацию, связанную с событием;
  • предопределенный класс EventHandler для реализации ссылки на метод – обработчик события.
  • Пример:

    using System;
     namespace Events00
     {
     // Однопоточное приложение, в котором для реализации механизма
     // реакции на события используется стандартный класс-делегат
     // System.EventHandler.
     // Объявляется класс события, производный от System.EventArgs.
     //=============================================================== 
     class EventPosition:System.EventArgs
     {
     // Дополнительная информация о событии.
     public int X;	
    
     // Конструктор...
     public EventPosition(int key)
     {
     X = key;
     }
    
     } 
     //===============================================================
     //Базовый класс действующих в приложении объектов.
     //Содержит ВСЕ необходимое для того, чтобы объекты производных классов
     // могли адекватно реагировать на заложенные в базовом классе события. 
    
     class BaseClass
     {
     // Ссылка на это событие идентифицируется как xEvent.
     // Это "стандартное" событие.
     // xEvent стыкуется со стандартным классом-делегатом System.EventHandler.
     public static event System.EventHandler xEvent;
    
     // Статическая переменная - носитель дополнительной информации.
     static int xPosition = 0;
     // Статическая функция. Это модель процесса непрерывного сканирования.
     // Аналог цикла обработки сообщений приложения. 	 
     public static void XScaner()
     {
     while (true)
     {//============================================================= 
     while(true)
     {//=============================================================
     // Источником события является вводимая с клавиатуры
     // последовательность символов, соответствующая целочисленному
     // значению 50. При получении этого значения происходит уведомление
     // подписанных на событие объектов.=============================
     try
      {
     Console.WriteLine("new xPosition, please: "); 
     xPosition = Int32.Parse(Console.ReadLine());
     break; 
      }
     catch
      {
     Console.WriteLine("Incorrect value of xPosition!");
      } 
      
     }//==============================================================
     if (xPosition < 0) break; // Отрицательные значения являются сигналом
     // к прекращению выполнения, а при получении 50 – возбуждается событие! 
     if (xPosition == 50) xEvent(new BaseClass(), new EventPosition(xPosition));
     }//==============================================================
     }
     }
    
     //=================================================================
     // Важное обстоятельство! В этом приложении событие возбуждается
     // ПРЕДКОМ, а реагирует на него объект - представитель класса ПОТОМКА!
     //=================================================================
    
    
     // Объявление первого производного класса.
     // Надо сделать дополнительные усилия для того, чтобы объекты этого класса
     // стали бы реагировать на события. 
    
     class ClassONE:BaseClass
     {
     public void MyFun(object obj, System.EventArgs ev)
     {
     Console.Write("{0} – ",this.ToString());
     Console.WriteLine
     ("{0}:YES! {1}",((BaseClass)obj).ToString(),((EventPosition)ev). X.ToString());
     }	
     }
     // Второй класс чуть сложнее.
     // Снабжен конструктором, который позволяет классу
     // самостоятельно "подписаться" на "интересующее" его событие.
    
     class ClassTWO:BaseClass
     {
     public ClassTWO()
     {
     BaseClass.xEvent += new System.EventHandler(this.MyFun);
     }
     public void MyFun(object obj, System.EventArgs ev)
     {
     Console.Write("{0} – ",this.ToString());
     Console.WriteLine
     ("{0}:YES! {1}",((BaseClass)obj).ToString(),((EventPosition)ev).X.ToString());
     }	
     }
    
    
     class mainClass
     {
     static void Main(string[] args)
     {
     Console.WriteLine("0______________________________"); 
     // Создали первый объект и подписали его на получение события.
     ClassONE one = new ClassONE();
     BaseClass.xEvent += new System.EventHandler(one.MyFun);
     // Второй объект подписался сам. 
     ClassTWO two = new ClassTWO(); 
     // Запустили цикл прослушивания базового класса.
     BaseClass.XScaner();
     // При получении отрицательного значения цикл обработки
     // сообщений прерывается.
     Console.WriteLine("1______________________________"); 
     // Объект - представитель класса ClassONE перестает 
     // получать уведомление о событии.
     BaseClass.xEvent –= new System.EventHandler(one.MyFun);
     // После чего повторно запускается цикл прослушивания,
     // который прекращает выполняться после повторного
     // получения отрицательного значения.
     BaseClass.XScaner();
     Console.WriteLine("2______________________________"); 
     }
     }
     }

    Класс Application

    Public sealed class Application – класс, закрытый для наследования.

    Предоставляет СТАТИЧЕСКИЕ (и только статические!) методы и свойства для общего управления приложением, предоставляет статические методы и свойства для управления приложением, в том числе:

  • методы для запуска и остановки приложения;
  • методы для запуска и остановки потоков в рамках приложения;
  • методы для обработки сообщений Windows;
  • свойства для получения сведений о приложении.
  • Открытые свойства
    AllowQuit Получает значение, определяющее, может ли вызывающий объект выйти из этого приложения
    CommonAppDataPath Получает путь для данных приложения, являющихся общими для всех пользователей
    CommonAppDataRegistry Получает раздел реестра для данных приложения, являющихся общими для всех пользователей
    CompanyName Получает название компании, связанное с приложением
    CurrentCulture Получает или задает данные о культурной среде для текущего потока
    CurrentInputLanguage Получает или задает текущий язык ввода для текущего потока
    ExecutablePath Получает путь для исполняемого файла, запустившего приложение, включая исполняемое имя
    LocalUserAppDataPath Получает путь для данных приложения местного, не перемещающегося пользователя
    MessageLoop Получает значение, указывающее, существует ли цикл обработки сообщений в данном потоке
    ProductName Получает название продукта, связанное с данным приложением
    ProductVersion Получает версию продукта, связанную с данным приложением
    SafeTopLevelCaptionFormat Получает или задает строку формата для использования в заголовках окон верхнего уровня, когда они отображаются с предупреждающим объявлением
    StartupPath Получает путь для исполняемого файла, запустившего приложение, не включая исполняемое имя
    UserAppDataPath Получает путь для данных приложения пользователя
    UserAppDataRegistry Получает раздел реестра для данных приложения пользователя
    Открытые методы
    AddMessageFilter Добавляет фильтр сообщений для контроля за сообщениями Windows во время их направления к местам назначения
    DoEvents Обрабатывает все сообщения Windows, в данный момент находящиеся в очереди сообщений
    EnableVisualStyles Включите визуальные стили Windows XP для приложения
    Exit Сообщает всем прокачкам сообщений, что следует завершить работу, а после обработки сообщений закрывает все окна приложения
    ExitThread Выходит из цикла обработки сообщений в текущем потоке и закрывает все окна в потоке
    OleRequired Инициализирует OLE в текущем потоке
    OnThreadException Вызывает событие ThreadException
    RemoveMessageFilter Удаляет фильтр сообщений из прокачки сообщений приложения
    Run Перегружен. Запускает стандартный цикл обработки сообщений приложения в текущем потоке.
    Открытые события
    ApplicationExit Происходит при закрытии приложения.
    Idle Происходит, когда приложение заканчивает обработку и собирается перейти в состояние незанятости.
    ThreadException Возникает при выдаче неперехваченного исключения потока.
    ThreadExit Происходит при закрытии потока. Перед закрытием главного потока для приложения вызывается данное событие, за которым следует событие ApplicationExit.

    IMessageFilter позволяет остановить вызов события или выполнить специальные операции до вызова обработчика событий.

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

    События класса Application
    ApplicationExit Статическое. Происходит при закрытии приложения
    Idle Статическое. Происходит, когда приложение заканчивает обработку и собирается перейти в состояние незанятости
    ThreadException Статическое. Возникает при выдаче неперехваченного исключения потока
    ThreadExit Статическое. Происходит при закрытии потока. Перед закрытием главного потока для приложения вызывается данное событие, за которым следует событие ApplicationExit

    Итак, класс Application располагает методами для запуска и останова ПОТОКОВ и ПРИЛОЖЕНИЙ, а также для обработки Windows messages.

    Вызов методов Run обеспечивает выполнение цикла обработки сообщений (an application message loop) в текущем потоке, а также, возможно, делает видимой соответствующую форму.

    Вызов методов Exit и ExitThread приводит к остановке цикла обработки сообщений.

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

    Вызов AddMessageFilter обеспечивает добавление фильтра Windows сообщений для работы с сообщениями.

    Интерфейс IMessageFilter позволяет реализовывать специальные алгоритмы непосредственно перед вызовом обработчика сообщения.

    Класс статический, и объектов – представителей этого класса создать невозможно!

    Windows message

    Прежде всего, Message – это СТРУКТУРА, представляющая в .NET сообщения Windows, те самые, которые адресуются приложению и используются системой как средство уведомления выполняющихся в Windows приложений.

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

    Объект – представитель Message structure, может быть создан с использованием метода Create (создать – не означает отправить).

    Список членов Message structure:

    ЧленыОбъявление
    Свойство HWnd. Get, set. Дескриптор окна (window handle) public IntPtr HWnd(get; set;)
    Свойство Msg. Get, set. ID сообщения public int Msg {get; set;}
    Свойство WParam. Get, set. Поле WParam сообщения. Значение этого поля зависит от конкретного сообщения. Поле WParam обычно используется для фиксирования небольших фрагментов информации, например, значений флагов public IntPtr WParam {get; set;}
    Свойство LParam property. Get, set. Поле LParam зависит от типа сообщения. Значением поля может быть ссылка на объект ( object ) public IntPtr Lparam {get; set;}
    Свойство Result. Специфицирует значение, которое возвращает Windows после перехвата сообщения public IntPtr Result {get; set;}
    Метод Create. Создает новое сообщение — представитель структуры Message
    public static Message Create (
    IntPtr hWnd,
    int msg,
    IntPtr wparam,
    IntPtr lparam);
    operator ==. Сравнивает два сообщения на предмет определения их идентичности
    public static Boolean
    operator ==(
    Message left,
    Message right);
    operator !=. Сравнивает два сообщения на предмет определения их различия
    public static Boolean
    operator !=(
    Message left,
    Message righ);
    Метод Equals. Сравнивает два сообщения для определения их эквивалентности
    public override bool
    Equals(object o)
    Метод GetHashCode. Вычисляет hash код дескриптора (handle) сообщения
    public override int
    GetHashCode();

    При этом IntPtr – это platform-specific тип, который используется для представления указателей или дескрипторов. Предназначен для представления целочисленных величин, размеры которых зависят от характеристик платформы ( is platform-specific ). То есть ожидается, что объект этого типа будет иметь размер 32 бита на 32-разрядных аппаратных средствах и операционных системах, и 64 бита — на аппаратных средствах на 64 бита и операционных системах.

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

    Объект IntPtr может быть также использован для поддержки дескрипторов.

    Например, объекты IntPtr используются в классе System.IO.FileStream для работы с дескрипторами файлов.

    Существует еще один экзотический тип – UIntPtr, который, в отличие от IntPtr, не является CLS-compliant типом.

    Только IntPtr -тип используется в common language runtime.

    UIntPtr тип разработан в основном для поддержки архитектурной симметрии (to maintain architectural symmetry) с IntPtr -типом.

    Примеры перехвата сообщений

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

    using System;
     using System.Windows.Forms;
    
     namespace MessageLoop01
     {
     // A message filter.
     public class MyMessageFilter : IMessageFilter 
     {
     long nMess = 0;
     public bool PreFilterMessage(ref Message m) 
     {
     nMess++;
     Console.WriteLine
     ("Processing the messages:{0}–{1}:{2}",m.Msg,nMess,Application.MessageLoop);
     return false;
     }
     }
    
    
     // Summary description for Class1.
    
     class Class1
     {
    
     // The main entry Point for the application.
    
    
     static void Main(string[] args)
     {
     Application.AddMessageFilter(new MyMessageFilter());
     Application.Run();
     }
     }
     }

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

    using System;
     using System.Windows.Forms;
    
     namespace MessageLoop01
     {
    
     // A message filter.
     public class MyMessageFilter : IMessageFilter 
     {
     long nMess = 0;
     public bool PreFilterMessage(ref Message m) 
     {
     nMess++;
     Console.WriteLine
     ("Processing the messages:{0}–{1}:{2}>{3}", m.Msg,m.LParam,m.WParam,nMess);
     return false;
     }
     }
    
     class MyForm:Form
     {
     public MyForm()
     {
    
     if (Application.MessageLoop) Console.WriteLine("Yes!");
     else Console.WriteLine("No!");
     Application.AddMessageFilter(new MyMessageFilter());
     }
     }
    
    
     // Summary description for Class1.
    
     class Class1
     {
    
     // The main entry Point for the application.
    
     static void Main(string[] args)
     {
     if (Application.MessageLoop) Console.WriteLine("Yes!");
     else Console.WriteLine("No!");
    
     Application.Run(new MyForm());
     }
     }
     }

    Метод WndProc

    В классах-наследниках класса Control (в классах Form, "Кнопка", "Список", ...) объявляется виртуальный метод WndProc, который обеспечивает обработку передаваемых приложению сообщений Windows. Переопределение этого метода позволяет задавать специфику поведения создаваемого элемента управления, включая и саму форму.

    protected virtual void WndProc(ref Message m);
     Parameters
     m 
     The Windows Message to process.

    Все сообщения после PreProcessMessage метода поступают к WndProc -методу. В сущности, ничего не изменилось! WndProc -метод соответствует WindowProc -функции.

    Пример переопределения WndProc

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

    Ниже демонстрируется пример переопределения метода WndProc. В результате переопределения приложение "научилось" идентифицировать и реагировать на системное сообщение WM_ACTIVATEAPP, которое передается каждому приложению при переводе приложения из активного состояния в пассивное и обратно:

    using System;
     using System.Drawing;
     using System.Windows.Forms;
    
     namespace csTempWindowsApplication1
     {
     public class Form1 : System.Windows.Forms.Form
     {
     // Здесь определяется константа, которая содержится в "windows.h" 
     // header file.
     private const int WM_ACTIVATEAPP = 0x001C;
     // Флаг активности приложения. 
     private bool appActive = true;
    
     static void Main() 
     {
     Application.Run(new Form1());
     }
     public Form1()
     { 
     // Задание свойств формы.
     this.Size = new System.Drawing.Size(300,300);
     this.Text = "Form1";
     this.Font = new System.Drawing.Font("Microsoft Sans Serif",
                                          18F,
                                          System.Drawing.FontStyle.Bold,
                                          System.Drawing.GraphicsUnit.Point,
                                          ((System.Byte)(0))
                                         );
     }
    
     // Вот переопределенная оконная процедура. 
     protected override void WndProc(ref Message m) 
     {
     // Listen for operating system messages.
     switch (m.Msg)
     {
     // Сообщение под кодовым названием WM_ACTIVATEAPP occurs when the application
     // becomes the active application or becomes inactive.
     case WM_ACTIVATEAPP:
     // The WParam value identifies what is occurring.
     appActive = (((int)m.WParam != 0));
    
     // Invalidate to get new text painted.
     this.Invalidate(); 
     break; 
     }
     base.WndProc(ref m);
     }
    
     protected override void OnPaint(PaintEventArgs e) 
     {
     // Стиль отрисовки текста определяется состоянием приложения.
     if (appActive) 
     {
     e.Graphics.FillRectangle(SystemBrushes.ActiveCaption,20,20,260,50);
     e.Graphics.DrawString("Application is active",
                                this.Font, SystemBrushes.ActiveCaptionText, 20,20);
     }
     else 
     {
     e.Graphics.FillRectangle(SystemBrushes.InactiveCaption,20,20,260,50);
     e.Graphics.DrawString("Application is Inactive",
                                this.Font, SystemBrushes.ActiveCaptionText, 20,20);
     }
     }
    
     }
     }

    Контекст приложения

    Создается простейшее Windows-приложение с единственной формой. Точка входа в приложение – статическая функция Main – располагается непосредственно в классе формы. Здесь форма создается, инициализируется, показывается:

    // Этот явно избыточный набор пространств имен формируется
     // Visual Studio по умолчанию.
     using System;
     using System.Drawing;
     using System.Collections;
     using System.ComponentModel;
     using System.Windows.Forms;
     using System.Data;
    
     namespace xForm
     {
    
     // Summary description for Form1.
    
     public class Form1 : System.Windows.Forms.Form
     {
    
     // Required designer variable.
    
     private System.ComponentModel.Container components = null;
    
     public Form1()
     {
     //
     // Required for Windows Form Designer support
     //
     InitializeComponent();
    
     //
     // TODO: Add any constructor code after InitializeComponent call
     //
     }
    
    
     // Clean up any resources being used.
    
     protected override void Dispose( bool disposing )
     {
     if( disposing )
     {
     if (components != null) 
     {
     components.Dispose();
     }
     }
     base.Dispose( disposing );
     }
    
     #region Windows Form Designer generated code
    
     // Required method for Designer support – do not modify
     // the contents of this method with the code editor.
    
     private void InitializeComponent()
     {
     this.components = new System.ComponentModel.Container();
     this.Size = new System.Drawing.Size(300,300);
     this.Text = "Form1";
     }
     #endregion
    
     // The main entry Point for the application.
    
     static void Main() 
     {
     Application.Run(new Form1());
     }
     }
     }

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

    ApplicationContext (контекст приложения) специфицирует и объединяет контекстную информацию о потоках приложения.

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

  • Пара конструкторов, которые обеспечивают инициализацию объекта. Один из них в качестве параметра использует ссылку на объект – представитель класса формы.
  • Свойство MainForm, определяющее главную форму данного контекста приложения.
  • Общедоступные методы Dispose (Overloaded. Releases the resources used by the ApplicationContext), Equals (inherited from Object ), ExitThread (Terminates the message loop of the thread), GetHashCode (inherited from Object ), GetType (inherited from Object), ToString (inherited from Object ).
  • Событие ThreadExit, которое происходит, когда в результате выполнение метода ExitThread прекращает выполнение цикл обработки сообщений.
  • Protected методы Dispose (Releases the resources used by the ApplicationContext), ExitThreadCore (Terminates the message loop of the thread), Finalize, MemberwiseClone, OnMainFormClosed.
  • Можно обойтись без контекста приложения, однако программный код, создаваемый с использованием объекта – представителя класса контекста приложения, позволяет инкапсулировать детали реализации конкретных форм приложения. Прилагаемый фрагмент приложения обеспечивает создание пары форм – представителей класса MyForm и отслеживает присутствие хотя бы одной формы. После закрытия обеих форм приложение завершает выполнение:

    // По сравнению с предыдущим примером сократили количество
    // используемых пространств имен. Оставили только необходимые. 
    using System;
    using System.ComponentModel;
    using System.Windows.Forms;
    
    namespace xApplicationContext
    {
    public class xForm : System.Windows.Forms.Form
    {
    
    private System.ComponentModel.Container components = null;
    private MyApplicationContext appContext; 
    
    // Модифицировали конструктор.
    // 1. Формы украшены названиями.
    // 2. В соответствии с замыслом, о факте закрытия формы
    // должно быть известно объекту - контексту приложения,
    // который осуществляет общее руководство приложением.
    public xForm(string keyText, MyApplicationContext contextKey)
    {
    this.Text = keyText;
    appContext = contextKey;
    InitializeComponent();	
    }
    
    protected override void Dispose( bool disposing )
    {
    if( disposing )
    {
    if (components != null) 
    {
    components.Dispose();
    }
    }
    base.Dispose( disposing );
    }
    
    #region Windows Form Designer generated code
    
    // Required method for Designer support – do not modify
    // the contents of this method with the code editor.
    
    private void InitializeComponent()
    {
    this.components = new System.ComponentModel.Container();
    this.Size = new System.Drawing.Size(300,300);
    // Объект - контекст приложения должен отреагировать на
    // факт (событие) закрытия формы. Здесь производится 
    // подписка объекта - контекста приложения на событие
    // закрытия формы. 
    this.Closed+=new EventHandler(appContext.OnFormClosed);
    }
    #endregion
    
    // The main entry Point for the application. 
    // Точка входа в приложение перенесена в класс
    // контекста приложения.
    
    //static void Main() 
    //{
    //	Application.Run(new xForm());
    //}
    }
    
    // The class that handles the creation of the application windows
    public class MyApplicationContext : ApplicationContext 
    {
    
    private int formCount;
    private xForm form1;
    private xForm form2;
    
    
    // Конструктор Контекста приложения.
    private MyApplicationContext() 
    {
    formCount = 0;
    // Create both application forms and handle the Closed event
    // to know when both forms are closed.
    form1 = new xForm("Form 0",this);
    formCount++;
    
    form2 = new xForm("Form 1",this);
    formCount++;
    form1.Show();
    form2.Show();
    }
    
    public void OnFormClosed(object sender, EventArgs e)
    {
    // Форма закрывается – счетчик открытых форм уменьшается.
    // Приложение сохраняет работоспособность до тех пор,
    // пока значение счетчика не окажется равным 0.
    // После этого – Exit().
    formCount––;
    if (formCount == 0) 
    {
    Application.Exit(); 
    }
    }
    
    static void Main(string[] args) 
    {
    // Создается объект – представитель класса MyApplicationContext,
    // который берет на себя функции управления приложением.
    MyApplicationContext context = new MyApplicationContext();
    // Приложение запускается для объекта контекста приложения.
    Application.Run(context);
    }
    }
    }
    Вернуться к учебному плану