C# winforms примеры. Введение в Windows Forms. Создание графического приложения

Последнее обновление: 31.10.2015

Для создания графических интерфейсов с помощью платформы.NET применяются разные технологии - Window Forms, WPF, приложения для магазина Windows Store (для ОС Windows 8/8.1/10). Однако наиболее простой и удобной платформой до сих пор остается Window Forms или формы. Данное руководство ставит своей целью дать понимание принципов создания графических интерфейсов с помощью технологии WinForms и работы основных элементов управления.

Создание графического приложения

Для создания графического проекта нам потребуется среда разработки Visual Studio. Поскольку наиболее распространенная пока версия Visual Studio 2013, то для данного руководства я буду использовать бесплатную версию данной среды Visual Studio Community 2013 которую можно найти на странице https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx .

После установки среды и всех ее компонентов, запустим Visual Studio и создадим проект графического приложения. Для этого в меню выберем пункт File (Файл) и в подменю выберем New - > Project (Создать - > Проект). После этого перед нами откроется диалоговое окно создания нового проекта:

В левой колонке выберем Windows Desktop , а в центральной части среди типов проектов - тип Windows Forms Application и дадим ему какое-нибудь имя в поле внизу. Например, назовем его HelloApp . После этого нажимаем OK.

После этого Visual Studio откроет наш проект с созданными по умолчанию файлами:

Большую часть пространства Visual Studio занимает графический дизайнер, который содержит форму будущего приложения. Пока она пуста и имеет только заголовок Form1. Справа находится окно файлов решения/проекта - Solution Explorer (Обозреватель решений). Там и находятся все связанные с нашим приложением файлы, в том числе файлы формы Form1.cs .

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

Теперь найдем в этом окне свойство формы Text и изменим его значение на любое другое:

Таким образом мы поменяли заголовок формы. Теперь перенесем на поле какой-нибудь элемент управления, например, кнопку. Для этого найдем в левой части Visual Studio вкладку Toolbox (Панель инструментов) . Нажмем на эту вкладку, и у нас откроется панель с элементами, откуда мы можем с помощью мыши перенести на форму любой элемент:

Найдем среди элементов кнопку и, захватив ее указателем мыши, перенесем на форму:

Это визуальная часть. Теперь приступим к самому программированию. Добавим простейший код на языке C#, который бы выводил сообщение по нажатию кнопки. Для этого мы должны перейти в файл кода, который связан с этой формой. Если у нас не открыт файл кода, мы можем нажать на форму правой кнопкой мыши и в появившемся меню выбрать View Code (Посмотреть файл кода):

Однако воспользуемся другим способом, чтобы не писать много лишнего кода. Наведем указатель мыши на кнопку и щелкнем по ней двойным щелчком. Мы автоматически попадаем в файл кода Form1.cs , который выглядит так:

Using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace HelloApp { public partial class Form1: Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { } } }

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

Using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace HelloApp { public partial class Form1: Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { MessageBox.Show("Привет"); } } }

Запуск приложения

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

После запуска приложения студия компилирует его в файл с расширением exe. Найти данный файл можно, зайдя в папку проекта и далее в каталог bin/Debug или bin/Release

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

  1. https://metanit.com/sharp/windowsforms/1.1.php

    Введение в Windows Forms

    Для создания графических интерфейсов с помощью платформы.NET применяются разные технологии - Window Forms, WPF, приложения для магазина Windows Store (для ОС Windows 8/8.1/10). Однако наиболее простой и удобной платформой до сих пор остается Window Forms или формы. Данное руководство ставит своей целью дать понимание принципов создания графических интерфейсов с помощью технологии WinForms и работы основных элементов управления.

    Создание графического приложения
    Для создания графического проекта нам потребуется среда разработки Visual Studio. Поскольку наиболее распространенная пока версия Visual Studio 2013, то для данного руководства я буду использовать бесплатную версию данной среды Visual Studio Community 2013 которую можно найти на странице https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx .

    После установки среды и всех ее компонентов, запустим Visual Studio и создадим проект графического приложения. Для этого в меню выберем пункт File (Файл) и в подменю выберем New - > Project (Создать - > Проект). После этого перед нами откроется диалоговое окно создания нового проекта:

    В левой колонке выберем Windows Desktop, а в центральной части среди типов проектов - тип Windows Forms Application и дадим ему какое-нибудь имя в поле внизу. Например, назовем его HelloApp . После этого нажимаем OK.

    После этого Visual Studio откроет наш проект с созданными по умолчанию файлами:

    Большую часть пространства Visual Studio занимает графический дизайнер, который содержит форму будущего приложения. Пока она пуста и имеет только заголовок Form1. Справа находится окно файлов решения/проекта - Solution Explorer (Обозреватель решений). Там и находятся все связанные с нашим приложением файлы, в том числе файлы формы Form1.cs .

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

    Теперь найдем в этом окне свойство формы Text и изменим его значение на любое другое:

    Таким образом мы поменяли заголовок формы. Теперь перенесем на поле какой-нибудь элемент управления, например, кнопку. Для этого найдем в левой части Visual Studio вкладку Toolbox (Панель инструментов). Нажмем на эту вкладку, и у нас откроется панель с элементами, откуда мы можем с помощью мыши перенести на форму любой элемент:

    Найдем среди элементов кнопку и, захватив ее указателем мыши, перенесем на форму:

    Это визуальная часть. Теперь приступим к самому программированию. Добавим простейший код на языке C#, который бы выводил сообщение по нажатию кнопки. Для этого мы должны перейти в файл кода, который связан с этой формой. Если у нас не открыт файл кода, мы можем нажать на форму правой кнопкой мыши и в появившемся меню выбрать View Code (Посмотреть файл кода):

    Однако воспользуемся другим способом, чтобы не писать много лишнего кода. Наведем указатель мыши на кнопку и щелкнем по ней двойным щелчком. Мы автоматически попадаем в файл кода Form1.cs , который выглядит так:

    Using System;

    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;

    using System.Windows.Forms;

    Namespace HelloApp
    {

    {
    public Form1()
    {
    InitializeComponent();
    }


    {

    }
    }
    }
    Добавим вывод сообщения по нажатию кнопки, изменив код следующим образом:

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    }

    Private void button1_Click(object sender, EventArgs e)
    {
    MessageBox.Show("Привет");
    }
    }
    }
    Запуск приложения
    Чтобы запустить приложение в режиме отладки, нажмем на клавишу F5 или на зеленую стрелочку на панели Visual Studio. После этого запустится наша форма с одинокой кнопкой. И если мы нажмем на кнопку на форме, то нам будет отображено сообщение с приветствием.

    После запуска приложения студия компилирует его в файл с расширением exe. Найти данный файл можно, зайдя в папку проекта и далее в каталог bin/Debug или bin/Release

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

  2. https://metanit.com/sharp/windowsforms/2.1.php

    Работа с формами
    Основы форм

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

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

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

    Несмотря на то, что мы видим только форму, но стартовой точкой входа в графическое приложение является класс Program, расположенный в файле Program.cs :

    Using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    static class Program
    {
    static void Main()
    {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
    }
    }
    }
    Сначала программой запускается данный класс, затем с помощью выражения Application.Run(new Form1()) он запускает форму Form1. Если вдруг мы захотим изменить стартовую форму в приложении на какую-нибудь другую, то нам надо изменить в этом выражении Form1 на соответствующий класс формы.

    Сама форма сложна по содержанию. Она делится на ряд компонентов. Так, в структуре проекта есть файл Form1.Designer.cs , который выглядит примерно так:

    Namespace HelloApp
    {
    partial class Form1
    {
    ///


    /// Required designer variable.
    ///

    ///


    /// Clean up any resources being used.
    ///

    /// true if managed resources should be disposed; otherwise, false.

    {

    {
    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.
    ///


    {
    this.SuspendLayout();
    // Form1



    this.Name = "Form1";
    this.Text = "Привет мир!";
    this.ResumeLayout(false);

    }
    }
    Здесь объявляется частичный класс формы Form1, которая имеет два метода: Dispose(), который выполняет роль деструктора объекта, и InitializeComponent(), который устанавливает начальные значения свойств формы.

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

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

    Еще один файл - Form1.resx - хранит ресурсы формы. Как правило, ресурсы используются для создания однообразных форм сразу для нескольких языковых культур.

    И более важный файл - Form1.cs , который в структуре проекта называется просто Form1, содержит код или программную логику формы:

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    }
    }
    }
    По умолчанию здесь есть только конструктор формы, в котором просто вызывается метод InitializeComponent(), объявленный в файле дизайнера Form1.Designer.cs . Именно с этим файлом мы и будем больше работать.

  3. https://metanit.com/sharp/windowsforms/2.2.php

    Основные свойства форм

    С помощью специального окна Properties (Свойства) справа Visual Studio предоставляет нам удобный интерфейс для управления свойствами элемента:

    Большинство этих свойств оказывает влияние на визуальное отображение формы. Пробежимся по основным свойствам:

    • Name: устанавливает имя формы - точнее имя класса, который наследуется от класса Form
    • BackColor: указывает на фоновый цвет формы. Щелкнув на это свойство, мы сможем выбрать тот цвет, который нам подходит из списка предложенных цветов или цветовой палитры
    • BackgroundImage: указывает на фоновое изображение формы
    • BackgroundImageLayout: определяет, как изображение, заданное в свойстве BackgroundImage, будет располагаться на форме.
    • ControlBox: указывает, отображается ли меню формы. В данном случае под меню понимается меню самого верхнего уровня, где находятся иконка приложения, заголовок формы, а также кнопки минимизации формы и крестик. Если данное свойство имеет значение false, то мы не увидим ни иконку, ни крестика, с помощью которого обычно закрывается форма
    • Cursor: определяет тип курсора, который используется на форме
    • Enabled: если данное свойство имеет значение false, то она не сможет получать ввод от пользователя, то есть мы не сможем нажать на кнопки, ввести текст в текстовые поля и т.д.
    • Font: задает шрифт для всей формы и всех помещенных на нее элементов управления. Однако, задав у элементов формы свой шрифт, мы можем тем самым переопределить его
    • ForeColor: цвет шрифта на форме
    • FormBorderStyle: указывает, как будет отображаться граница формы и строка заголовка. Устанавливая данное свойство в None можно создавать внешний вид приложения произвольной формы
    • HelpButton: указывает, отображается ли кнопка справки формы
    • Icon: задает иконку формы
    • Location: определяет положение по отношению к верхнему левому углу экрана, если для свойства StartPosition установлено значение Manual
    • MaximizeBox: указывает, будет ли доступна кнопка максимизации окна в заголовке формы
    • MinimizeBox: указывает, будет ли доступна кнопка минимизации окна
    • MaximumSize: задает максимальный размер формы
    • MinimumSize: задает минимальный размер формы
    • Opacity: задает прозрачность формы
    • Size: определяет начальный размер формы
    • StartPosition: указывает на начальную позицию, с которой форма появляется на экране
    • Text: определяет заголовок формы
    • TopMost: если данное свойство имеет значение true, то форма всегда будет находиться поверх других окон
    • Visible: видима ли форма, если мы хотим скрыть форму от пользователя, то можем задать данному свойству значение false
    • WindowState: указывает, в каком состоянии форма будет находиться при запуске: в нормальном, максимизированном или минимизированном
    Программная настройка свойств
    С помощью значений свойств в окне Свойства мы можем изменить по своему усмотрению внешний вид формы, но все то же самое мы можем сделать динамически в коде. Перейдем к коду, для этого нажмем правой кнопкой мыши на форме и выберем в появившемся контекстном меню View Code (Просмотр кода). Перед нами открывается файл кода Form1.cs . Изменим его следующим образом:

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    Text = "Hello World!";
    this.BackColor = Color.Aquamarine;
    this.Width = 250;
    this.Height = 250;
    }
    }
    }

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

    Установка размеров формы
    Для установки размеров формы можно использовать такие свойства как Width/Height или Size. Width/Height принимают числовые значения, как в вышеприведенном примере. При установке размеров через свойство Size, нам надо присвоить свойству объект типа Size:

    This.Size = new Size(200,150);
    Объект Size в свою очередь принимает в конструкторе числовые значения для установки ширины и высоты.

    Начальное расположение формы
    Начальное расположение формы устанавливается с помощью свойства StartPosition, которое может принимать одно из следующих значений:

    • Manual: Положение формы определяется свойством Location
    • CenterScreen: Положение формы в центре экрана
    • WindowsDefaultLocation: Позиция формы на экране задается системой Windows, а размер определяется свойством Size
    • WindowsDefaultBounds: Начальная позиция и размер формы на экране задается системой Windows
    • CenterParent: Положение формы устанавливается в центре родительского окна
    Все эти значения содержатся в перечислении FormStartPosition, поэтому, чтобы, например, установить форму в центре экрана, нам надо прописать так:

    This.StartPosition = FormStartPosition.CenterScreen;
    Фон и цвета формы
    Чтобы установить цвет как фона формы, так и шрифта, нам надо использовать цветовое значение, хранящееся в структуре Color:

    This.BackColor = Color.Aquamarine;
    this.ForeColor = Color.Red;
    Кроме того, мы можем в качестве фона задать изображение в свойстве BackgroundImage, выбрав его в окне свойств или в коде, указав путь к изображению:

    This.BackgroundImage = Image.FromFile("C:\Users\Eugene\Pictures\3332.jpg");
    Чтобы должным образом настроить нужное нам отображение фоновой картинки, надо использовать свойство BackgroundImageLayout, которое может принимать одно из следующих значений:

    • None: Изображение помещается в верхнем левом углу формы и сохраняет свои первоначальные значения
    • Tile: Изображение располагается на форме в виде мозаики
    • Center: Изображение располагается по центру формы
    • Stretch: Изображение растягивается до размеров формы без сохранения пропорций
    • Zoom: Изображение растягивается до размеров формы с сохранением пропорций
    Например, расположим форму по центру экрана:

    This.StartPosition = FormStartPosition.CenterScreen;

  4. https://metanit.com/sharp/windowsforms/2.3.php

    Добавление форм. Взаимодействие между формами

    Чтобы добавить еще одну форму в проект, нажмем на имя проекта в окне Solution Explorer (Обозреватель решений) правой кнопкой мыши и выберем Add(Добавить)->Windows Form...

    Дадим новой форме какое-нибудь имя, например, Form2.cs :

    Итак, у нас в проект была добавлена вторая форма. Теперь попробуем осуществить взаимодействие между двумя формами. Допустим, первая форма по нажатию на кнопку будет вызывать вторую форму. Во-первых, добавим на первую форму Form1 кнопку и двойным щелчком по кнопке перейдем в файл кода. Итак, мы попадем в обработчик события нажатия кнопки, который создается по умолчанию после двойного щелчка по кнопке:


    {

    }
    Теперь добавим в него код вызова второй формы. У нас вторая форма называется Form2, поэтому сначала мы создаем объект данного класса, а потом для его отображения на экране вызываем метод Show:

    Private void button1_Click(object sender, EventArgs e)
    {
    Form2 newForm = new Form2();
    newForm.Show();
    }
    Теперь сделаем наоборот - чтобы вторая форма воздействовала на первую. Пока вторая форма не знает о существовании первой. Чтобы это исправить, надо второй форме как-то передать сведения о первой форме. Для этого воспользуемся передачей ссылки на форму в конструкторе.

    Итак перейдем ко второй форме и перейдем к ее коду - нажмем правой кнопкой мыши на форму и выберем View Code (Просмотр кода). Пока он пустой и содержит только конструктор. Поскольку C# поддерживает переопределение методов, то мы можем создать несколько методов и конструкторов с разными параметрами и в зависимости от ситуации вызывать один из них. Итак, изменим файл кода второй формы на следующий:

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form2: Form
    {
    public Form2()
    {
    InitializeComponent();
    }

    Public Form2(Form1 f)
    {
    InitializeComponent();
    f.BackColor = Color.Yellow;
    }
    }
    }
    Фактически мы только добавили здесь новый конструктор public Form2(Form1 f), в котором мы получаем первую форму и устанавливаем ее фон в желтый цвет. Теперь перейдем к коду первой формы, где мы вызывали вторую форму и изменим его на следующий:

    Private void button1_Click(object sender, EventArgs e)
    {
    Form2 newForm = new Form2(this);
    newForm.Show();
    }
    Поскольку в данном случае ключевое слово this представляет ссылку на текущий объект - объект Form1, то при создании второй формы она будет получать ее (ссылку) и через нее управлять первой формой.

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

    Мы можем также создавать объекты и текущей формы:

    Private void button1_Click(object sender, EventArgs e)
    {
    Form1 newForm1 = new Form1();
    newForm1.Show();
    Form2 newForm2 = new Form2(newForm1);
    newForm2.Show();
    }
    При работе с несколькими формами надо учитывать, что одна из них является главной - которая запускается первой в файле Program.cs. Если у нас одновременно открыта куча форм, то при закрытии главной закрывается все приложение и вместе с ним все остальные формы.

  5. https://metanit.com/sharp/windowsforms/2.4.php

    События в Windows Forms. События формы

    Для взаимодействия с пользователем в Windows Forms используется механизм событий. События в Windows Forms представляют стандартные события на C#, только применяемые к визуальным компонентам и подчиняются тем же правилам, что события в C#. Но создание обработчиков событий в Windows Forms все же имеет некоторые особенности.

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

    Чтобы добавить обработчик, можно просто два раза нажать по пустому полю рядом с названием события, и после этого Visual Studio автоматически сгенерирует обработчик события. Например, нажмем для создания обработчика для события Load:

    И в этом поле отобразится название метода обработчика события Load. По умолчанию он называется Form1_Load.

    Если мы перейдем в файл кода формы Form1.cs, то увидим автосгенерированный метод Form1_Load:


    {
    public Form1()
    {
    InitializeComponent();
    }


    {

    }
    }
    И при каждой загрузке формы будет срабатывать код в обработчике Form1_Load.

    Как правило, большинство обработчиков различных визуальных компонентов имеют два параметра: sender - объект, инициировавший событие, и аргумент, хранящий информацию о событии (в данном случае EventArgs e).

    Но это только обработчик. Добавление же обработчика, созданного таким образом, производится в файле Form1.Designer.cs:

    Namespace HelloApp
    {
    partial class Form1
    {
    private System.ComponentModel.IContainer components = null;

    Protected override void Dispose(bool disposing)
    {
    if (disposing && (components != null))
    {
    components.Dispose();
    }
    base.Dispose(disposing);
    }
    private void InitializeComponent()
    {
    this.SuspendLayout();

    This.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(284, 261);
    this.Name = "Form1";
    // добавление обработчика
    this.Load += new System.EventHandler(this.Form1_Load);
    this.ResumeLayout(false);
    }
    }
    }
    Для добавления обработчика используется стандартный синтаксис C#: this.Load += new System.EventHandler(this.Form1_Load)

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

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

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    this.Load += LoadEvent;
    }

    Private void Form1_Load(object sender, EventArgs e)
    {
    }

    Private void LoadEvent(object sender, EventArgs e)
    {

    }
    }
    }
    Кроме ранее созданного обработчика Form1_Load здесь также добавлен другой обработчик загрузки формы: this.Load += LoadEvent;, который устанавливает в качестве фона желтый цвет.

  6. https://metanit.com/sharp/windowsforms/2.5.php

    Создание непрямоугольных форм. Закрытие формы

    По умолчанию все формы в Windows Forms являются прямоугольными. Однако мы можем создавать и непрямоугольные произвольные формы. Для этого используется свойство Region. В качестве значения оно принимает объект одноименного класса Region.

    При создании непрямоугольных форм, как правило, не используются границы формы, так как границы задаются этим объектом Region. Чтобы убрать границы формы, надо присвоить у формы свойству FormBorderStyle значение None.

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

    Итак, перейдем к коду формы и изменим его следующим образом:

    Using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    Namespace HelloApp
    {
    public partial class Form1: Form
    {
    Point moveStart; // точка для перемещения

    Public Form1()
    {
    InitializeComponent();
    this.FormBorderStyle = FormBorderStyle.None;
    this.BackColor = Color.Yellow;
    button1.Text = "Закрыть";
    }

    Private void button1_Click(object sender, EventArgs e)
    {
    this.Close();
    }

    Private void Form1_Load(object sender, EventArgs e)
    {
    System.Drawing.Drawing2D.GraphicsPath myPath = new System.Drawing.Drawing2D.GraphicsPath();
    // создаем эллипс с высотой и шириной формы
    myPath.AddEllipse(0, 0, this.Width, this.Height);
    // создаем с помощью элипса ту область формы, которую мы хотим видеть
    Region myRegion = new Region(myPath);
    // устанавливаем видимую область
    this.Region = myRegion;
    }

    Private void Form1_MouseDown(object sender, MouseEventArgs e)
    {

    if (e.Button == MouseButtons.Left)
    {
    moveStart = new Point(e.X, e.Y);
    }
    }

    Private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
    // если нажата левая кнопка мыши
    if ((e.Button & MouseButtons.Left) != 0)
    {
    // получаем новую точку положения формы
    Point deltaPos = new Point(e.X - moveStart.X, e.Y - moveStart.Y);
    // устанавливаем положение формы
    this.Location = new Point(this.Location.X + deltaPos.X,
    this.Location.Y + deltaPos.Y);
    }
    }
    }
    }

    Создание области формы происходит в обработчике события Form1_Load. Для создания области используется графический путь - объект класса System.Drawing.Drawing2D.GraphicsPath, в который добавляется эллипс. Графический путь позволяет создать фигуру любой формы, поэтому, если мы захотим форму в виде морской звезды, то нам просто надо должным образом настроить используемый графический путь.

    Для закрытия формы в обработчике события нажатия кнопки button1_Click форма закрывается программным образом: this.Close()

    Для перемещения формы обрабатываются два события формы - событие нажатия кнопки мыши и событие перемещения указателя мыши.

  7. https://metanit.com/sharp/windowsforms/3.1.php

    Контейнеры в Windows Forms

    Для организации элементов управления в связанные группы существуют специальные элементы - контейнеры. Например, Panel, FlowLayoutPanel, SplitContainer, GroupBox. Ту же форму также можно отнести к контейнерам. Использование контейнеров облегчает управление элементами, а также придает форме определенный визуальный стиль.

    Все контейнеры имеют свойство Controls, которое содержит все элементы данного контейнера. Когда мы переносим какой-нибудь элемент с панели инструментов на контейнер, например, кнопку, она автоматически добавляется в данную коллекцию данного контейнера. Либо мы также можем добавить элемент управления динамически с помощью кода в эту же коллекцию.

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

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

    Для динамического добавления элементов создадим обработчик события загрузки формы в файле кода:


    {
    }
    Теперь добавим в него код добавления кнопки на форму:

    Private void Form1_Load(object sender, EventArgs e)
    {


    helloButton.ForeColor = Color.DarkGray;
    helloButton.Location = new Point(10, 10);

    this.Controls.Add(helloButton);
    }
    Сначала мы создаем кнопку и устанавливаем ее свойства. Затем, используя метод Controls.Add мы добавляем ее в коллекцию элементов формы. Если бы мы это не сделали, мы бы кнопку не увидели, поскольку в этом случае для нашей формы ее просто не существовало бы.

    С помощью метода Controls.Remove() можно удалить ранее добавленный элемент с формы:

    This.Controls.Remove(helloButton);

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

  8. https://metanit.com/sharp/windowsforms/3.2.php

    Элементы GroupBox, Panel и FlowLayoutPanel

    GroupBox представляет собой специальный контейнер, который ограничен от остальной формы границей. Он имеет заголовок, который устанавливается через свойство Text. Чтобы сделать GroupBox без заголовка, в качестве значения свойства Text просто устанавливается пустая строка.

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

    Элемент Panel представляет панель и также, как и GroupBox, объединяет элементы в группы. Она может визуально сливаться с остальной формой, если она имеет то же значение цвета фона в свойстве BackColor, что и форма. Чтобы ее выделить можно кроме цвета указать для элемента границы с помощью свойства BorderStyle, которое по умолчанию имеет значение None, то есть отсутствие границ.

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

    Также, как и форма, GroupBox и Panel имеют коллекции элементов, и мы также можем динамически добавлять в эти контейнеры элементы. Например, на форме есть элемент GroupBox, который имеет имя groupBox1:

    Private void Form1_Load(object sender, EventArgs e)
    {
    Button helloButton = new Button();
    helloButton.BackColor = Color.LightGray;
    helloButton.ForeColor = Color.Red;
    helloButton.Location = new Point(30, 30);
    helloButton.Text = "Привет";
    groupBox1.Controls.Add(helloButton);
    }
    Для указания расположения элемента в контейнере мы используем структуру Point: new Point(30, 30);, которой в конструкторе передаем размещение по осям Х и Y. Эти координаты устанавливаются относительно левого верхнего угла контейнера - то есть в данном случае элемента GroupBox

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

    This.Controls.Remove(groupBox1);
    FlowLayoutPanel
    Элемент FlowLayoutPanel является унаследован от класса Panel, и поэтому наследует все его свойства. Однако при этом добавляя дополнительную функциональность. Так, этот элемент позволяет изменять позиционирование и компоновку дочерних элементов при изменении размеров формы во время выполнения программы.

    Свойство элемента FlowDirection позволяет задать направление, в котором направлены дочерние элементы. По умолчанию имеет значение LeftToRight - то есть элементы будут располагаться начиная от левого верхнего края. Следующие элементы будут идти вправо. Это свойство также может принимать следующие значения:

    • RightToLeft - элементы располагаются от правого верхнего угла в левую сторону
    • TopDown - элементы располагаются от левого верхнего угла и идут вниз
    • BottomUp - элементы располагаются от левого нижнего угла и идут вверх

    При расположении элементов важную роль играет свойство WrapContents. По умолчанию оно имеет значение True. Это позволяет переносить элементы, которые не умещаются в FlowLayoutPanel, на новую строку или в новый столбец. Если оно имеет значение False, то элементы не переносятся, а к контейнеру просто добавляются полосы прокрутки, если свойство AutoScroll равно true.
  9. https://metanit.com/sharp/windowsforms/3.3.php

    TableLayoutPanel

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

    Чтобы установить нужное число строки столбцов таблицы, мы можем использовать свойства Rows и Columns соответственно. Выбрав один из этих пунктов в окне Properties (Свойства), нам отобразится следующее окно для настройки столбцов и строк:

    В поле Size Type мы можем указать размер столбцов / строк. Нам доступны три возможные варианта:

    • Absolute: задается абсолютный размер для строк или столбцов в пикселях
    • Percent: задается относительный размер в процентах. Если нам надо создать резиновый дизайн формы, чтобы ее строки и столбцы, а также элементы управления в ячейках таблицы автоматически масштабировались при изменении размеров формы, то нам нужно использовать именно эту опцию
    • AutoSize: высота строк и ширина столбцов задается автоматически в зависимости от размера самой большой в строке или столбце ячейки
    Также мы можем комбинировать эти значения, например, один столбец может быть фиксированным с абсолютной шириной, а остальные столбцы могут иметь ширину в процентах.

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

    Итак, например, у меня имеется три столбца и три строки размер у которых одинаков - 33.33%. В каждую ячейку таблицы добавлена кнопка, у которой установлено свойство Dock=Fill.

    Если я изменю размеры формы, то автоматически масштабируются и строки и столбцы вместе с заключенными в них кнопками:

    Что довольно удобно для создания масштабируемых интерфейсов.

    В коде динамически мы можем изменять значения столбцов и строк. Причем все столбцы представлены типом ColumnStyle, а строки - типом RowStyle:

    TableLayoutPanel1.RowStyles.SizeType = SizeType.Percent;
    tableLayoutPanel1.RowStyles.Height = 40;

    TableLayoutPanel1.ColumnStyles.SizeType = SizeType.Absolute;
    tableLayoutPanel1.ColumnStyles.Width = 50;
    Для установки размера в ColumnStyle и RowStyle определено свойство SizeType, которое принимает одно из значений одноименного перечисления SizeType

    Добавление элемента в контейнер TableLayoutPanel имеет свои особенности. Мы можем добавить его как в следующую свободную ячейку или можем явным образом указать ячейку таблицы:

    Button saveButton = new Button();
    // добавляем кнопку в следующую свободную ячейку
    tableLayoutPanel1.Controls.Add(saveButton);
    // добавляем кнопку в ячейку (2,2)
    tableLayoutPanel1.Controls.Add(saveButton, 2, 2);

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

  10. https://metanit.com/sharp/windowsforms/3.4.php

    Размеры элементов и их позиционирование в контейнере



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

    Также мы можем установить позицию элемента в коде:

    1
    2
    3
    4
    private void Form1_Load(object sender, EventArgs e)
    {
    button1.Location = new Point(50, 50);
    }
    Установка размеров
    С помощью свойства Size можно задать размеры элемента:

    Дополнительные свойства MaximumSize и MinimumSize позволяют ограничить минимальный и максимальный размеры.

    Установка свойств в коде:

    1
    2
    3
    4
    button1.Size = new Size { Width = 50, Height = 25 };
    // установка свойств по отдельности
    button1.Width = 100;
    button1.Height = 35;
    Свойство Anchor
    Дополнительные возможности по позиционировании элемента позволяет определить свойство Anchor. Это свойство определяет расстояние между одной из сторон элемента и стороной контейнера. И если при работе с контейнером мы будем его растягивать, то вместе с ним будет растягиваться и вложенный элемент.

    По умолчанию у каждого добавляемого элемента это свойство равно Top, Left:

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

    Мы можем задать четыре возможных значения для этого свойства или их комбинацию:

  11. Right
  12. Например, если мы изменим значение этого свойства на противоположное - Bottom, Right, тогда у нас будет неизменным расстояние между правой и нижней стороной элемента и формой.

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

    Чтобы задать это свойство в коде, надо использовать перечисление AnchorStyles:

    1
    2
    3
    button1.Anchor = AnchorStyles.Left;
    // задаем комбинацию значений
    button1.Anchor = AnchorStyles.Left | AnchorStyles.Top;
    Свойство Dock
    Свойство Dock позволяет прикрепить элемент к определенной стороне контейнера. По умолчанию оно имеет значение None, но также позволяет задать еще пять значений:

  • Top: элемент прижимается к верхней границе контейнера
  • Bottom: элемент прижимается к нижней границе контейнера
  • Left: элемент прижимается к левой стороне контейнера
  • Right: элемент прикрепляется к правой стороне контейнера
  • Fill: элемент заполняет все пространство контейнера
  • https://metanit.com/sharp/windowsforms/3.5.php

    Панель вкладок TabControl и SplitContainer



    TabControl

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

    Чтобы настроить вкладки элемента TabControl используем свойство TabPages . При переносе элемента TabControl с панели инструментов на форму по умолчанию создаются две вкладки - tabPage1 и tabPage2. Изменим их отображение с помощью свойства TabPages:

    Нам откроется окно редактирования/добавления и удаления вкладок:

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

    Управление вкладками в коде
    Для добавления новой вкладки нам надо ее создать и добавить в коллекцию tabControl1.TabPages с помощью метода Add:

    //добавление вкладки
    TabPage newTabPage = new TabPage();
    newTabPage.Text = "Континенты";
    tabControl1.TabPages.Add(newTabPage);
    Удаление так же просто:

    // удаление вкладки
    // по индексу
    tabControl1.TabPages.RemoveAt(0);
    // по объекту
    tabControl1.TabPages.Remove(newTabPage);
    Получая в коллекции tabControl1.TabPages нужную вкладку по индексу, мы можем ей легко манипулировать:

    // изменение свойств
    tabControl1.TabPages.Text = "Первая вкладка";
    SplitContainer
    Элемент SplitContainer позволяет создавать две разделенные сплитером панели. Изменяя положение сплитера, можно изменить размеры этих панелей.

    Используя свойство Orientation, можно задать горизонтальное или вертикальное отображение сплитера на форму. В данном случае это свойство принимает значения Horisontal и Vertical соответственно.

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

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

    Чтобы изменить положение сплитера в коде, мы можем управлять свойством SplitterDistance, которое задает положение сплиттера в пикселях от левого или верхнего края элемента SplitContainer. А с помощью свойства SplitterIncrement можно задать шаг, на который будет перемещаться сплиттер при движении его с помощью клавиш-стрелок.

    Чтобы скрыть одну из двух панелей, мы можем установить свойство Panel1Collapsed или Panel2Collapsed в true

  • https://metanit.com/sharp/windowsforms/4.1.php

    Элементы управления

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

    • Anchor: Определяет, как элемент будет растягиваться
    • BackColor: Определяет фоновый цвет элемента
    • BackgroundImage: Определяет фоновое изображение элемента
    • ContextMenu: Контекстное меню, которое открывается при нажатии на элемент правой кнопкой мыши. Задается с помощью элемента ContextMenu
    • Cursor: Представляет, как будет отображаться курсор мыши при наведении на элемент
    • Dock: Задает расположение элемента на форме
    • Enabled: Определяет, будет ли доступен элемент для использования. Если это свойство имеет значение False, то элемент блокируется.
    • Font: Устанавливает шрифт текста для элемента
    • ForeColor: Определяет цвет шрифта
    • Location: Определяет координаты верхнего левого угла элемента управления
    • Name: Имя элемента управления
    • Size: Определяет размер элемента
    • Width: ширина элемента
    • Height: высота элемента
    • TabIndex: Определяет порядок обхода элемента по нажатию на клавишу Tab
    • Tag: Позволяет сохранять значение, ассоциированное с этим элементом управления
    Кнопка
    Наиболее часто используемым элементом управления является кнопка. Обрабатывая событие нажатия кнопки, мы может производить те или иные действия.

    При нажатии на кнопку на форме в редакторе Visual Studio мы по умолчанию попадаем в код обработчика события Click, который будет выполняться при нажатии:

    Private void button1_Click(object sender, EventArgs e)
    {
    MessageBox.Show("Hello World");
    }
    Оформление кнопки
    Чтобы управлять внешним отображением кнопки, можно использовать свойство FlatStyle . Оно может принимать следующие значения:

    • Flat - Кнопка имеет плоский вид
    • Popup - Кнопка приобретает объемный вид при наведении на нее указателя, в иных случаях она имеет плоский вид
    • Standard - Кнопка имеет объемный вид (используется по умолчанию)
    • System - Вид кнопки зависит от операционной системы
    Изображение на кнопке
    Как и для многих элементов управления, для кнопки можно задавать изображение с помощью свойства BackgroundImage. Однако мы можем также управлять размещением текста и изображения на кнопки. Для этого надо использовать свойство TextImageRelation. Оно приобретает следующие значения:
    • Overlay: текст накладывается на изображение
    • ImageAboveText: изображение располагается над текстом
    • TextAboveImage: текст располагается над изображением
    • ImageBeforeText: изображение располагается перед текстом
    • TextBeforeImage: текст располагается перед изображением
    Например, установим для кнопки изображение. Для этого выберем кнопку и в окне Свойств нажмем на поле Image (не путать с BackgroundImage). Нам откроется диалоговое окно установи изображения:

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

    После выбора изображения мы можем установить свойство ImageAlign, которое управляет позиционированием изображения на кнопке:

    Нам доступны 9 вариантов, с помощью которых мы можем прикрепить изображение к определенной стороне кнопки. Оставим здесь значение по умолчанию - MiddleCenter, то есть позиционирование по центру.

    Затем перейдем к свойству TextImageRelation и установим для него значение ImageBeforeText. В итоге мы получим кнопку, где сразу после изображения идет надпись на кнопке:

    Клавиши быстрого доступа
    При работе с формами при использовании клавиатуры очень удобно пользоваться клавишами быстрого доступа. При нажатии на клавиатуре комбинации клавиш At+некоторый символ, будет вызываться определенная кнопка. Например, зададим для некоторой кнопки свойство Text равное &Аватар. Первый знак - амперсанд - определяет ту букву, которая будет подчеркнута. В данном случае надпись будет выглядеть как Аватар. И теперь чтобы вызвать событие Click, нам достаточно нажать на комбинацию клавиш Alt+А.

    Кнопки по умолчанию
    Форма, на которой размещаются все элементы управления, имеет свойства, позволяющие назначать кнопку по умолчанию и кнопку отмены.

    Так, свойство формы AcceptButton позволяет назначать кнопку по умолчанию, которая будет срабатывать по нажатию на клавишу Enter.

    Аналогично работает свойство формы CancelButton, которое назначает кнопку отмены. Назначив такую кнопку, мы можем вызвать ее нажатие, нажав на клавишу Esc.

  • https://metanit.com/sharp/windowsforms/4.2.php

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

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

    • Свойство ActiveLinkColor задает цвет ссылки при нажатии
    • Свойство LinkColor задает цвет ссылки до нажатия, по которой еще не было переходов
    • Свойство VisitedLinkColor задает цвет ссылки, по которой уже были переходы
    Кроме цвета ссылки для данного элемента мы можем задать свойство LinkBehavior, которое управляет поведением ссылки. Это свойство принимает четыре возможных значения:
    • SystemDefault: для ссылки устанавливаются системные настройки
    • AlwaysUnderline: ссылка всегда подчеркивается
    • HoverUnderline: ссылка подчеркивается только при наведении на нее курсора мыши
    • NeverUnderline: ссылка никогда не подчеркивается
    По умолчанию весь текст на данном элементе считается ссылкой. Однако с помощью свойства LinkArea мы можем изменить область ссылки. Например, мы не хотим включать в ссылку первые шесть символов. Для этого задаем подсвойство Start:

    Чтобы выполнить переход по ссылке по нажатию на нее, надо дополнительно написать код. Данный код должен обрабатывать событие LinkClicked, которое есть у элемента LinkLabel. Например, пусть у нас на форме есть элемент ссылки называется linkLabel1 и который содержит некоторую ссылку:

    Public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    // задаем обработчик события
    linkLabel1.LinkClicked += linkLabel1_LinkClicked;
    }

    Private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
    {
    System.Diagnostics.Process.Start("http://metanit.com ");
    }
    }
    Метод System.Diagnostics.Process.Start() откроет данную ссылку в веб-браузере, который установлен в системе браузером по умолчанию.

  • https://metanit.com/sharp/windowsforms/4.3.php

    Текстовое поле TextBox

    Для ввода и редактирования текста предназначены текстовые поля - элемент TextBox. Так же как и у элемента Label текст элемента TextBox можно установить или получить с помощью свойства Text.

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

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

    • None: без прокруток (по умолчанию)
    • Horizontal: создает горизонтальную прокрутку при длине строки, превышающей ширину текстового поля
    • Vertical: создает вертикальную прокрутку, если строки не помещаются в текстовом поле
    • Both: создает вертикальную и горизонтальную прокрутку

    Автозаполнение текстового поля
    Элемент TextBox обладает достаточными возможностями для создания автозаполняемого поля. Для этого нам надо привязать свойство AutoCompleteCustomSource элемента TextBox к некоторой коллекции, из которой берутся данные для заполнения поля.

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

    Public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();
    AutoCompleteStringCollection source = new AutoCompleteStringCollection()
    {
    "Кузнецов",
    "Иванов",
    "Петров",
    "Кустов"
    };
    textBox1.AutoCompleteCustomSource = source;
    textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
    }
    }

    Режим автодополнения, представленный свойством AutoCompleteMode, имеет несколько возможных значений:

    • None: отсутствие автодополнения
    • Suggest: предлагает варианты для ввода, но не дополняет
    • Append: дополняет введенное значение до строки из списка, но не предлагает варианты для выбора
    • SuggestAppend: одновременно и предлагает варианты для автодополнения, и дополняет введенное пользователем значение
    Перенос по словам
    Чтобы текст в элементе TextBox переносился по словам, надо установить свойство WordWrap равным true. То есть если одно слово не умещается на строке, то но переносится на следующую. Данное свойство будет работать только для многострочных текстовых полей.

    Ввод пароля
    Также данный элемент имеет свойства, которые позволяют сделать из него поле для ввода пароля. Так, для этого надо использовать PasswordChar и UseSystemPasswordChar.

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

    Свойство UseSystemPasswordChar имеет похожее действие. Если мы установим его значение в true, то вместо введенных символов в текстовом поле будет отображаться знак пароля, принятый в системе, например, точка.

    Событие TextChanged
    Из всех событий элемента TextBox следует отметить событие TextChanged, которое срабатывает при изменении текста в элементе. Например, поместим на форму кроме текстового поля метку и сделаем так, чтобы при изменении текста в текстовом поле также менялся текст на метке:

    Public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();

    TextBox1.TextChanged += textBox1_TextChanged;
    }

    Private void textBox1_TextChanged(object sender, EventArgs e)
    {
    label1.Text = textBox1.Text;
    }
    }

  • https://metanit.com/sharp/windowsforms/4.4.php

    Элемент MaskedTextBox

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

    Чтобы контролировать вводимые в поле символы, надо задать маску. Для задания маски можно применять следующие символы:

    • 0: Позволяет вводить только цифры
    • 9: Позволяет вводить цифры и пробелы
    • #: Позволяет вводить цифры, пробелы и знаки "+" и "-"
    • L: Позволяет вводить только буквенные символы
    • ?: Позволяет вводить дополнительные необязательные буквенные символы
    • A: Позволяет вводить буквенные и цифровые символы
    • .: Задает позицию разделителя целой и дробной части
    • ,: Используется для разделения разрядов в целой части числа
    • :: Используется в временных промежутках - разделяет часы, минуты и секунды
    • /: Используется для разделения дат
    • $: Используется в качестве символа валюты
    Чтобы задать маску, надо установить свойство Mask элемента. Найдя это свойство в окне свойств(Porperties), нажмем на него и нам отобразится окно для задания одного из стандартных шаблонов маски. В частности мы можем выбрать Phone number (Телефонный номер), который подразумевает ввод в текстовое поле только телефонного номера:

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

    Теперь сделаем свою маску. Например, создадим маску для ввода инициалов имени и отчества и фамилий ограниченной длины в текстовое поле. Для этого присвоим свойству Mask значение L.L.L?????????. Тогда ввод в текстовое поле будет выглядеть следующим образом:

    Данный элемент также представляет нам ряд свойств, которые можно использовать для управления вводом. Так, свойство BeepOnError при установке значения true подает звуковой сигнал при введении некорректного символа.

    Свойство HidePromptOnLeave при установке в true при потери текстовым полем фокуса скрывает, указанные в PromptChar

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

    Свойство AsciiOnly при значении true позволяет вводить только asci-символы, то есть символы из диапазона A-Z и a-z.

  • https://metanit.com/sharp/windowsforms/4.5.php

    Элементы Radiobutton и CheckBox

    CheckBox
    Элемент CheckBox или флажок предназначен для установки одного из двух значений: отмечен или не отмечен. Чтобы отметить флажок, надо установить у его свойства Checked значение true.

    Кроме свойства Checked у элемента CheckBox имеется свойство CheckState, которое позволяет задать для флажка одно из трех состояний - Checked (отмечен), Indeterminate (флажок не определен - отмечен, но находится в неактивном состоянии) и Unchecked (не отмечен)

    Также следует отметить свойство AutoCheck - если оно имеет значение false, то мы не можем изменять состояние флажка. По умолчанию оно имеет значение true.

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

    Private void checkBox_CheckedChanged(object sender, EventArgs e)
    {
    CheckBox checkBox = (CheckBox)sender; // приводим отправителя к элементу типа CheckBox
    if (checkBox.Checked == true)
    {
    MessageBox.Show("Флажок " + checkBox.Text + " теперь отмечен");
    }
    else
    {
    MessageBox.Show("Флажок " + checkBox.Text + " теперь не отмечен");
    }
    }
    Radiobutton
    На элемент CheckBox похож элемент RadioButton или переключатель. Переключатели располагаются группами, и включение одного переключателя означает отключение всех остальных.

    Чтобы установить у переключателя включенное состояние, надо присвоить его свойству Checked значение true.

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

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

    Private void radioButton_CheckedChanged(object sender, EventArgs e)
    {
    // приводим отправителя к элементу типа RadioButton
    RadioButton radioButton = (RadioButton)sender;
    if (radioButton.Checked)
    {
    MessageBox.Show("Вы выбрали " + radioButton.Text);
    }
    }

  • https://metanit.com/sharp/windowsforms/4.6.php

    ListBox

    Элемент ListBox представляет собой простой список. Ключевым свойством этого элемента является свойство Items, которое как раз и хранит набор всех элементов списка.

    Элементы в список могут добавляться как во время разработки, так и программным способом. В Visual Studio в окне Properties (Свойства) для элемента ListBox мы можем найти свойство Items. После двойного щелчка на свойство нам отобразится окно для добавления элементов в список:

    В пустое поле мы вводим по одному элементу списка - по одному на каждой строке. После этого все добавленные нами элементы окажутся в списке, и мы сможем ими управлять:

    Программное управление элементами в ListBox
    Добавление элементов
    Итак, все элементы списка входят в свойство Items, которое представляет собой коллекцию. Для добавления нового элемента в эту коллекцию, а значит и в список, надо использовать метод Add, например: listBox1.Items.Add("Новый элемент");. При использовании этого метода каждый добавляемый элемент добавляется в конец списка.

    Можно добавить сразу несколько элементов, например, массив. Для этого используется метод AddRange:

    String countries = { "Бразилия", "Аргентина", "Чили", "Уругвай", "Колумбия" };
    listBox1.Items.AddRange(countries);
    Вставка элементов
    В отличие от простого добавления вставка производится по определенному индексу списка с помощью метода Insert:

    ListBox1.Items.Insert(1, "Парагвай");
    В данном случае вставляем элемент на вторую позицию в списке, так как отсчет позиций начинается с нуля.

    Удаление элементов
    Для удаления элемента по его тексту используется метод Remove:

    ListBox1.Items.Remove("Чили");
    Чтобы удалить элемент по его индексу в списке, используется метод RemoveAt:

    ListBox1.Items.RemoveAt(1);
    Кроме того, можно очистить сразу весь список, применив метод Clear:

    ListBox1.Items.Clear();
    Доступ к элементам списка
    Используя индекс элемента, можно сам элемент в списке. Например, получим первый элемент списка:

    String firstElement = listBox1.Items;
    Метод Count позволяет определить количество элементов в списке:

    Int number = listBox1.Items.Count();
    Выделение элементов списка
    При выделении элементов списка мы можем ими управлять как через индекс, так и через сам выделенный элемент. Получить выделенные элементы можно с помощью следующих свойств элемента ListBox:

    • SelectedIndex: возвращает или устанавливает номер выделенного элемента списка. Если выделенные элементы отсутствуют, тогда свойство имеет значение -1
    • SelectedIndices: возвращает или устанавливает коллекцию выделенных элементов в виде набора их индексов
    • SelectedItem : возвращает или устанавливает текст выделенного элемента
    • SelectedItems: возвращает или устанавливает выделенные элементы в виде коллекции
    По умолчанию список поддерживает выделение одного элемента. Чтобы добавить возможность выделения нескольких элементов, надо установить у его свойства SelectionMode значение MultiSimple.

    Чтобы выделить элемент програмно, надо применить метод SetSelected(int index, bool value), где index - номер выделенного элемента. Если второй параметр - value имеет значение true, то элемент по указанному индексу выделяется, если false, то выделение наоборот скрывается:

    ListBox1.SetSelected(2, true); // будет выделен третий элемент
    Чтобы снять выделение со всех выделенных элементов, используется метод ClearSelected.

    Событие SelectedIndexChanged
    Из всех событий элемента ListBox надо отметить в первую очередь событие SelectedIndexChanged, которое возникает при изменении выделенного элемента:

    Public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();

    String countries = { "Бразилия", "Аргентина", "Чили", "Уругвай", "Колумбия" };
    listBox1.Items.AddRange(countries);
    listBox1.SelectedIndexChanged += listBox1_SelectedIndexChanged;
    }

    Void listBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
    string selectedCountry = listBox1.SelectedItem.ToString();
    MessageBox.Show(selectedCountry);
    }
    }
    В данном случае по выбору элемента списка будет отображаться сообщение с выделенным элементом.

    comboBox1.Items.AddRange(new string { "Уругвай", "Эквадор" });
    // добавляем один элемент на определенную позицию
    comboBox1.Items.Insert(1, "Боливия");
    При добавлении с помощью методов Add / AddRange все новые элементы помещаются в конец списка. Однако если мы зададим у ComboBox свойство Sorted равным true, тогда при добавлении будет автоматически производиться сортировка.

    Удаление элементов:

    // удаляем один элемент
    comboBox1.Items.Remove("Аргентина");
    // удаляем элемент по индексу
    comboBox1.Items.RemoveAt(1);
    // удаляем все элементы
    comboBox1.Items.Clear();
    Мы можем получить элемент по индексу и производить с ним разные действия. Например, изменить его:

    ComboBox1.Items = "Парагвай";
    Настройка оформления ComboBox
    С помощью ряда свойств можно настроить стиль оформления компонента. Так, свойство DropDownWidth задает ширину выпадающего списка. С помощью свойства DropDownHeight можно установить высоту выпадающего списка.

    Еще одно свойство MaxDropDownItems позволяет задать число видимых элементов списка - от 1 до 100. По умолчанию это число равно 8.

    Другое свойство DropDownStyle задает стиль ComboBox. Оно может принимать три возможных значения:

    • Dropdown: используется по умолчанию. Мы можем открыть выпадающий список вариантов при вводе значения в текстовое поле или нажав на кнопку со стрелкой в правой части элемента, и нам отобразится собственно выпадающий список, в котором можно выбрать возможный вариант
    • DropdownList: чтобы открыть выпадающий список, надо нажать на кнопку со стрелкой в правой стороне элемента
    • Simple: ComboBox представляет простое текстовое поле, в котором для перехода между элементами мы можем использовать клавиши клавиатуры вверх/вниз

    Событие SelectedIndexChanged
    Наиболее важным событием для ComboBox также является событие SelectedIndexChanged, позволяющее отследить выбор элемента в списке:

    Public partial class Form1: Form
    {
    public Form1()
    {
    InitializeComponent();

    ComboBox1.SelectedIndexChanged += comboBox1_SelectedIndexChanged;
    }

    Void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
    string selectedState = comboBox1.SelectedItem.ToString();
    MessageBox.Show(selectedState);
    }
    }
    Здесь также свойство SelectedItem будет ссылаться на выбранный элемент.

  • Сегодня я хочу рассказать о том, как создать проект Windows Forms на C++ в IDE Visual Studio 2013. Дело в том, что, начиная с VS 2012, в списке проектов, которые можно создать, убрали пункт Приложение Windows Forms. Я сейчас говорю о том, который на C++, создать такой проект на C# можно, выбрав соответствующий пункт в разделе создаваемых проектов. Однако тот факт, что такой проект нельзя выбрать из списка, не говорит о том, что его нельзя создать самому. Именно об этом я и хочу рассказать в этой статье.

    Первое, что потребуется сделать - запустить Visual Studio. Как только VS запустили, нажимаем последовательно Файл > Создать > Проект

    После этого в открывшемся окне будет предложено выбрать тип проекта. Нам необходимо выбрать в разделе Visual C++ подраздел CLR и выбрать пункт Пустой проект CLR.

    Когда проект будет создан, в обозревателе решений кликаем правой кнопкой мыши по созданному проекту. В открывшемся контекстном меню последовательно выбираем Добавить > Создать элемент и в открывшемся меню в разделе UI выбираем Форма Windows Forms

    Когда форма будет добавлена, в обозревателе решений выбираем файл MyForm.cpp. Перед вами откроется новая вкладка с единственной строчкой кода:

    #include "MyForm.h"

    В этот файл нам необходимо добавить следующий код:

    Using namespace System; using namespace System::Windows::Forms; void Main(array^ args) { Application::EnableVisualStyles(); Application::SetCompatibleTextRenderingDefault(false); Project1::MyForm form; Application::Run(%form); }

    После этого в свойствах проекта. Выбираем подраздел Система раздела Компоновщик и в строке Подсистема из выпадающего меню выбираем Windows (/SUBSYSTEM:WINDOWS) и нажимаем Применить.

    Не закрывая окно свойств проекта, переходим в подраздел Дополнительно и в строке Точка входа пишем Main и после этого нажимаем клавишу ОК.
    На этом настройки проекта заканчиваются. Для редактирования внешнего вида формы, необходимо перейти во вкладку MyForm.h [Конструктор], кликнув дважды по файлу MyForm.h в обозревателе решений.

    Доброго времени суток!
    Model-View-Presenter - довольно известный шаблон проектирования. С первого взгляда все выглядит просто: есть Модель (Model), которая содержит всю бизнес-логику экрана; Вид/Представление (View), который знает, как отобразить те или иные данные; Представитель (Presenter), который является связующий звеном - реагирует на действия пользователя во View, изменяя Model, и наоборот.
    Сложность начинается, когда количество форм в проекте становится более одной.
    В данной статье рассматривается:
    - немножко теории;
    - общие проблемы реализации MVP (а именно Passive View) под Windows Forms;
    - особенности реализации переходов между формами и передача параметров, модальные окна;
    - использование IoC-контейнера и шаблона Dependency Injection - DI (а именно Сonstructor Injection);
    - некоторые особенности тестирования MVP приложения (с использованием NUnit и NSubstitute);
    - все это будет происходить на примере мини-проекта и постарается быть наглядным.
    В статье затрагивается:
    - применение шаблона Адаптер (Adapter);
    - простенькая реализация шаблона Контроллер приложения (Application Controller).
    Для кого эта статья?
    Главным образом для начинающих разработчиков на Windows Forms, которые слышали, но не пробовали, или пробовали, но не получилось. Хотя уверен, что некоторые приемы применимы и для WPF, и даже для веб-разработки.

    Постановка задачи

    Придумаем простую задачу - реализовать 3 экрана:
    1) экран авторизации;
    2) главный экран;
    3) модальный экран изменения имени пользователя.
    Должно получиться что-то вроде этого:

    Немного теории

    MVP, как и его родитель, MVC (Model-View-Controller) придуман для удобства разделения бизнес-логики от способа ее отображения.

    На просторах интернета можно встретить целое множество реализаций MVP. По способу доставки данных в представление их можно разделить на 3 категории:
    - Passive View: View содержит минимальную логику отображения примитивных данных (строки, числа), остальным занимается Presenter;
    - Presentation Model: во View могут передаваться не только примитивные данные, но и бизнес-объекты;
    - Supervising Controller: View знает о наличии модели и сам забирает из нее данные.

    Далее будет рассматриваться модификация Passive View. Опишем основные черты:
    - интерфейс Представления (IView), который предоставляет некий контракт для отображения данных;
    - Представление - конкретная реализация IView, которая умеет отображать саму себя в конкретном интерфейсе (будь то Windows Forms, WPF или даже консоль) и ничего не знает о том, кто ей управляет. В нашем случае это формы;
    - Модель - предоставляет некоторую бизнес-логику (примеры: доступ к базе данных, репозитории, сервисы). Может быть представлена в виде класса или опять же, интерфейса и реализации;
    - Представитель содержит ссылку на Представление через интерфейс (IView), управляет им, подписывается на его события, производит простую валидацию (проверку) введенных данных; также содержит ссылку на модель или на ее интерфейс, передавая в нее данные из View и запрашивая обновления.

    Типичная реализация Представителя

    public class Presenter { private readonly IView _view; private readonly IService _service; public Presenter(IView view, IService service) { _view = view; _service = service; _view.UserIdChanged += () => UpdateUserInfo(); } private void UpdateUserInfo() { var user = _service.GetUser(_view.UserId); _view.Username = user.Username; _view.Age = user.Age; } }


    Какие плюсы нам дает малая связанность классов (использование интерфейсов, событий)?
    1. Позволяет относительно свободно менять логику любого компонента, не ломая остального.
    2. Большие возможности при unit-тестировании. Поклонники TDD должны быть в восторге.
    Начнем!

    Как организовать проекты?

    Условимся, что решение будет состоять из 4х проектов:
    - DomainModel - содержит сервисы и всевозможные репозитории, одним словом - модель;
    - Presentation - содержит логику приложения, не зависящую от визуального представления, т.е. все Представители, интерфейсы Представлений и остальные базовые классы;
    - UI - Windows Forms приложение, содержит только лишь формы (реализацию интерфейсов Представлений) и логику запуска;
    - Tests - unit-тесты.

    Что писать в Main()?

    Стандартная реализация запуска Windows Forms приложения выглядит так:

    Private static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); // непосредственный запуск формы (представления) }
    Но мы условились, что Представители будут управлять Представлениями, следовательно хотелось бы, чтобы код выглядел как-то так:

    Private static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var presenter = new LoginPresenter(new LoginForm(), new LoginService()); // Dependency Injection presenter.Run(); }
    Попробуем реализовать первый экран:

    Базовые интерфейсы

    // общие методы для всех представлений public interface IView { void Show(); void Close(); } // контракт, по которому представитель будет взаимодействовать с формой public interface ILoginView: IView { string Username { get; } string Password { get; } event Action Login; // событие "пользователь пытается авторизоваться" void ShowError(string errorMessage); } public interface IPresenter { void Run(); } // глупейший сервис авторизации public interface ILoginService { bool Login(User user); // true - успешная авторизация, иначе false }


    Представление

    public class LoginPresenter: IPresenter { private readonly ILoginView _view; private readonly ILoginService _service; public LoginPresenter(ILoginView view, ILoginService service) { _view = view; _service = service; _view.Login += () => Login(_view.Username, _view.Password); } public void Run() { _view.Show(); } private void Login(string username, string password) { if (username == null) throw new ArgumentNullException("username"); if (password == null) throw new ArgumentNullException("password"); var user = new User {Name = username, Password = password}; if (!_service.Login(user)) { _view.ShowError("Invalid username or password"); } else { // успешная авторизация, запуск главного экрана (?) } } }


    Создать форму и реализовать в ней интерфейс ILoginView не составит труда, как и написать реализацию ILoginService. Следует только отметить одну особенность:

    Public partial class LoginForm: Form, ILoginView { // ... public new void Show() { Application.Run(this); } }
    Это заклинание позволит нашему приложению запуститься, отобразить форму, а по закрытии формы корректно завершить приложение. Но к этому мы еще вернемся.

    А тесты будут?

    С момента написания представителя (LoginPresenter), появляется возможность сразу же его от-unit-тестировать, не реализуя ни формы, ни сервисы.
    Для написания тестов я использовал библиотеки NUnit и NSubstitute (библиотека создания классов-заглушек по их интерфейсам, mock).

    Тесты для LoginPresenter

    Public class LoginPresenterTests { private ILoginView _view; public void SetUp() { _view = Substitute.For(); // заглушка для представления var service = Substitute.For(); // заглушка для сервиса service.Login(Arg.Any()) // авторизуется только пользователь admin/password .Returns(info => info.Arg().Name == "admin" && info.Arg().Password == "password"); var presenter = new LoginPresenter(_view, service); presenter.Run(); } public void InvalidUser() { _view.Username.Returns("Vladimir"); _view.Password.Returns("VladimirPass"); _view.Login += Raise.Event(); _view.Received().ShowError(Arg.Any()); // этот метод должен вызваться с текстом ошибки } public void ValidUser() { _view.Username.Returns("admin"); _view.Password.Returns("password"); _view.Login += Raise.Event(); _view.DidNotReceive().ShowError(Arg.Any()); // а в этом случае все ОК } }


    Тесты довольно глупые, как пока и само приложение. Но так или иначе, они успешно пройдены.

    Кто и как запустит второй экран с параметром?

    Как вы могли заметить, я не написал никакого кода при успешной авторизации. Как же мне запустить второй экран? Первое на ум приходит это:

    // LoginPresenter: успешная авторизация var mainPresenter = new MainPresenter(new MainForm()); mainPresenter.Run(user);
    Но мы условились, что представители ничего не знают о представлениях кроме их интерфейсов. Что же делать?
    На помощь приходит паттерн Application Controller (реализован упрощенно), внутри которого содержится IoC-контейнер, знающий, как по интерфейсу получить объект реализации.
    Контроллер передается каждому Представителю параметром конструктора (снова DI) и реализует примерно следующие методы:

    Public interface IApplicationController { IApplicationController RegisterView() where TImplementation: class, TView where TView: IView; IApplicationController RegisterService() where TImplementation: class, TService; void Run() where TPresenter: class, IPresenter; }
    После небольшого рефакторинга запуск приложения стал выглядеть так:

    Private static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // все зависимости теперь регистрируются в одном месте: var controller = new ApplicationController(new LightInjectAdapder()) .RegisterView() .RegisterService() .RegisterView(); controller.Run(); }
    Пару слов о new ApplicationController(new LightInjectAdapder()) . В качестве IoC-контейнера я использовал библиотеку LightInject, но не напрямую, а через адаптер (паттерн Adapter), чтобы в случае, если понадобится сменить контейнер на другой, я смог написать другой адаптер и не менять логику контроллера. Все используемые методы есть в большинстве IoC-библиотек, сложностей возникнуть не должно.
    Реализуем дополнительный интерфейс IPresenter, отличающийся только тем, что метод Run принимает параметр. Затем унаследуемся от него аналогично первому экрану.
    Теперь, не без гордости, запускаем второй экран, передавая туда авторизованного пользователя:

    Controller.Run(user); View.Close();

    Нельзя просто так взять и закрыть форму...

    Один из подводных камней связан со строчкой View.Close() , после которой закрывалась первая форма, а вместе с ней и приложение. Дело в том, что Application.Run(Form) запускает стандартный цикл обработки сообщений Windows и рассматривает переданную форму как главную форму приложения. Это выражается в том, что приложение вешает ExitThread на событие Form.Closed , что и вызывает закрытие приложения после закрытия формы.
    Обойти данную проблему можно несколькими способами, один из них - использовать другой вариант метода: Application.Run(ApplicationContext) , затем вовремя подменяя свойство ApplicationContext.MainForm . Передача контекста формам реализована с помощью Контроллера приложения, в котором регистрируется объект (instance) ApplicationContext и затем подставляется в конструктор формы (опять DI) во время запуска Представителя. Методы отображения первых двух экранов теперь выглядят так:

    // LoginForm public new void Show() { _context.MainForm = this; Application.Run(_context); } // MainForm public new void Show() { _context.MainForm = this; base.Show(); }

    Модальное окно

    Реализация модального окна не вызывает затруднений. По кнопке "Сменить имя" выполняется Controller.Run(user) . Единственное отличие этой формы от остальных - она не главная, поэтому форме для показа не требуется ApplicationContext:

    Public new void Show() { ShowDialog(); }
    Если необходимо открыть обычное окно, метод вообще не требуется определять, так как он уже реализован в классе Form.

    Ну и накрутили... Как теперь ЭТО использовать?

    Теперь, когда каркас готов, добавление новой формы сводится к следующим шагам:
    1. Пишем интерфейс Представления, интерфейс Модели (если требуется).
    2. Реализуем Представителя, попутно решив, будем ли мы в него передавать какие-то данные или модель.
    3. [Опционально] Пишем тесты для Представителя, убеждаемся, что все нормально.
    4. [Опционально] Реализуем Модель и тесты для нее.
    5. Накидываем формочки и реализуем интерфейс Представления.
    Смена IoC-контейнера на ваш любимый происходит путем реализации простого интерфейса IContainer классом-адаптером.

    Забрать демонстрационный проект можно c