-
I'm new to Avalonia and I'm struggling quite a bit with the concept of data bindings and property change events. I have this little sample application with an ObservableCollection of Things and a custom control that binds to that collection. There is also a button that, when clicked, adds an element to the collection. Now, I cannot figure out how to make the custom control react to changes in the collection instantaneously, meaning it should redraw itself as soon as I click the button. I tried to override the OnPropertyChanged event in the custom control but that does not really work. It redraws the control only when I mouse-over the rendered output. It seems that I need to implement the CollectionChanged event in the custom control but I don't know how or where exactly. There is no method I can override. MainWindow.axaml: <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:SampleMVVM.ViewModels"
xmlns:cc="using:SampleMVVM.Controls"
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="SampleMVVM.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="SampleMVVM">
<StackPanel Background="Black">
<Button Command="{Binding Clicker}">Click Me!</Button>
<cc:ListDisplay
Things="{Binding Things}"
HorizontalAlignment="Left"
Width="800"
Height="450"/>
</StackPanel>
</Window> MainWindowViewModel.cs: using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SampleMVVM.Models;
namespace SampleMVVM.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private ObservableCollection<Thing> _things = new();
public MainWindowViewModel()
{
Things.Add(new Thing("One", 1));
Things.Add(new Thing("Two", 2));
Things.Add(new Thing("Three", 3));
Things.Add(new Thing("Four", 4));
Things.Add(new Thing("Five", 5));
Things.Add(new Thing("Six", 6));
Things.Add(new Thing("Seven", 7));
}
[RelayCommand]
public void Clicker()
{
var r = new Random();
var value = r.Next(99);
Things.Add(new Thing("Generic", value));
}
} Thing.cs: using CommunityToolkit.Mvvm.ComponentModel;
namespace SampleMVVM.Models;
public partial class Thing : ObservableObject
{
[ObservableProperty]
private string _name;
[ObservableProperty]
private int _value;
public Thing(string name, int value)
{
Name = name;
Value = value;
}
} ListDisplay.cs (this is the custom control): using System.Collections.ObjectModel;
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using SampleMVVM.Models;
namespace SampleMVVM.Controls;
public class ListDisplay : Control
{
private ObservableCollection<Thing> _things;
public static readonly DirectProperty<ListDisplay, ObservableCollection<Thing>> ThingsProperty =
AvaloniaProperty.RegisterDirect<ListDisplay, ObservableCollection<Thing>>(
"Things",
o => o.Things,
(o, v) => o.Things = v);
// This does not work. Fires when mouse is moved over the rendered text
// because 'IsPointerOver' property is changed
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
//Console.WriteLine(change.Property.Name);
InvalidateVisual();
}
public ObservableCollection<Thing> Things
{
get => _things;
set => SetAndRaise(ThingsProperty, ref _things, value);
}
public override void Render(DrawingContext context)
{
int i = 0;
foreach (Thing thing in _things)
{
var brush = new SolidColorBrush(Color.FromRgb(255, 200, 0));
var point = new Point(30, 20 * i);
var typeface = new Typeface("Courier New");
var text = new FormattedText(
$"-> Name: {thing.Name}, Value: {thing.Value}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
20,
brush);
context.DrawText(text, point);
i++;
}
base.Render(context);
}
} I realize that this is probably a stupid beginner question. I really tried to find an answer online and in the official documentation, but just couldn't. Please, can anybody nudge me in the right direction? Also, a little follow-up question: According to the tooltip in Rider, CollectionChanged only seems to fire, when Items are moved, added, deleted, etc. but not when an item (a Thing in this example) changes its values. So I was wondering what I would have to do to make the custom control react to changes in the individual items? Edit: Found out about syntax highlighting and updated my original post to make it look nicer. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
You should use the It could look something like this. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (e.Property == ThingsProperty)
{
var (oldValue, newValue) = e.GetOldAndNewValue<ObservableCollection<Thing>>();
oldValue.CollectionChanged -= OnThingsChanged;
newValue.CollectionChanged += OnThingsChanged;
// OnThingsChanged() or the function that OnThingsChanged would call to handle the initial change
}
base.OnPropertyChanged(change);
} I don't think this is really documented anywhere. I had to work it out by looking though the source code for the builtin controls to see what they did when working on custom controls. For changes to a property of a I assume you need to do some custom rendering for the contents of |
Beta Was this translation helpful? Give feedback.
OnPropertyChanged
will be called whenThings
changes to a different value. As the function is called when an Avalonia property changed. So will only be called when a different collection object is bound toThings
.You should use the
OnPropertyChanged
function to subscribe to theCollectionChanged
event on the collection (make sure you unsubscribe to the event on the previous collection).It could look something like this.