NeuroAgent

Avalonia UI Binding Not Working: Causes and Solutions

Fix data binding issues in Avalonia UI. Learn why UI doesn't update when using Community MVVM Toolkit and how to properly implement the MVVM pattern.

Question

Why isn’t two-way binding working in Avalonia UI?

I’m trying to understand the MVVM pattern, particularly in Avalonia UI. For implementing Notify, I decided to use Community MVVM Toolkit. I created [ObservableProperty] attributes, but the values in the UI don’t change. Default bindings work, but values assigned from code don’t display.

At the same time, commands from buttons execute and changes to field values are visible in the debugger. However, there are no changes in the UI.

Here’s my code:

ViewModel (MainWindowViewModel.cs):

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--;
    }
}

XAML (MainWindow.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>

Where is the error? How should this be done correctly?

NeuroAgent

UI Update Issues in Avalonia UI with Community MVVM Toolkit

UI update problems in Avalonia UI when using Community MVVM Toolkit typically arise from incorrect property bindings in XAML and improper use of the [ObservableProperty] attribute. Let’s analyze and fix your code.

Your values aren’t updating in the UI because you’re binding to private fields instead of the generated properties, and you’re not notifying about changes to certain values.


Contents


Main Issues in Your Code

  1. Incorrect XAML Bindings: You’re binding to private fields (_Counts, _Chislo) instead of the generated public properties (Counts, Chislo)

  2. Missing Change Notifications: The RandomValue property doesn’t implement INotifyPropertyChanged

  3. Direct Field Value Assignment: Instead of using generated properties, you’re modifying private fields directly

  4. Incorrect Command Implementation: Not using RelayCommand from Community MVVM Toolkit


ViewModel Correction

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;
    
    // Correct: [ObservableProperty] creates a public property named Counts
    [ObservableProperty]
    private int counts; // Private field with lowercase
    
    [ObservableProperty]
    private int chislo; // Private field with lowercase
    
    // This property should also support notifications
    [ObservableProperty]
    private int randomValue;
    
    [ObservableProperty]
    private string message;
    
    // Commands should be implemented through 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 = "You guessed it";
            return;
        }

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

        Counts--;
    }
}

Key Changes:

  • Renamed _Countscounts and _Chislochislo (lowercase)
  • Added [ObservableProperty] for randomValue
  • Used [RelayCommand] instead of directly creating commands
  • Changed property references in methods (e.g., RandomValue instead of RandomValue)

XAML Binding Correction

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 is only for IDE preview -->
        <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>
                <!-- 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="Enter your guess"></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="Message to user"></TextBlock>
                <!-- This binding was already correct -->
                <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>

Main Corrections:

  • Removed Mode=TwoWay from IsVisible (this attribute doesn’t support TwoWay)
  • Changed bindings: {Binding Counts} and {Binding Chislo} instead of _Counts and _Chislo
  • Fixed commands to {Binding GenerateRandomCommand} and {Binding CheckInputCommand}

Important MVVM Work Points

1. Working with ObservableProperty Rules

csharp
// Correct - [ObservableProperty] creates a public property
[ObservableProperty]
private string userName; // Private field

// Generated automatically:
public string UserName 
{
    get => userName;
    set => SetProperty(ref userName, value);
}

2. Correct XAML Bindings

xml
<!-- Binding to generated property -->
<TextBlock Text="{Binding UserName}" />

<!-- Binding to command -->
<Button Command="{Binding SaveCommand}" />

3. Setting DataContext

csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        
        // Set DataContext in code
        DataContext = new MainWindowViewModel();
    }
}

Complete Corrected Code

ViewModel

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

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

        Counts--;
    }
}

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">

    <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}">
                <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 GenerateRandomCommand}"></Button>
            <Button FontSize="21" HorizontalAlignment="Stretch" Content="Check your luck" IsVisible="{Binding IsAttempt}"
                    Command="{Binding CheckInputCommand}"></Button>
        </StackPanel>
    </Grid>

</Window>

Conclusion

  1. The main issue was incorrect bindings - you were using private field names instead of generated public property names

  2. Community MVVM Toolkit automatically creates properties from fields with the [ObservableProperty] attribute, following this rule:

    • Field: private int counts;
    • Property: public int Counts { get; set; }
  3. Always use generated properties in code and XAML, not private fields

  4. Commands should be created through the [RelayCommand] attribute, which will automatically generate properties with the Command suffix

  5. Ensure DataContext is set correctly in the window’s constructor code

After these corrections, your UI should properly respond to value changes in the ViewModel.