Почему не работает обратная привязка в Avalonia UI?
Я пытаюсь разобраться с паттерном MVVM и Avalonia UI в частности. Для реализации Notify решил использовать Community MVVM Toolkit. Создал [ObservableProperty]. Однако значения в UI не изменяются. Дефолтные биндинги работают, а вот присвоенные из кода значения не отображаются.
При этом команды с кнопок выполняются и в дебаггере видно изменения значений полей. Однако никаких изменений в UI нет.
Вот мой код:
ViewModel (MainWindowViewModel.cs):
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PrakN.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private bool isCountChange = true;
[ObservableProperty]
private bool isAttempt = false;
[ObservableProperty]
private bool isAttemptAllow;
[ObservableProperty]
public int _Counts;
[ObservableProperty]
public int _Chislo;
public int RandomValue { get; private set; }
[ObservableProperty]
private string _Message;
public void GenerateRandow()
{
Random rnd = new Random();
RandomValue = rnd.Next(1, 10);
isCountChange = false;
isAttempt = true;
}
public void CheckInput()
{
if (Chislo == RandomValue)
{
Message = "Вы угадали";
return;
}
if (Counts == 0)
{
isAttemptAllow = false;
return;
}
Counts--;
}
}
XAML (MainWindow.xaml):
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PrakN.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PrakN.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="PrakN">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsEnabled="{Binding IsCountChange }">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите число попыток"></TextBlock>
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Counts}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt, Mode=TwoWay}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите ваш вариант"></TextBlock>
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Chislo}" IsReadOnly="{Binding IsAttemptAllow}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Сообщение пользователю"></TextBlock>
<TextBlock FontSize="21" Width="200" Margin="5" Text="{Binding Message}"></TextBlock>
</StackPanel>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Установить число попыток" IsVisible="{Binding IsCountChange}"
Command="{Binding GenerateRandow}"></Button>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Проверить удачу" IsVisible="{Binding IsAttempt, Mode=TwoWay}"
Command="{Binding CheckInput}"></Button>
</StackPanel>
</Grid>
</Window>
Где ошибка? Как правильно это сделать?
Проблема с обновлением UI в Avalonia UI при использовании Community MVVM Toolkit возникает из-за неправильной привязки свойств в XAML и некорректного использования атрибута [ObservableProperty]. Давайте разберем и исправим ваш код.
Ваши значения не обновляются в UI, потому что вы привязываетесь к приватным полям вместо сгенерированных свойств, а также не уведомляете об изменении некоторых значений.
Contents
- Основные проблемы в вашем коде
- Исправление ViewModel
- Исправление XAML привязок
- Важные моменты работы с MVVM
- Полный исправленный код
- Заключение
Основные проблемы в вашем коде
-
Неправильные привязки в XAML: Вы привязываетесь к приватным полям (
_Counts,_Chislo) вместо сгенерированных публичных свойств (Counts,Chislo) -
Отсутствие уведомлений об изменении: Свойство
RandomValueне реализуетINotifyPropertyChanged -
Прямое присвоение значений полей: Вместо использования сгенерированных свойств вы изменяете приватные поля напрямую
-
Некорректная работа с командами: Не используется
RelayCommandиз Community MVVM Toolkit
Исправление ViewModel
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PrakN.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private bool isCountChange = true;
[ObservableProperty]
private bool isAttempt = false;
[ObservableProperty]
private bool isAttemptAllow;
// Правильно: [ObservableProperty] создает публичное свойство Counts
[ObservableProperty]
private int counts; // Приватное поле с маленькой буквы
[ObservableProperty]
private int chislo; // Приватное поле с маленькой буквы
// Это свойство также должно поддерживать уведомления
[ObservableProperty]
private int randomValue;
[ObservableProperty]
private string message;
// Команды должны быть реализованы через RelayCommand
[RelayCommand]
private void GenerateRandom()
{
Random rnd = new Random();
RandomValue = rnd.Next(1, 10);
IsCountChange = false;
IsAttempt = true;
}
[RelayCommand]
private void CheckInput()
{
if (Chislo == RandomValue)
{
Message = "Вы угадали";
return;
}
if (Counts == 0)
{
IsAttemptAllow = false;
return;
}
Counts--;
}
}
Ключевые изменения:
- Переименовал
_Counts→countsи_Chislo→chislo(с маленькой буквы) - Добавил
[ObservableProperty]дляrandomValue - Использовал
[RelayCommand]вместо прямого создания команд - Изменил обращения к свойствам в методах (например,
RandomValueвместоRandomValue)
Исправление XAML привязок
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PrakN.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PrakN.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="PrakN">
<Design.DataContext>
<!-- Это только для предпросмотра в IDE -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsEnabled="{Binding IsCountChange}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите число попыток"></TextBlock>
<!-- Correct binding to public property Counts -->
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Counts}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите ваш вариант"></TextBlock>
<!-- Correct binding to public property Chislo -->
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Chislo}" IsReadOnly="{Binding IsAttemptAllow}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Сообщение пользователю"></TextBlock>
<!-- This binding was already correct -->
<TextBlock FontSize="21" Width="200" Margin="5" Text="{Binding Message}"></TextBlock>
</StackPanel>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Установить число попыток" IsVisible="{Binding IsCountChange}"
Command="{Binding GenerateRandomCommand}"></Button>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Проверить удачу" IsVisible="{Binding IsAttempt}"
Command="{Binding CheckInputCommand}"></Button>
</StackPanel>
</Grid>
</Window>
Основные исправления:
- Убрал
Mode=TwoWayизIsVisible(этот атрибут не поддерживает TwoWay) - Изменил привязки:
{Binding Counts}и{Binding Chislo}вместо_Countsи_Chislo - Исправил команды на
{Binding GenerateRandomCommand}и{Binding CheckInputCommand}
Важные моменты работы с MVVM
1. Правила работы с ObservableProperty
// Правильно - [ObservableProperty] создает публичное свойство
[ObservableProperty]
private string userName; // Приватное поле
// Генерируется автоматически:
public string UserName
{
get => userName;
set => SetProperty(ref userName, value);
}
2. Правильные привязки в XAML
<!-- Привязка к сгенерированному свойству -->
<TextBlock Text="{Binding UserName}" />
<!-- Привязка к команде -->
<Button Command="{Binding SaveCommand}" />
3. Установка DataContext
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Установка DataContext в коде
DataContext = new MainWindowViewModel();
}
}
Полный исправленный код
ViewModel
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PrakN.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private bool isCountChange = true;
[ObservableProperty]
private bool isAttempt = false;
[ObservableProperty]
private bool isAttemptAllow;
[ObservableProperty]
private int counts;
[ObservableProperty]
private int chislo;
[ObservableProperty]
private int randomValue;
[ObservableProperty]
private string message;
[RelayCommand]
private void GenerateRandom()
{
Random rnd = new Random();
RandomValue = rnd.Next(1, 10);
IsCountChange = false;
IsAttempt = true;
}
[RelayCommand]
private void CheckInput()
{
if (Chislo == RandomValue)
{
Message = "Вы угадали";
return;
}
if (Counts == 0)
{
IsAttemptAllow = false;
return;
}
Counts--;
}
}
XAML
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PrakN.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PrakN.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="PrakN">
<Grid>
<StackPanel HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsEnabled="{Binding IsCountChange}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите число попыток"></TextBlock>
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Counts}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Введите ваш вариант"></TextBlock>
<TextBox FontSize="21" Width="200" Margin="5" Text="{Binding Chislo}" IsReadOnly="{Binding IsAttemptAllow}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IsAttempt}">
<TextBlock FontSize="21" TextAlignment="Right" Margin="5" Text="Сообщение пользователю"></TextBlock>
<TextBlock FontSize="21" Width="200" Margin="5" Text="{Binding Message}"></TextBlock>
</StackPanel>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Установить число попыток" IsVisible="{Binding IsCountChange}"
Command="{Binding GenerateRandomCommand}"></Button>
<Button FontSize="21" HorizontalAlignment="Stretch" Content="Проверить удачу" IsVisible="{Binding IsAttempt}"
Command="{Binding CheckInputCommand}"></Button>
</StackPanel>
</Grid>
</Window>
Заключение
-
Основная проблема была в неправильных привязках - вы использовали имена приватных полей вместо сгенерированных публичных свойств
-
Community MVVM Toolkit автоматически создает свойства из полей с атрибутом
[ObservableProperty], следуя правилу:- Поле:
private int counts; - Свойство:
public int Counts { get; set; }
- Поле:
-
Всегда используйте сгенерированные свойства в коде и XAML, а не приватные поля
-
Команды должны создаваться через атрибут
[RelayCommand], что автоматически сгенерирует свойства с суффиксомCommand -
Убедитесь, что DataContext установлен правильно в коде конструктора окна
После этих исправлений ваш UI должен корректно реагировать на изменения значений в ViewModel.