Перейти к основному содержимому

C# WinForms и WPF — простые окна

Подборка готовых примеров WinForms и WPF на C# с построчным разбором — «что написано» и «зачем так». Материал рассчитан на тех, кто ищет в Google «winforms пример c#», «как сделать окно на c#», «wpf button click», «textbox winforms», «messagebox c#», «лабораторная winforms» или готовит домашку, лабораторную и курсовую с графическим интерфейсом.


Для кого эта статья

АудиторияЗачем открыть
ШкольникиИнформатика, проект «окно с кнопкой», конвертер температуры
СтудентыЛабораторная «десктоп на C#», зачёт по WinForms или WPF
СамоучкиСкопировать код → dotnet run → разобрать по таблице
После PythonУже делали Tkinter — те же идеи (окно, кнопка, поле ввода), другой синтаксис
После консоли C#Знаете Console.WriteLine — здесь вместо консоли Form или Window

Как работать с примером:

  1. Создайте проект — dotnet new winforms или dotnet new wpf (команды ниже).
  2. Скопируйте целиком блок кода из нужного раздела.
  3. Запустите — dotnet run в папке проекта (нужен Windows).
  4. Прочитайте Разбор под кодом — там смысл каждой строки и типичные ошибки.
  5. Измените текст, размер окна, формулу — так быстрее запоминается API.

Примеры запускаются только на Windows с .NET SDK 8+. В браузере, как симулятор Turtle на странице Lab, они не выполняются.

Сначала теория

Обзор платформ — WinForms и WPF с нуля. Рецепты по каждому элементу — справочник WinForms и справочник WPF. Аналог на Python — Tkinter — окна и виджеты. Синтаксис C# — первая программа.


Краткий указатель — что ищут в Google

РазделТипичный запрос
Каркас WinFormswinforms program.cs пример, application.run c#, statthread winforms
Каркас WPFwpf mainwindow xaml пример, initializecomponent, startupuri
Окно с текстомwinforms label пример, wpf textblock, как сделать окно c#
Кнопка и MessageBoxwinforms button click, messagebox.show c#, wpf button click event
Поле ввода и имяwinforms textbox get text, wpf textbox x:name, форма ввода c#
Конвертер °C → °Fконвертер температуры winforms, tryparse c# textbox, курсовая калькулятор c#
CheckBox и RadioButtonwinforms checkbox, wpf radiobutton groupname, настройки форма c#
Список задачwinforms listbox add item, wpf listbox пример, todo list c# gui
Форма входаwinforms tablelayoutpanel, wpf grid passwordbox, форма логин c#
Менюwinforms menustrip, wpf menu menuitem, statusbar c#
OpenFileDialogopenfiledialog c#, microsoft.win32.openfiledialog wpf
Привязка WPFwpf binding textbox, datacontext пример, binding mode
Частые ошибкиокно сразу закрывается winforms, net8.0-windows, invoke ui thread

WinForms и WPF — что выбрать для учебного проекта

WinFormsWPF
РазметкаКод C# или визуальный Designer в Visual StudioXAML + code-behind
ОтрисовкаGDI+, «родные» окна WindowsDirectX, векторный UI
Привязка данныхБазовая{Binding}, MVVM
Шаблон проектаdotnet new winformsdotnet new wpf
Когда удобнееБыстрая утилита, CRUD, legacyКастомный UI, стили, анимации

Для первого окна подойдут оба стека. WinForms проще «увидеть результат в одном .cs файле»; WPF учит XAML — это пригодится в практикуме WPF.


Создание проекта

WinForms:

dotnet new winforms -n MyDesktopApp -o MyDesktopApp
cd MyDesktopApp
dotnet run

WPF:

dotnet new wpf -n MyWpfApp -o MyWpfApp
cd MyWpfApp
dotnet run

Разбор команд

КомандаСмысл
dotnet new winformsШаблон с Program.cs, Form1.cs, флагом UseWindowsForms в .csproj
dotnet new wpfШаблон с App.xaml, MainWindow.xaml, флагом UseWPF
-n MyDesktopAppИмя проекта и namespace по умолчанию
-o MyDesktopAppПапка на диске
dotnet runСборка и запуск; откроется пустое окно

Появится пустое окно — шаблон уже рабочий. Примеры ниже можно заменить содержимое Program.cs (WinForms) или MainWindow.xaml + MainWindow.xaml.cs (WPF).


Из чего состоит десктоп-приложение на C#

В консольной программе всё крутится вокруг Main() и Console.ReadLine(). В WinForms и WPF точка входа та же, но вместо текста в консоли — окно и цикл сообщений, который ждёт клики и ввод.

ЧастьWinFormsWPFРоль
Точка входаProgram.Main()App.xamlStartupUriЗапуск приложения
Главное окноFormWindowРамка с заголовком и кнопкой закрытия
Элемент UILabel, Button, TextBoxТо же + TextBlock, PasswordBoxТо, что видит пользователь
Событиеbutton.Click += ...Click="OnClick" в XAMLРеакция на действие
ЦиклApplication.Run(form)Внутри ApplicationПрограмма жива, пока окно открыто
ДиалогMessageBox.Show(...)MessageBox.Show(...)Всплывающее сообщение поверх окна

Пока цикл работает, код после Application.Run не выполняется — как mainloop() в Tkinter.


Словарь элементов за 30 секунд

ЭлементWinFormsWPFЗачемКак прочитать значение
ОкноFormWindowГлавная рамка
НадписьLabelLabel, TextBlockСтатический текстlabel.Text / {Binding}
КнопкаButtonButtonДействие по кликуClick / command
Поле вводаTextBoxTextBoxОдна строкаtextBox.Text
ПарольTextBox + UseSystemPasswordCharPasswordBoxСкрытый вводPasswordBox.Password
ГалочкаCheckBoxCheckBoxВкл/выклChecked / IsChecked
ПереключательRadioButtonRadioButtonОдин из несколькихChecked + группа
СписокListBoxListBoxTo-do, выбор строкиSelectedIndex, Items
МенюMenuStripMenuФайл, Справкаобработчик пункта
ДиалогMessageBox.ShowMessageBox.ShowOK, Yes/Noвозвращаемое значение

Компоновка WinForms: Location + Size, Dock, Anchor, TableLayoutPanel.
Компоновка WPF: Grid, StackPanel, DockPanel — см. справочник XAML.


Обязательный каркас WinForms

Любой пример WinForms ниже повторяет эту структуру. Запомните её — как import tkinter и mainloop() в Tkinter.

Скопируйте в Program.cs целиком (после dotnet new winforms):

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

namespace MyDesktopApp;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Моё приложение",
ClientSize = new Size(400, 300),
StartPosition = FormStartPosition.CenterScreen
};

// --- здесь Label, Button, TextBox и остальные контролы ---

Application.Run(form);
}
}

Разбор каркаса WinForms — по строкам

Строка / блокСмысл
using System.Drawing;Типы Point, Size, Font, Color — координаты и оформление
using System.Windows.Forms;Form, Button, Application, MessageBox
namespace MyDesktopApp;Имя проекта из шаблона dotnet new; должно совпадать с .csproj
[STAThread]Один UI-поток для COM и контролов Windows; без атрибута возможны странные ошибки
ApplicationConfiguration.Initialize()Настройка DPI и стилей (.NET 6+); вызывают один раз в начале Main
new Form { ... }Главное окно; Text — заголовок в строке заголовка ОС
ClientSize = new Size(400, 300)Ширина × высота рабочей области без рамки окна
StartPosition = CenterScreenОкно по центру монитора при старте
Application.Run(form)Цикл сообщений; без этой строки окно мелькает и сразу закроется

Файл проекта (шаблон создаёт сам):

<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>

Если указать net8.0 без -windows, типы Form и Application не найдутся при сборке.

Что попробовать: добавьте form.MaximizeBox = false; — исчезнет кнопка «развернуть».


Обязательный каркас WPF

WPF делит UI на разметку (XAML) и код (C#). Окно описывают в двух связанных файлах.

MainWindow.xaml — замените содержимое после dotnet new wpf:

<Window x:Class="MyWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Моё приложение"
Height="300" Width="400"
WindowStartupLocation="CenterScreen">
<!-- Label, Button, TextBox — между открывающим и закрывающим Window -->
</Window>

MainWindow.xaml.cs:

namespace MyWpfApp;

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

App.xaml (шаблон, обычно не трогают):

<Application x:Class="MyWpfApp.App"
StartupUri="MainWindow.xaml">
</Application>

Разбор каркаса WPF — по частям

ЧастьСмысл
x:Class="MyWpfApp.MainWindow"Связь XAML с классом MainWindow в MainWindow.xaml.cs; имя должно совпадать
xmlns / xmlns:xПространства имён XML — стандартные для WPF
Title, Height, WidthЗаголовок и размер окна (в отличие от WinForms ClientSize)
WindowStartupLocation="CenterScreen"Центрирование — аналог FormStartPosition.CenterScreen
partial class MainWindow : WindowКласс окна; partial — вторая часть генерируется из XAML
InitializeComponent()Загружает XAML и создаёт поля для x:Name; не удаляйте
StartupUri="MainWindow.xaml"Какое окно открыть при старте

Что попробуйте: смените Title и перезапустите dotnet run — заголовок окна изменится без правки C#.


Стартовые окна

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


Минимальное окно с меткой

Задача: показать, что C# на .NET умеет открыть окно с текстом — минимум для проверки установки SDK и сдачи отчёта «программа с GUI».

Что получится: окно по центру экрана с одной надписью «Окно работает!».

WinForms — полный Program.cs

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

namespace HelloWinForms;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Привет, WinForms",
ClientSize = new Size(400, 200),
StartPosition = FormStartPosition.CenterScreen
};

var label = new Label
{
Text = "Окно работает!",
AutoSize = true,
Location = new Point(20, 20),
Font = new Font("Segoe UI", 14f)
};
form.Controls.Add(label);

Application.Run(form);
}
}

Разбор WinForms — что делает каждая строка UI

СтрокаСмысл
var label = new LabelСоздаём надпись — аналог tk.Label в Python
Text = "Окно работает!"Текст, который видит пользователь
AutoSize = trueШирина и высота подстраиваются под длину текста
Location = new Point(20, 20)Отступ 20 px от левого верхнего угла клиентской области
Font = new Font("Segoe UI", 14f)Шрифт и размер; f у 14 — литерал float
form.Controls.Add(label)Обязательно: без добавления на форму контрол не виден

WPF — MainWindow.xaml

<Window x:Class="HelloWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Привет, WPF"
Height="200" Width="400"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20">
<TextBlock Text="Окно работает!" FontSize="14"/>
</StackPanel>
</Window>

Разбор WPF

ЭлементСмысл
StackPanelКонтейнер: дочерние элементы стопкой сверху вниз
Margin="20"Отступ контента от краёв окна
TextBlockТолько текст; Label в WPF чаще для подписей к полям

Что попробовать: смените текст и размер окна; в WinForms — form.Text и ClientSize, в WPF — Title и Width/Height.


Кнопка и MessageBox

Задача: по нажатию кнопки показать диалог — основа калькулятора, формы входа и любой «кнопки Сохранить».

Что получится: кнопка «Нажми меня»; после клика — окно сообщения Windows.

WinForms — полный Program.cs

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

namespace ButtonDemo;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Кнопка",
ClientSize = new Size(320, 180),
StartPosition = FormStartPosition.CenterScreen
};

var button = new Button
{
Text = "Нажми меня",
Location = new Point(20, 20),
AutoSize = true
};

// Click += подписка на событие; лямбда вызывается ТОЛЬКО по клику
button.Click += (_, _) => MessageBox.Show(
"Кнопка нажата!",
"Сообщение",
MessageBoxButtons.OK,
MessageBoxIcon.Information);

form.Controls.Add(button);
Application.Run(form);
}
}

Разбор события Click в WinForms

КонструкцияСмысл
button.Click += (_, _) => ...Подписка на событие «клик»; (_, _) — аргументы не нужны
Нельзя писать button.Click += MessageBox.Show(...)Show(...) выполнится сразу при старте, не по клику
MessageBox.Show(текст, заголовок, кнопки, иконка)Модальное окно; код ждёт, пока пользователь нажмёт OK
MessageBoxButtons.OKОдна кнопка OK
MessageBoxIcon.InformationСиний значок «i»

WPF — MainWindow.xaml

<Window x:Class="ButtonDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Кнопка" Height="180" Width="320"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20">
<Button Content="Нажми меня" Click="OnClick" Padding="12,6"/>
</StackPanel>
</Window>

MainWindow.xaml.cs:

using System.Windows;

namespace ButtonDemo;

public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();

private void OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Кнопка нажата!", "Сообщение");
}
}

Разбор WPF Click

ЧастьСмысл
Click="OnClick" в XAMLИмя метода в .xaml.cs; компилятор связывает разметку и код
RoutedEventArgs eОбъект события; в простых примерах часто не используют
Content="..." у ButtonТекст на кнопке (в WinForms — свойство Text)

Что попробовать: MessageBoxButtons.YesNo (WinForms) или MessageBoxButton.YesNo (WPF) — проверьте, что вернётся при Yes и No.


Поле ввода и приветствие

Задача: прочитать имя из TextBox и показать приветствие — типичная форма «введите имя» в лабораторных.

Что получится: подпись, поле ввода, кнопка; пустое имя — предупреждение.

WinForms — полный Program.cs

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

namespace GreetApp;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Приветствие",
ClientSize = new Size(360, 160),
StartPosition = FormStartPosition.CenterScreen,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false
};

var nameLabel = new Label
{
Text = "Ваше имя:",
AutoSize = true,
Location = new Point(20, 24)
};

var entry = new TextBox
{
Location = new Point(120, 20),
Width = 200
};

var greetButton = new Button
{
Text = "Приветствовать",
Location = new Point(20, 60),
AutoSize = true
};

greetButton.Click += (_, _) =>
{
var name = entry.Text.Trim();
if (string.IsNullOrEmpty(name))
{
MessageBox.Show("Введите имя", "Пусто",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
MessageBox.Show($"Здравствуй, {name}!", "Привет");
};

form.Controls.Add(nameLabel);
form.Controls.Add(entry);
form.Controls.Add(greetButton);
entry.Focus();

Application.Run(form);
}
}

Разбор логики формы

СтрокаСмысл
entry.TextТекущий текст в поле; читают внутри обработчика Click, когда пользователь уже ввёл данные
.Trim()Убирает пробелы по краям — " Анна ""Анна"
string.IsNullOrEmpty(name)Проверка на пустую строку после trim
return;Выход из обработчика без второго MessageBox
$"Здравствуй, &#123;name&#125;!"Интерполяция строк C#
entry.Focus()Курсор сразу в поле при открытии окна
FormBorderStyle.FixedDialogОкно фиксированного размера — часто для маленьких форм

WPF — MainWindow.xaml

<Window x:Class="GreetApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Приветствие" Height="160" Width="360"
ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<StackPanel Margin="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Ваше имя:" VerticalAlignment="Center"/>
<TextBox x:Name="NameBox" Grid.Column="1" Margin="8,0,0,0"/>
</Grid>
<Button Content="Приветствовать" Click="OnGreet"
Margin="0,12,0,0" HorizontalAlignment="Left"/>
</StackPanel>
</Window>

MainWindow.xaml.cs:

using System.Windows;

namespace GreetApp;

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
NameBox.Focus();
}

private void OnGreet(object sender, RoutedEventArgs e)
{
var name = NameBox.Text.Trim();
if (string.IsNullOrEmpty(name))
{
MessageBox.Show("Введите имя", "Пусто",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
MessageBox.Show($"Здравствуй, {name}!", "Привет");
}
}

Разбор x:Name в WPF

АтрибутСмысл
x:Name="NameBox"Поле NameBox в code-behind — доступ из C# как NameBox.Text
Grid.Column="1"TextBox во втором столбце сетки
Width="*" у столбцаВторой столбец растягивается на оставшуюся ширину

Что попробовать: второе поле «Фамилия» и вывод Здравствуй, Имя Фамилия!; Enter в поле — обработчик KeyDown (WinForms) или KeyDown="..." (WPF).


Конвертер °C → °F

Задача: классическая учебная программа — ввод числа, формула, вывод результата на форме. Часто встречается в заданиях и курсовых.

Формула: $F = C \times \frac{9}{5} + 32$

WinForms — полный Program.cs

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

namespace TempConverter;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Конвертер температуры",
ClientSize = new Size(340, 180),
StartPosition = FormStartPosition.CenterScreen,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false
};

var tempLabel = new Label
{
Text = "Температура (°C):",
AutoSize = true,
Location = new Point(20, 24)
};

var entry = new TextBox { Location = new Point(160, 20), Width = 80 };

var resultLabel = new Label
{
Text = "—",
AutoSize = true,
Location = new Point(20, 60),
Font = new Font("Segoe UI", 12f)
};

var convertButton = new Button
{
Text = "Перевести",
Location = new Point(20, 100),
AutoSize = true
};

convertButton.Click += (_, _) =>
{
var raw = entry.Text.Trim().Replace(',', '.');
if (!double.TryParse(raw, NumberStyles.Any, CultureInfo.InvariantCulture, out var celsius))
{
MessageBox.Show("Введите число, например 25", "Ошибка");
return;
}
var fahrenheit = celsius * 9 / 5 + 32;
resultLabel.Text = $"{celsius:F1} °C = {fahrenheit:F1} °F";
};

entry.KeyDown += (_, e) =>
{
if (e.KeyCode == Keys.Enter)
convertButton.PerformClick();
};

form.Controls.Add(tempLabel);
form.Controls.Add(entry);
form.Controls.Add(resultLabel);
form.Controls.Add(convertButton);
entry.Focus();

Application.Run(form);
}
}

Разбор парсинга и вывода

СтрокаСмысл
.Replace(',', '.')Пользователь ввёл 25,5 — для TryParse нужна точка
double.TryParse(..., out var celsius)Безопасный разбор; при буквах вернёт false, без исключения
CultureInfo.InvariantCultureДесятичная точка не зависит от языка Windows
$"&#123;celsius:F1&#125; °C"Один знак после запятой
PerformClick()Программно «нажать» кнопку — Enter дублирует «Перевести»
resultLabel.Text = ...Обновление надписи без нового окна — результат на той же форме

WPF — ключевые файлы

MainWindow.xaml:

<Window x:Class="TempConverter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Конвертер температуры" Height="180" Width="340"
ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<StackPanel Margin="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Label Content="Температура (°C):" VerticalAlignment="Center"/>
<TextBox x:Name="TempBox" Grid.Column="1" Margin="8,0,0,0" KeyDown="OnTempKeyDown"/>
</Grid>
<TextBlock x:Name="ResultText" Margin="0,12,0,0" FontSize="12" Text=""/>
<Button Content="Перевести" Click="OnConvert" Margin="0,12,0,0" HorizontalAlignment="Left"/>
</StackPanel>
</Window>

MainWindow.xaml.cs — та же логика TryParse и формула, что в WinForms; результат пишется в ResultText.Text.

Что попробуйте: кнопка «Очистить» — entry.Clear() и resultLabel.Text = "—"; обратный перевод °F → °C.


Флажок и переключатели

Задача: несколько настроек «вкл/выкл» и выбор одной роли — учит работе с состоянием контролов.

Смысл: CheckBoxнезависимые галочки; RadioButtonодин вариант из группы.

WinForms — фрагмент для Main (после создания form)

var notifyCheck = new CheckBox
{
Text = "Уведомления", Location = new Point(20, 20), Checked = true, AutoSize = true
};
var soundCheck = new CheckBox { Text = "Звук", Location = new Point(20, 50), AutoSize = true };
var userRadio = new RadioButton
{
Text = "Пользователь", Location = new Point(20, 90), Checked = true, AutoSize = true
};
var adminRadio = new RadioButton { Text = "Администратор", Location = new Point(20, 120), AutoSize = true };
var statusLabel = new Label { Location = new Point(20, 160), AutoSize = true, ForeColor = Color.Gray };

void UpdateStatus()
{
var parts = new List<string>();
if (notifyCheck.Checked) parts.Add("уведомления");
if (soundCheck.Checked) parts.Add("звук");
var role = adminRadio.Checked ? "admin" : "user";
statusLabel.Text = $"Роль: {role}; включено: {(parts.Count > 0 ? string.Join(", ", parts) : "ничего")}";
}

notifyCheck.CheckedChanged += (_, _) => UpdateStatus();
soundCheck.CheckedChanged += (_, _) => UpdateStatus();
userRadio.CheckedChanged += (_, _) => UpdateStatus();
adminRadio.CheckedChanged += (_, _) => UpdateStatus();

form.Controls.AddRange(new Control[] { notifyCheck, soundCheck, userRadio, adminRadio, statusLabel });
UpdateStatus();

Разбор

APIСмысл
Checked = trueГалочка / переключатель включены при старте
CheckedChangedСобытие при смене состояния — обновляем строку статуса
adminRadio.Checked ? "admin" : "user"Тернарный оператор — кто выбран
string.Join(", ", parts)Список включённых опций через запятую
UpdateStatus() при стартеНачальная подпись, не пустая строка

WPF

GroupName="Role" у RadioButton объединяет переключатели в одну группу. IsChecked имеет тип bool? — проверяют == true.

Что попробуйте: третья роль «Гость»; отключение кнопки, если имя пустое (button.Enabled = false в WinForms, IsEnabled="False" в WPF).


Список задач (ListBox)

Задача: мини to-do — добавить задачу, удалить выбранную. Удобный мини-проект для отчёта (2–3 экрана в Word + скриншот).

Что получится: поле ввода, кнопки «Добавить» / «Удалить», список строк.

WinForms — полный Program.cs

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

namespace TodoList;

static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();

var form = new Form
{
Text = "Список задач",
ClientSize = new Size(320, 280),
StartPosition = FormStartPosition.CenterScreen
};

var entry = new TextBox { Location = new Point(12, 12), Width = 280 };
var addButton = new Button { Text = "Добавить", Location = new Point(12, 44), AutoSize = true };
var removeButton = new Button { Text = "Удалить", Location = new Point(100, 44), AutoSize = true };
var listBox = new ListBox { Location = new Point(12, 80), Size = new Size(280, 160) };

void AddTask()
{
var text = entry.Text.Trim();
if (text.Length == 0) return;
listBox.Items.Add(text);
entry.Clear();
}

void RemoveTask()
{
if (listBox.SelectedIndex >= 0)
listBox.Items.RemoveAt(listBox.SelectedIndex);
}

addButton.Click += (_, _) => AddTask();
removeButton.Click += (_, _) => RemoveTask();
entry.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter) AddTask(); };

form.Controls.AddRange(new Control[] { entry, addButton, removeButton, listBox });
entry.Focus();

Application.Run(form);
}
}

Разбор ListBox

Метод / свойствоСмысл
listBox.Items.Add(text)Добавить строку в конец списка
listBox.SelectedIndexИндекс выделенной строки; -1, если ничего не выбрано
RemoveAt(index)Удалить строку по индексу
entry.Clear()Очистить поле после добавления
Локальные функции AddTask / RemoveTaskC# 7+ — короткий способ не дублировать код Enter и кнопки

WPF

Grid с тремя строками: поле ввода, панель кнопок, ListBox с Height="*". Логика в AddTask() — та же.

Что попробуйте: счётчик задач в заголовке окна — form.Text = $"Задач: &#123;listBox.Items.Count&#125;".


Примеры окон и элементов

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


1. Форма входа

Задача: поля Email и пароль с выравниванием — как на сайте, но в десктопе.

WinForms — TableLayoutPanel

var table = new TableLayoutPanel
{
Dock = DockStyle.Fill,
Padding = new Padding(12),
ColumnCount = 2,
RowCount = 3
};
table.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f));

var emailBox = new TextBox { Dock = DockStyle.Fill, Margin = new Padding(8, 4, 0, 4) };
var passBox = new TextBox
{
Dock = DockStyle.Fill,
UseSystemPasswordChar = true,
Margin = new Padding(8, 4, 0, 4)
};
var loginButton = new Button { Text = "Войти", Anchor = AnchorStyles.Right, AutoSize = true };
loginButton.Click += (_, _) => MessageBox.Show("Вход (демо)");

table.Controls.Add(new Label { Text = "Email:", AutoSize = true }, 0, 0);
table.Controls.Add(emailBox, 1, 0);
table.Controls.Add(new Label { Text = "Пароль:", AutoSize = true }, 0, 1);
table.Controls.Add(passBox, 1, 1);
table.Controls.Add(loginButton, 1, 2);

form.Controls.Add(table);

Разбор TableLayoutPanel

СвойствоСмысл
ColumnCount = 2Две колонки — подпись и поле
SizeType.AutoSizeПервая колонка по ширине текста «Email:»
SizeType.Percent, 100fВторая колонка забирает всю оставшуюся ширину
Controls.Add(control, column, row)Ячейка таблицы — аналог grid(row, column) в Tkinter
UseSystemPasswordChar = trueСимволы пароля отображаются как ●

WPF — Grid + PasswordBox

<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Email:" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Margin="8,4,0,4"/>
<Label Grid.Row="1" Content="Пароль:" VerticalAlignment="Center"/>
<PasswordBox x:Name="PassBox" Grid.Row="1" Grid.Column="1" Margin="8,4,0,4"/>
<Button Grid.Row="2" Grid.Column="1" Content="Войти"
HorizontalAlignment="Right" Margin="0,12,0,0" Click="OnLogin"/>
</Grid>

Смысл: в WPF пароль — отдельный контрол PasswordBox; свойство Password, не Text.


2. Меню и строка состояния

WinForms:

var menu = new MenuStrip();
var fileMenu = new ToolStripMenuItem("Файл");
fileMenu.DropDownItems.Add("Новый", null, (_, _) => MessageBox.Show("Новый"));
fileMenu.DropDownItems.Add("Выход", null, (_, _) => form.Close());
menu.Items.Add(fileMenu);

var status = new StatusStrip();
status.Items.Add(new ToolStripStatusLabel("Готово"));

form.MainMenuStrip = menu;
form.Controls.Add(menu);
form.Controls.Add(status);

Разбор

APIСмысл
MenuStripПолоса меню под заголовком окна
DropDownItems.Add(текст, иконка, обработчик)Пункт выпадающего меню
form.MainMenuStrip = menuПривязка меню к форме — корректные Alt+буква и отступ контента
StatusStripСерая полоска внизу — «Готово», строка состояния

3. Диалог выбора файла

WinForms:

using var dialog = new OpenFileDialog
{
Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",
Title = "Открыть файл"
};
if (dialog.ShowDialog(form) == DialogResult.OK)
MessageBox.Show(form, $"Выбран: {dialog.FileName}");

WPF:

var dialog = new Microsoft.Win32.OpenFileDialog
{
Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",
Title = "Открыть файл"
};
if (dialog.ShowDialog() == true)
MessageBox.Show($"Выбран: {dialog.FileName}");

Разбор Filter

Строка фильтра: Описание|*.расширение|Описание2|*.расширение2.
Символ | разделяет пары «подпись — маска».

СтекКлассУспешный результат
WinFormsSystem.Windows.Forms.OpenFileDialogDialogResult.OK
WPFMicrosoft.Win32.OpenFileDialogtrue

4. Мини-калькулятор сложения (WinForms)

Задача: два TextBox, кнопка «=», результат в Label — связка ввода, события и TryParse.

var aBox = new TextBox { Location = new Point(20, 20), Width = 60 };
var bBox = new TextBox { Location = new Point(100, 20), Width = 60 };
var sumButton = new Button { Text = "=", Location = new Point(180, 18), AutoSize = true };
var resultLabel = new Label { Location = new Point(220, 22), AutoSize = true };

sumButton.Click += (_, _) =>
{
if (double.TryParse(aBox.Text.Replace(',', '.'), out var a) &&
double.TryParse(bBox.Text.Replace(',', '.'), out var b))
resultLabel.Text = (a + b).ToString("G");
else
resultLabel.Text = "Ошибка";
};

form.Controls.AddRange(new Control[] { aBox, bBox, sumButton, resultLabel });

Смысл: результат на форме, без MessageBox — пользователь видит ошибку «Ошибка» в том же окне. Расширение до -, *, / — один обработчик и switch по оператору.


5. Привязка данных в WPF (простой уровень)

Задача: текст «Привет, …!» обновляется сам, пока пользователь печатает в поле — задел под MVVM.

Models/Person.cs:

namespace MyWpfApp.Models;

public sealed class Person
{
public string Name { get; set; } = "Гость";
}

MainWindow.xaml:

<StackPanel Margin="20">
<TextBlock Text="{Binding Name, StringFormat='Привет, {0}!'}" FontSize="14"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0,12,0,0"/>
</StackPanel>

MainWindow.xaml.cs:

using MyWpfApp.Models;

public MainWindow()
{
InitializeComponent();
DataContext = new Person();
}

Разбор Binding

КонструкцияСмысл
DataContext = new Person()Объект-источник для всех &#123;Binding ...&#125; на окне
&#123;Binding Name&#125;Свойство Person.Name
StringFormat='Привет, {0}!'Шаблон отображения
UpdateSourceTrigger=PropertyChangedМодель обновляется на каждый символ, не только при потере фокуса
Два &#123;Binding Name&#125;Одно свойство — два контрола; WPF синхронизирует их
Дальше — MVVM

В учебных проектах достаточно DataContext и простого класса. В курсовой и production свойства выносят во ViewModel с INotifyPropertyChanged — см. 119.md и практикум MVVM.


6. Подтверждение при закрытии окна

WinForms:

form.FormClosing += (_, e) =>
{
if (MessageBox.Show("Закрыть приложение?", "Выход",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
e.Cancel = true;
};

WPF — атрибут Closing="OnClosing" на Window и в обработчике e.Cancel = true при ответе No.

Смысл: крестик в заголовке по умолчанию закрывает окно сразу; Cancel = true отменяет закрытие — нужно для «Сохранить изменения?».


7. Шаблоны для своих проектов

Окно по центру

WinForms: StartPosition = FormStartPosition.CenterScreen.
WPF: WindowStartupLocation="CenterScreen".

Логика отдельно от формы

// Services/GreetingService.cs
public static class GreetingService
{
public static string Build(string name) =>
string.IsNullOrWhiteSpace(name) ? "Введите имя" : $"Здравствуй, {name.Trim()}!";
}

Кнопка вызывает GreetingService.Build(entry.Text) — форма остаётся тонкой; логику можно проверить unit-тестом без GUI. Это плюс на защите курсовой.


Частые ошибки и как исправить

СимптомПричинаРешение
Окно мелькает и закрываетсяНет Application.Run(form)Добавьте в конец Main перед закрывающей }
«Не найден тип Form»TFM net8.0 без -windowsВ .csproj: net8.0-windows
Ошибка CS0246 Window / FormНет UseWindowsForms / UseWPFФлаги в .csproj из шаблона dotnet new
Контрол не виденНет Controls.AddКаждый контрол добавьте на form или в panel
Кнопка срабатывает при стартеВызвали метод вместо ссылкиClick += Handler, не Click += Handler()
XAML: имя не существуетx:Class не совпадает с namespaceMyApp.MainWindow в XAML = namespace MyApp + class MainWindow
UI «завис» на 5 секундThread.Sleep или тяжёлый цикл в Clickawait Task.Run(...) — см. 112.md
InvalidOperationExceptionОбновление UI из фонового потокаWinForms: control.Invoke(() => ...); WPF: Dispatcher.Invoke
25,5 не парситсяЛокаль Windows.Replace(',', '.') + InvariantCulture в TryParse

Маршрут изучения

ШагПример в статьеЗачемДальше
1Каркас WinFormsПонять Application.Run115 — теория WinForms
2Окно + кнопкаСобытия и MessageBox1152 — справочник UI
3TextBox + конвертерВвод, парсинг, формулаЛабораторная «калькулятор»
4ListBox to-doКоллекция на формеКурсовая «список задач»
5WPF BindingXAML и данные119 — WPF с нуля
6Практикум MVVMКлиент-серверTaskDesk

См. также


См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").