NeuroAgent

Why Binding Doesn't Work in Avalonia UI

Solving binding issues in Avalonia UI when using CommunityToolkit.Mvvm. Learn how to properly implement MVVM pattern and fix UI updates.

Question

Why isn’t reverse binding working in Avalonia UI? I’m trying to understand the MVVM pattern, particularly in Avalonia UI. For implementing INotifyPropertyChanged, I decided to use Community MVVM Toolkit. I’ve created [ObservableProperty] attributes, but the values in the UI don’t change. Default bindings work, but values assigned from code don’t display. Meanwhile, commands from buttons execute and I can see changes to field values in the debugger, but there are no changes in the UI. Where is the error? How should this be done correctly?

Here’s my code:

MainWindowsViewModel

csharp
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 = "You guessed it";
            return;
        }

        if (Counts == 0)
        {
            isAttemptAllow = false;
            return;
        }

        Counts--;
    }
}

MainWindows.xaml

xml
<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="Enter number of attempts"></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="Enter your guess"></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="Message to user"></TextBlock>
                <TextBlock FontSize="21" Width="200" Margin="5" Text="{Binding Message}"></TextBlock>
            </StackPanel>
            <Button FontSize="21" HorizontalAlignment="Stretch" Content="Set number of attempts" IsVisible="{Binding IsCountChange}"
                    Command="{Binding GenerateRandow}"></Button>
            <Button FontSize="21" HorizontalAlignment="Stretch" Content="Check your luck" IsVisible="{Binding IsAttempt, Mode=TwoWay}"
                    Command="{Binding CheckInput}"></Button>
        </StackPanel>
    </Grid>

</Window>
NeuroAgent

Issues with Data Binding in Your Code

The problem with data binding in your code is related to several key aspects of implementing MVVM with the Community MVVM Toolkit in Avalonia UI.

The main reason the UI doesn’t update when values change from your code is that your ViewModelBase likely doesn’t properly implement the INotifyPropertyChanged interface or doesn’t use the necessary base classes from CommunityToolkit.Mvvm.

Table of Contents

Main Problems in Your Code

  1. Inheriting from the wrong base class
    Your MainWindowViewModel inherits from ViewModelBase, but for [ObservableProperty] to work, you need to inherit from ObservableObject from CommunityToolkit.Mvvm.

  2. Incorrect field naming
    For the [ObservableProperty] attribute, field names must follow naming conventions; otherwise, the code generator won’t be able to properly create properties.

  3. Missing proper INotifyPropertyChanged implementation
    Without the correct base class, the system won’t be able to notify the UI about property changes.

Correct ViewModel Implementation

You need to fix the base class and ViewModel implementation:

csharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace PrakN.ViewModels;

// Inherit from ObservableObject, not 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;
        // These changes should automatically notify the UI
    }

    [RelayCommand]
    public void CheckInput()
    {
        if (Chislo == RandomValue)
        {
            Message = "You guessed correctly";
            return;
        }

        if (Counts == 0)
        {
            IsAttemptAllow = false;
            return;
        }

        Counts--;
        // This change should also update the UI
    }
}

Important: Note that I renamed methods and properties to follow C# conventions (PascalCase for public members).

Setting Up Bindings in XAML

Your XAML is mostly correct, but there are a few improvements:

xml
<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="Enter number of attempts"></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="Enter your guess"></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="Message to user"></TextBlock>
                <TextBlock FontSize="21" Width="200" Margin="5" Text="{Binding Message}"></TextBlock>
            </StackPanel>
            <Button FontSize="21" HorizontalAlignment="Stretch" Content="Set number of attempts" 
                    IsVisible="{Binding IsCountChange}" Command="{Binding GenerateRandomCommand}"></Button>
            <Button FontSize="21" HorizontalAlignment="Stretch" Content="Check your luck" 
                    IsVisible="{Binding IsAttempt}" Command="{Binding CheckInputCommand}"></Button>
        </StackPanel>
    </Grid>

</Window>

Solving Common Problems

1. DataContext Issue

Make sure you properly set the DataContext in App.axaml.cs or the MainWindow constructor:

csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        
        // Proper DataContext setup
        this.DataContext = new MainWindowViewModel();
    }
}

2. Code Generation Issue

Ensure you have the necessary NuGet packages installed:

  • CommunityToolkit.Mvvm
  • Avalonia

And that the source code generator is enabled in your .csproj file:

xml
<ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
    <PackageReference Include="Avalonia" Version="11.0.10" />
</ItemGroup>

3. Threading Issue

If changes occur in a thread other than the UI thread, use Dispatcher:

csharp
[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;
        });
    });
}

Additional Recommendations

  1. Use [RelayCommand] for commands instead of public methods
  2. Check binding modes: for TextBox use Mode=TwoWay, for TextBlock use OneWay
  3. Add binding debugging in Avalonia to diagnose issues
  4. Use nameof() for better code reliability
csharp
// Instead of hardcoded strings
OnPropertyChanged("Counts");

// Use nameof()
OnPropertyChanged(nameof(Counts));

Complete Corrected Code

MainWindowViewModel.cs:

csharp
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()
    {
        // Default initialization
        Counts = 3; // For example, 3 attempts by default
    }

    [RelayCommand]
    public void GenerateRandom()
    {
        Random rnd = new Random();
        RandomValue = rnd.Next(1, 10);
        IsCountChange = false;
        IsAttempt = true;
        Message = "Guess a number from 1 to 10";
    }

    [RelayCommand]
    public void CheckInput()
    {
        if (Chislo == RandomValue)
        {
            Message = "You guessed correctly!";
            IsAttempt = false;
            return;
        }

        if (Counts <= 1)
        {
            IsAttemptAllow = false;
            Message = $"You didn't guess. The number was {RandomValue}";
            return;
        }

        Counts--;
        Message = $"Incorrect. Attempts remaining: {Counts}";
    }
}

Conclusion

The main problems in your code were:

  1. Incorrect inheritance - you need to use ObservableObject instead of ViewModelBase
  2. Missing [RelayCommand] attributes - commands should be marked with this attribute
  3. Potential DataContext issues - make sure it’s properly set

After fixing these issues, the bindings should work correctly, and the UI will update when properties change in the ViewModel. If the problem persists, check for errors in the Visual Studio “Output” window and ensure that the CommunityToolkit.Mvvm source code generator is working properly.