Разработка оконных приложений на C#
Оконное приложение на C# - это программа с графическим интерфейсом: окна, кнопки, поля ввода и таблицы. Для Windows чаще всего выбирают Windows Forms ради простоты и WPF ради гибкого оформления. Здесь собрано всё, что нужно для старта: выбор технологии, установка, первый проект на .NET 8, события, асинхронность, привязка данных и публикация готовой программы.

Содержание
Какую технологию выбрать
Под Windows на C# существует несколько UI-платформ, и от выбора зависит, сколько времени вы потратите на интерфейс. Все они работают на современном .NET, а классический .NET Framework 4.8 остаётся только для поддержки старых проектов. Новичку лучше сразу создавать проекты на .NET 8, там тот же язык, те же компоненты и актуальные обновления.
| Технология | Для чего подходит | Сложность | Особенности |
|---|---|---|---|
| Windows Forms | учебные программы, внутренние утилиты, быстрые формы ввода данных | низкая | визуальный конструктор, готовые элементы, только Windows |
| WPF | бизнес-приложения со сложным интерфейсом, таблицы, графика, темы | средняя | разметка XAML, привязка данных, шаблоны, паттерн MVVM |
| WinUI 3 | современные приложения в стиле Windows 11 | выше средней | новые элементы Fluent, упаковка в MSIX |
| Avalonia UI | программы для Windows, Linux и macOS | средняя | XAML-подобная разметка, один код на все системы |
| .NET MAUI | приложения для Android, iOS, Windows и macOS | выше средней | ориентирован на мобильные устройства |
Если вы только учитесь, начинайте с Windows Forms: интерфейс собирается перетаскиванием, а логика пишется в обработчиках событий, поэтому быстро видно результат. Когда понадобятся красивые окна, гибкая вёрстка и разделение кода и оформления, переходите на WPF. Приложение для нескольких операционных систем разумнее делать на Avalonia.
Что нужно установить
Основной инструмент - Visual Studio 2022 Community, она бесплатна для обучения, open source и небольших команд. При установке отметьте рабочую нагрузку "Разработка классических приложений .NET" (.NET desktop development), в неё входят шаблоны Windows Forms и WPF, конструктор форм и .NET SDK. Альтернативы: JetBrains Rider, который работает с WPF, и Visual Studio Code с расширением C# Dev Kit, но визуального конструктора форм в Code нет.
Проверить, что пакет разработки установился, можно в командной строке командой dotnet --list-sdks. Должна появиться версия 8.0 или новее. Эта же утилита создаёт проекты без Visual Studio, что удобно для автоматизации и проверки.
dotnet new winforms -n MyFirstApp
cd MyFirstApp
dotnet runПервое приложение на Windows Forms
В Visual Studio выберите "Создать проект", найдите шаблон "Приложение Windows Forms" для C# (не путайте с вариантом ".NET Framework"), задайте имя и целевую платформу .NET 8. Откроется конструктор с пустой формой Form1. В панели элементов перетащите на неё Button, Label и TextBox, а в окне "Свойства" измените им имена и тексты.
Файл Program.cs в новых шаблонах очень короткий: настройки высокого разрешения и шрифта выполняет один вызов. Именно отсюда запускается главное окно.
namespace MyFirstApp;
static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}Код формы разделён на два файла: Form1.Designer.cs создаётся конструктором автоматически и не редактируется вручную, а Form1.cs принадлежит вам. Двойной щелчок по кнопке создаёт в нём обработчик события Click.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonGreet_Click(object sender, EventArgs e)
{
string name = textBoxName.Text.Trim();
labelResult.Text = string.IsNullOrEmpty(name)
? "Введите имя"
: $"Привет, {name}!";
}
}Запуск выполняется клавишей F5 или зелёной кнопкой на панели. Пользователь вводит имя, нажимает кнопку, и метка показывает приветствие. Всё взаимодействие построено на событиях: нажатие, изменение текста, закрытие окна. Подписку на событие можно сделать и в коде: buttonGreet.Click += buttonGreet_Click;.
Основные элементы управления
Набор стандартных компонентов закрывает большинство задач. Чтобы не путаться в названиях, держите перед глазами эту краткую шпаргалку.
| Элемент | Назначение | Ключевое свойство или событие |
|---|---|---|
| Label | подпись, вывод текста | Text |
| TextBox | ввод строки | Text, TextChanged |
| Button | запуск действия | Click |
| CheckBox, RadioButton | флажок и выбор одного из вариантов | Checked, CheckedChanged |
| ComboBox, ListBox | выбор из списка | Items, SelectedItem |
| DataGridView | таблица данных | DataSource, CellValueChanged |
| NumericUpDown | числовой ввод с ограничением | Value, Minimum, Maximum |
| Timer | действие по таймеру | Interval, Tick |
Про отдельные компоненты у нас есть подробные материалы: как вывести число в TextBox и как добавить строку в DataGridView.
Расположение элементов и адаптивность
Чтобы окно нормально выглядело при изменении размера, не расставляйте элементы вслепую. Свойство Anchor привязывает элемент к краям формы, а Dock растягивает его на всю сторону контейнера. Для сложных макетов используйте TableLayoutPanel и FlowLayoutPanel: они сами распределяют место между вложенными элементами. Отдельно проверьте работу на экране с масштабом 125 или 150 процентов, типичная проблема - обрезанный текст кнопок.
Долгие операции и асинхронность
Главная ошибка начинающих - тяжёлая работа прямо в обработчике кнопки. Пока код выполняется в потоке интерфейса, окно не перерисовывается и выглядит зависшим. Решение - вынести расчёт или запрос в фоновую задачу через async и await. После await выполнение возвращается в поток интерфейса, поэтому элементы можно менять напрямую.
private async void buttonLoad_Click(object sender, EventArgs e)
{
buttonLoad.Enabled = false;
labelResult.Text = "Загрузка...";
try
{
using var client = new HttpClient();
string html = await client.GetStringAsync("https://example.com");
labelResult.Text = $"Получено символов: {html.Length}";
}
catch (HttpRequestException ex)
{
labelResult.Text = "Ошибка: " + ex.Message;
}
finally
{
buttonLoad.Enabled = true;
}
}Если расчёт загружает процессор, а не сеть, оберните его в await Task.Run(() => ...). Обращаться к элементам формы изнутри такого лямбда-блока нельзя, иначе получите исключение о доступе из другого потока. Как правильно обновлять интерфейс из фона, мы разобрали отдельно: обновление GUI из другого потока. И не используйте Application.DoEvents() как костыль, о рисках написано в статье про DoEvents.
Несколько окон и передача данных
Реальные программы редко состоят из одной формы. Для диалогов используется метод ShowDialog(), он блокирует родительское окно, пока диалог не закрыт, и возвращает результат кнопки. Данные в дочернюю форму удобно передавать через конструктор, а забирать обратно через свойство.
public partial class SettingsForm : Form
{
public string UserName => textBoxName.Text;
public SettingsForm(string currentName)
{
InitializeComponent();
textBoxName.Text = currentName;
buttonOk.DialogResult = DialogResult.OK;
buttonCancel.DialogResult = DialogResult.Cancel;
}
}
// в главной форме
using var dialog = new SettingsForm(currentName);
if (dialog.ShowDialog(this) == DialogResult.OK)
{
currentName = dialog.UserName;
}Другие способы - события, делегаты и общий объект данных - описаны в руководстве про передачу данных между формами и в статье о взаимодействии форм Windows Forms.
Приложение на WPF
WPF отделяет внешний вид от логики. Интерфейс описывается в XAML, а поведение пишется в C#. Проект создаётся шаблоном "Приложение WPF" на .NET 8. Главное окно находится в MainWindow.xaml, а код окна - в MainWindow.xaml.cs. Разметка Grid делит окно на строки и столбцы, вложенные панели StackPanel выстраивают элементы друг за другом.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Приветствие" Height="220" Width="360">
<StackPanel Margin="16">
<TextBox x:Name="NameBox" Margin="0,0,0,8" />
<Button Content="Поздороваться" Click="Greet_Click" />
<TextBlock x:Name="ResultText" Margin="0,12,0,0" FontSize="16" />
</StackPanel>
</Window>private void Greet_Click(object sender, RoutedEventArgs e)
{
ResultText.Text = string.IsNullOrWhiteSpace(NameBox.Text)
? "Введите имя"
: $"Привет, {NameBox.Text}!";
}Такой подход работает, но настоящая сила WPF проявляется в привязке данных. Вместо ручного копирования значений между полями и кодом элемент связывается со свойством объекта, и интерфейс обновляется сам при его изменении.
Привязка данных и MVVM
Для привязки класс модели представления реализует интерфейс INotifyPropertyChanged и сообщает об изменении свойств. Разметка указывает путь к свойству в выражении Binding. Так формируется паттерн MVVM: модель хранит данные, представление отвечает за вид, а модель представления связывает их. Подробный разбор паттерна есть в статье MVVM в C#.
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MainViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); OnPropertyChanged(nameof(Greeting)); }
}
public string Greeting => string.IsNullOrWhiteSpace(Name) ? "" : $"Привет, {Name}!";
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? prop = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}<StackPanel Margin="16">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Greeting}" FontSize="16" Margin="0,12,0,0" />
</StackPanel>В конструкторе окна достаточно написать DataContext = new MainViewModel();, и приветствие будет меняться по мере ввода без единой строки кода обработчиков. Чтобы не писать шаблонный код вручную, в реальных проектах подключают пакет CommunityToolkit.Mvvm, он генерирует свойства и команды атрибутами.
Стили и ресурсы
Оформление в WPF выносится в ресурсы: словари стилей применяются ко всем кнопкам или к элементам с заданным ключом. Это экономит правки при смене фирменных цветов, а темы оформления подключаются отдельным пакетом. В Windows Forms аналога нет, там внешний вид меняют вручную или сторонними библиотеками.
Работа с данными в окне
Типичное оконное приложение показывает таблицу из базы или файла. В Windows Forms достаточно присвоить список объектов свойству DataGridView.DataSource, а для автоматического обновления использовать BindingList<T>. В WPF применяется ObservableCollection<T> и элемент DataGrid или ListView. Для доступа к SQL Server, SQLite или PostgreSQL берут Entity Framework Core, а для чтения Excel и CSV существуют отдельные библиотеки, об одной из них рассказано в статье про работу с Excel.
var people = new BindingList<Person>
{
new("Анна", 28),
new("Игорь", 34)
};
dataGridView1.DataSource = people;
people.Add(new Person("Мария", 25)); // строка появится в таблице сама
public record Person(string Name, int Age);Записи (record) в роли источника данных работают для чтения, но свойства у них по умолчанию доступны только для инициализации, поэтому для редактируемой таблицы объявляйте обычный класс со свойствами get; set;.
Публикация готовой программы
Чтобы передать приложение пользователям, опубликуйте проект: в Visual Studio пункт "Опубликовать" либо команда в терминале. Вариант с флагом self-contained включает среду выполнения в папку программы, и на компьютере не нужно устанавливать .NET отдельно. Параметр единственного файла упаковывает всё в один exe.
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=trueДля установки с ярлыками и удалением используйте MSIX или сторонние сборщики инсталляторов вроде Inno Setup. Перед выпуском проверьте приложение в конфигурации Release: различия с отладочной сборкой описаны в статье про Debug и Release.
Типичные ошибки новичков
- Вся логика в обработчиках кнопок. Выносите расчёты в отдельные классы, тогда их можно тестировать и переиспользовать.
- Правка файла Designer.cs руками. Конструктор перезапишет изменения, меняйте свойства в окне "Свойства" или в конструкторе формы после
InitializeComponent(). - Тяжёлые вычисления в потоке интерфейса. Используйте
async/await. - Неосвобождённые ресурсы. Диалоги, потоки и подключения оборачивайте в
using. - Проверка ввода только на стороне интерфейса. Данные из полей проверяйте перед использованием:
int.TryParseвместоint.Parse.
Частые вопросы
С чего начать изучение оконных приложений на C#?
Сначала освойте основы языка: переменные, условия, циклы, классы. Затем установите Visual Studio, создайте проект Windows Forms на .NET 8 и сделайте простую программу, например калькулятор или список дел. Следующий шаг - работа с файлами и базой данных.
Что лучше выбрать: WinForms или WPF?
WinForms быстрее осваивается и подходит для внутренних утилит и учебных задач. WPF стоит выбирать для сложных интерфейсов, тем оформления и больших проектов с MVVM. Оба варианта поддерживаются в .NET 8.
Можно ли делать оконные приложения без Visual Studio?
Да. Проект создаётся командой dotnet new winforms или dotnet new wpf, редактируется в Rider или VS Code и запускается через dotnet run. Но визуальный конструктор форм в полном виде есть только в Visual Studio.
Будет ли оконное приложение работать на Linux или macOS?
Windows Forms и WPF рассчитаны на Windows. Для кроссплатформенных программ берите Avalonia UI или .NET MAUI.
Почему окно зависает при выполнении кнопки?
Код выполняется в потоке интерфейса, и окно не может перерисоваться. Вынесите долгую работу в Task.Run или используйте асинхронные методы с await.
Как превратить проект в один exe-файл?
Выполните публикацию с параметрами --self-contained true и -p:PublishSingleFile=true. Для маленького размера можно включить обрезку неиспользуемого кода, но в WPF и WinForms её нужно проверять тестами.





