Почему не работает обратная привязка в Avalonia UI? Я пытаюсь разобраться с паттерном MVVM и Avalonia UI в частности. Для реализации Notify решил использовать Community MVVM Toolkit. Создал [ObservableProperty]. Однако значения в UI не изменяются. Дефолтные биндинги работают, а вот присвоенные из кода значения не отображаются. При этом команды с кнопок выполняются и в дебаггере видно изменения значений полей. Однако никаких изменений в UI нет. Где ошибка? Как правильно это сделать?
Вот мой код:
MainWindowsViewModel
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--;
}
}
MainWindows.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>
Проблема с обратной привязкой в вашем коде связана с несколькими ключевыми моментами в реализации MVVM с использованием Community MVVM Toolkit в Avalonia UI.
Основная причина, по которой UI не обновляется при изменении значений из кода, заключается в том, что ваш ViewModelBase, скорее всего, не правильно реализует интерфейс INotifyPropertyChanged или не использует необходимые базовые классы из CommunityToolkit.Mvvm.
Содержание
- Основные проблемы в вашем коде
- Правильная реализация ViewModel
- Настройка биндингов в XAML
- Решение распространенных проблем
- Дополнительные рекомендации
- Полный исправленный код
- Заключение
Основные проблемы в вашем коде
-
Наследование от неправильного базового класса
ВашMainWindowViewModelнаследуется отViewModelBase, но для работы с[ObservableProperty]необходимо наследоваться отObservableObjectиз CommunityToolkit.Mvvm. -
Неправильное именование полей
Для атрибута[ObservableProperty]имена полей должны соответствовать соглашениям naming convention, иначе генератор кода не сможет правильно создать свойства. -
Отсутствие правильной реализации INotifyPropertyChanged
Без правильного базового класса система не сможет уведомлять UI об изменениях свойств.
Правильная реализация ViewModel
Вам нужно исправить базовый класс и реализацию ViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PrakN.ViewModels;
// Наследуемся от ObservableObject, а не от ViewModelBase
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private bool _isCountChange = true;
[ObservableProperty]
private bool _isAttempt = false;
[ObservableProperty]
private bool _isAttemptAllow;
[ObservableProperty]
private int _counts;
[ObservableProperty]
private int _chislo;
public int RandomValue { get; private set; }
[ObservableProperty]
private string _message;
[RelayCommand]
public void GenerateRandom()
{
Random rnd = new Random();
RandomValue = rnd.Next(1, 10);
IsCountChange = false;
IsAttempt = true;
// Эти изменения должны автоматически уведомить UI
}
[RelayCommand]
public void CheckInput()
{
if (Chislo == RandomValue)
{
Message = "Вы угадали";
return;
}
if (Counts == 0)
{
IsAttemptAllow = false;
return;
}
Counts--;
// Это изменение также должно обновить UI
}
}
Важно: Обратите внимание, что я переименовал методы и свойства, чтобы соответствовать соглашениям C# (PascalCase для публичных членов).
Настройка биндингов в XAML
Ваш XAML в целом правильный, но есть несколько улучшений:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PrakN.ViewModels"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://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>
<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, Mode=TwoWay}"></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, Mode=TwoWay}" 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>
Решение распространенных проблем
1. Проблема с DataContext
Убедитесь, что в App.axaml.cs или конструкторе MainWindow вы правильно устанавливаете DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Правильная установка DataContext
this.DataContext = new MainWindowViewModel();
}
}
2. Проблема с генерацией кода
Убедитесь, что у вас установлены необходимые пакеты NuGet:
CommunityToolkit.MvvmAvalonia
И что в файле .csproj включен генератор исходного кода:
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Avalonia" Version="11.0.10" />
</ItemGroup>
3. Проблема с потоками
Если изменения происходят в потоке отличном от UI-потока, используйте Dispatcher:
[RelayCommand]
public void GenerateRandom()
{
Task.Run(() =>
{
Random rnd = new Random();
var newValue = rnd.Next(1, 10);
Avalonia.Threading.UIThread.MainDispatcher.Post(() =>
{
RandomValue = newValue;
IsCountChange = false;
IsAttempt = true;
});
});
}
Дополнительные рекомендации
- Используйте [RelayCommand] для команд вместо публичных методов
- Проверьте режимы биндинга: для TextBox используйте
Mode=TwoWay, для TextBlock -OneWay - Добавьте отладку биндингов в Avalonia для диагностики проблем
- Используйте nameof() для повышения надежности кода
// Вместо жестко закодированных строк
OnPropertyChanged("Counts");
// Используйте nameof()
OnPropertyChanged(nameof(Counts));
Полный исправленный код
MainWindowViewModel.cs:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PrakN.ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private bool _isCountChange = true;
[ObservableProperty]
private bool _isAttempt = false;
[ObservableProperty]
private bool _isAttemptAllow;
[ObservableProperty]
private int _counts;
[ObservableProperty]
private int _chislo;
public int RandomValue { get; private set; }
[ObservableProperty]
private string _message;
public MainWindowViewModel()
{
// Инициализация по умолчанию
Counts = 3; // Например, 3 попытки по умолчанию
}
[RelayCommand]
public void GenerateRandom()
{
Random rnd = new Random();
RandomValue = rnd.Next(1, 10);
IsCountChange = false;
IsAttempt = true;
Message = "Угадайте число от 1 до 10";
}
[RelayCommand]
public void CheckInput()
{
if (Chislo == RandomValue)
{
Message = "Вы угад!";
IsAttempt = false;
return;
}
if (Counts <= 1)
{
IsAttemptAllow = false;
Message = $"Вы не угадали. Загаданное число было {RandomValue}";
return;
}
Counts--;
Message = $"Неверно. Осталось попыток: {Counts}";
}
}
Заключение
Основные проблемы в вашем коде были:
- Неправильное наследование - нужно использовать
ObservableObjectвместоViewModelBase - Отсутствие атрибутов [RelayCommand] - команды должны быть помечены этим атрибутом
- Потенциальные проблемы с DataContext - убедитесь, что он правильно установлен
После исправления этих проблем привязки должны работать корректно, и UI будет обновляться при изменении свойств в ViewModel. Если проблема останется, проверьте наличие ошибок в окне “Output” Visual Studio и убедитесь, что генератор исходного кода CommunityToolkit.Mvvm работает правильно.