Search code examples
c#.net-coreavaloniauiavalonia

Binding a list of rectangles to a canvas in AvaloniaUI


I am trying to bind an observable collection of rectangles to an items control with a canvas as the item panel, but there seems to be no way of binding the Canvas.Left and Canvas.Top properties to the Rectangle items. I attempted to do this by adding a ItemsControl.ItemContainerStyle like one would in WPF, but this doesn't seem to exist in Avalonia. I searched and found this: https://github.com/AvaloniaUI/Avalonia/discussions/10018 However, I couldn't find any reference to ItemContainerTheme on the Avalonia documentation.

This is what I have currently:

<ItemsControl Grid.Row="1" Items="{Binding Rectangles}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding Path=X}" />
            <Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Rectangle Fill="Red" Width="{Binding Width}" Height="{Binding Height}"></Rectangle>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This is the view model for each item that the items template is binding to:

public class RectangleViewModel : ViewModelBase
{
    private int _x = 0;
    public int X
    {
        get => _x;
        set => this.RaiseAndSetIfChanged(ref _x, value);
    }

    private int _y = 0;
    public int Y
    {
        get => _y;
        set => this.RaiseAndSetIfChanged(ref _y, value);
    }

    private int _width = 0;
    public int Width
    {
        get => _width;
        set => this.RaiseAndSetIfChanged(ref _width, value);
    }

    private int _height = 0;
    public int Height
    {
        get => _height;
        set => this.RaiseAndSetIfChanged(ref _height, value);
    }
}

And this is my Rectangles collection:

public ObservableCollection<RectangleViewModel> Rectangles { get; } = new();

Solution

  • Solved. I managed to find a somewhat related Github issue here https://github.com/AvaloniaUI/Avalonia/issues/2302 that uses the attached Styles attribute to add the Canvas.Left and Canvas.Top properties. The fixed result:

    <ItemsControl Grid.Row="1" Items="{Binding Rectangles}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    
        <ItemsControl.Styles>
            <Style Selector="ItemsControl > ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}"/>
                <Setter Property="Canvas.Top" Value="{Binding Y}"/>
            </Style>
        </ItemsControl.Styles>
    
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Stroke="#00FF00" StrokeThickness="2" Fill="Transparent" Width="{Binding Width}" Height="{Binding Height}"></Rectangle>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>