4 академических часа (180 минут)
✅ Что такое WPF и зачем он нужен
✅ XAML - язык разметки
✅ Элементы управления и разметка
✅ События и привязка данных
WPF (Windows Presentation Foundation) — это современная технология Microsoft для создания десктопных приложений под Windows.
| Характеристика | WinForms | WPF |
|---|---|---|
| Графика | Пиксельная | Векторная (DirectX) |
| Разметка | Код (C#) | XAML (как HTML) |
| Дизайн | Сложно кастомизировать | Легко (стили, шаблоны) |
| Разделение | Дизайн и логика вместе | Дизайн и логика разделены |
| Анимация | Сложно | Просто (встроенная) |
| Привязка данных | Ручная | Автоматическая (Binding) |
<div>
<button>Нажми</button>
</div>
<StackPanel>
<Button Content="Нажми"/>
</StackPanel>
<!-- MainWindow.xaml -->
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Мое первое WPF приложение"
Height="450" Width="800">
<Grid>
<Button Content="Нажми меня!"
Width="150" Height="50"
Click="Button_Click"/>
</Grid>
</Window>
// MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Привет, WPF!");
}
}
Задание: Создай новое WPF приложение, добавь кнопку с текстом "Привет" и обработчик, показывающий сообщение "Привет, мир!".
📌 Подсказка: Событие Click добавляется через атрибут Click="Имя_обработчика".
| Элемент | Назначение | Пример |
|---|---|---|
| Button | Кнопка | <Button Content="OK"/> |
| TextBox | Ввод текста | <TextBox Text="Введите имя"/> |
| Label | Текстовая метка | <Label Content="Имя:"/> |
| CheckBox | Флажок | <CheckBox Content="Согласен"/> |
| RadioButton | Переключатель | <RadioButton Content="Мужской"/> |
| ListBox | Список | <ListBox><ListBoxItem>Пункт 1</ListBoxItem></ListBox> |
| ComboBox | Выпадающий список | <ComboBox><ComboBoxItem>Выберите</ComboBoxItem></ComboBox> |
| Image | Картинка | <Image Source="image.jpg"/> |
<Window ...>
<StackPanel Margin="20">
<Label Content="Имя:"/>
<TextBox x:Name="txtName" Margin="0,0,0,10"/>
<Label Content="Возраст:"/>
<TextBox x:Name="txtAge" Margin="0,0,0,10"/>
<Label Content="Пол:"/>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="rbMale" Content="Мужской" GroupName="Gender"/>
<RadioButton x:Name="rbFemale" Content="Женский" GroupName="Gender"/>
</StackPanel>
<CheckBox x:Name="chkAgree" Content="Согласен с условиями" Margin="0,10,0,10"/>
<Button Content="Зарегистрироваться" Click="Register_Click"
Background="#667eea" Foreground="White" Height="40"/>
</StackPanel>
</Window>
Создай форму регистрации с полями: Имя, Email, Пароль, CheckBox "Запомнить меня", кнопка "Войти". При нажатии покажи сообщение с введенными данными.
Панели определяют, как элементы располагаются на экране.
<StackPanel Orientation="Vertical">
<Button/> <Button/> <Button/>
</StackPanel>Элементы друг за другом (по вертикали или горизонтали)
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0"/>
<Button Grid.Row="1"/>
</Grid>Таблица (строки и столбцы) - самая гибкая!
<WrapPanel>
<Button/> <Button/> <Button/>
</WrapPanel>Перенос на новую строку если не влезают
<Grid>
<!-- Определяем строки и столбцы -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Авто (по содержимому) -->
<RowDefinition Height="*"/> <!-- Звездочка (пропорционально) -->
<RowDefinition Height="50"/> <!-- Фиксированная высота -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!-- Элементы -->
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Content="Заголовок"/>
<TextBox Grid.Row="1" Grid.Column="0"/>
<Button Grid.Row="1" Grid.Column="1" Content="OK"/>
<StatusBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Заголовок -->
<TextBlock Grid.Row="0" Text="Калькулятор"
FontSize="24" HorizontalAlignment="Center"/>
<!-- Кнопки (5 строк по 4 столбца) -->
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition/><RowDefinition/>
<RowDefinition/><RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/><ColumnDefinition/>
<ColumnDefinition/><ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Цифры -->
<Button Grid.Row="0" Grid.Column="0" Content="7"/>
<Button Grid.Row="0" Grid.Column="1" Content="8"/>
<Button Grid.Row="0" Grid.Column="2" Content="9"/>
<Button Grid.Row="0" Grid.Column="3" Content="/"/>
<!-- ... остальные кнопки -->
</Grid>
<!-- Результат -->
<TextBox Grid.Row="2" Text="0" FontSize="20" Margin="10"/>
</Grid>
Создай интерфейс калькулятора с помощью Grid. Добавь цифры 0-9 и операции +, -, *, /. Пока результат просто выводи в TextBox.
📌 Дополнительно: Добавь кнопку "C" для очистки.
Отдохните, разомнитесь, обсудите пройденный материал
✅ XAML - язык разметки
✅ Элементы управления
✅ Панели разметки (StackPanel, Grid)
✅ События и обработчики
Сегодня узнаем:
✅ Привязка данных (Data Binding)
✅ Стили и триггеры
✅ Шаблоны элементов
✅ MVVM паттерн
<!-- XAML -->
<TextBox Text="{Binding UserName}" />
<Label Content="{Binding UserName}" />
// C# (ViewModel)
public class MainViewModel
{
private string _userName;
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged(); // Оповещаем UI об изменении!
}
}
}
// Базовый класс для ViewModel
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Упрощенный метод для установки значений
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
// Использование
public class PersonViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}
<Window x:Class="BindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Binding Demo" Height="300" Width="400">
<Window.DataContext>
<local:PersonViewModel/>
</Window.DataContext>
<StackPanel Margin="20">
<Label Content="Имя:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Возраст:"/>
<TextBox Text="{Binding Age}"/>
<Label Content="Приветствие:"/>
<Label Content="{Binding Greeting}" FontWeight="Bold"/>
<Button Content="Обновить" Command="{Binding UpdateCommand}" Margin="0,20,0,0"/>
</StackPanel>
</Window>
Создай приложение с TextBox (ввод имени) и Label (отображает "Привет, [имя]!"). При вводе текста Label обновляется автоматически.
📌 Подсказка: Используй UpdateSourceTrigger=PropertyChanged
Вместо события Click - используем команды! Так код становится чище.
// RelayCommand - простая реализация ICommand
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func _canExecute;
public RelayCommand(Action execute, Func canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
// Использование в ViewModel
public class MainViewModel : ViewModelBase
{
public ICommand SaveCommand { get; }
public MainViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
private bool CanSave() => !string.IsNullOrEmpty(Name);
private void Save() => MessageBox.Show("Сохранено!");
}
<!-- Определение стиля -->
<Window.Resources>
<Style x:Key="ModernButton" TargetType="Button">
<Setter Property="Background" Value="#667eea"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#764ba2"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- Применение стиля -->
<Button Style="{StaticResource ModernButton}" Content="Нажми меня!"/>
Создай стили для:
Примени стили к форме входа (Логин, Пароль, Кнопка "Войти").
📌 Подсказка: Используй CornerRadius для скругления углов кнопки (нужно добавить xmlns:sys="clr-namespace:System;assembly=mscorlib").
Стили меняют свойства, а шаблоны полностью меняют внешний вид элемента!
<!-- Шаблон для кнопки -->
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="10"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Шаблон для ListBoxItem -->
<DataTemplate x:Key="PersonTemplate">
<StackPanel Orientation="Horizontal">
<Ellipse Width="40" Height="40" Fill="{Binding AvatarColor}"/>
<StackPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Age}" FontSize="10"/>
</StackPanel>
</StackPanel>
</DataTemplate>
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Person> _people;
private Person _selectedPerson;
public MainViewModel()
{
LoadCommand = new RelayCommand(LoadPeople);
AddCommand = new RelayCommand(AddPerson);
DeleteCommand = new RelayCommand(DeletePerson, CanDelete);
_people = new ObservableCollection<Person>();
}
public ObservableCollection<Person> People
{
get => _people;
set => SetProperty(ref _people, value);
}
public Person SelectedPerson
{
get => _selectedPerson;
set => SetProperty(ref _selectedPerson, value);
}
public ICommand LoadCommand { get; }
public ICommand AddCommand { get; }
public ICommand DeleteCommand { get; }
private void LoadPeople()
{
People.Clear();
foreach(var p in Database.GetPeople())
People.Add(p);
}
}
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="MVVM Demo" Height="450" Width="600">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Список людей -->
<DataGrid Grid.Row="0"
ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="50"/>
<DataGridTextColumn Header="Имя" Binding="{Binding Name}" Width="150"/>
<DataGridTextColumn Header="Возраст" Binding="{Binding Age}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
<!-- Кнопки управления -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Загрузить" Command="{Binding LoadCommand}"
Width="100" Margin="5" Padding="8"/>
<Button Content="Добавить" Command="{Binding AddCommand}"
Width="100" Margin="5" Padding="8"/>
<Button Content="Удалить" Command="{Binding DeleteCommand}"
Width="100" Margin="5" Padding="8"/>
</StackPanel>
</Grid>
</Window>
Требования:
// TaskItem.cs (Model)
public class TaskItem : ViewModelBase
{
private bool _isCompleted;
public int Id { get; set; }
public string Title { get; set; }
public bool IsCompleted
{
get => _isCompleted;
set => SetProperty(ref _isCompleted, value);
}
}
// MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private ObservableCollection<TaskItem> _tasks;
private string _newTaskTitle;
public ObservableCollection<TaskItem> Tasks { get; set; }
public string NewTaskTitle { get; set; }
public ICommand AddTaskCommand { get; }
public ICommand DeleteTaskCommand { get; }
public int CompletedCount => Tasks.Count(t => t.IsCompleted);
public int TotalCount => Tasks.Count;
}
Что вы узнали за 4 часа:
🙏 Спасибо за внимание! Вопросы?