AvaloniaUI / Avalonia.Controls.ItemsRepeater

ItemsRepeater is a light-weight control to generate and present a collection of items.
MIT License
3 stars 1 forks source link

WrapLayout caching incorrect when source collection is changed #6

Open moon6969 opened 3 years ago

moon6969 commented 3 years ago

Description WrapLayout is not updating correctly when source collection is changed.

Consider an ItemsRepeater bound to a changing source collection "CurrentNode.Things". With <UniformGridLayout /> the ItemsRepeater displays the correct items from "CurrentNode.Things" as CurrentNode changes.

If I change to <WrapLayout />, the number of displayed items updates correctly, but any items from the previous source are displayed instead.

In the example below, the UniformGridLayout is left, and WrapLayout on the right... WrapLayout

To Reproduce

    public class Thing
    {
        public string? Name { get; set; }
    }

    public class FolderNode
    {
        public ObservableCollection<Thing> Things { get; set; } = new ObservableCollection<Thing>();

        public FolderNode(List<string> Names)
        {
            foreach (string c in Names)
            {
                Things.Add(new Thing { Name = c });
            }
        }
    }
    public class MainWindowViewModel : ViewModelBase
    {
        private FolderNode _currentNode = default!;
        public FolderNode CurrentNode
        {
            get => _currentNode;
            set
            {
                this.RaiseAndSetIfChanged(ref _currentNode, value);
            }
        }

        ObservableCollection<FolderNode> Folders { get; set; } = new ObservableCollection<FolderNode>();

        public ICommand SelectFolderCommand { get; }

        public MainWindowViewModel()
        {
            Folders.Add(new FolderNode(new List<string>()));
            Folders.Add(new FolderNode(new List<string>() { "A", "B" }));
            Folders.Add(new FolderNode(new List<string>() { "D", "E", "F" }));

            CurrentNode = Folders[0];

            SelectFolderCommand = ReactiveCommand.Create<string>(param =>
            {
                CurrentNode = Folders[int.Parse(param)];
            });
        }
    }
  <Grid ColumnDefinitions="Auto,1*,1*">

    <StackPanel Grid.Column="0">
      <Button Command="{Binding Path=SelectFolderCommand}" CommandParameter="0">0</Button>
      <Button Command="{Binding Path=SelectFolderCommand}" CommandParameter="1">2</Button>
      <Button Command="{Binding Path=SelectFolderCommand}" CommandParameter="2">3</Button>
    </StackPanel>

    <ItemsRepeater Grid.Column="1" Items="{Binding Path=CurrentNode.Things}" Background="Bisque">
      <ItemsRepeater.ItemTemplate>
        <DataTemplate>
          <TextBox Text="{Binding Path=Name}" HorizontalAlignment="Left" />
        </DataTemplate>
      </ItemsRepeater.ItemTemplate>
      <ItemsRepeater.Layout>
        <UniformGridLayout />
      </ItemsRepeater.Layout>
    </ItemsRepeater>

    <ItemsRepeater Grid.Column="2" Items="{Binding Path=CurrentNode.Things}" Background="AliceBlue">
      <ItemsRepeater.ItemTemplate>
        <DataTemplate>
          <TextBox Text="{Binding Path=Name}" />
        </DataTemplate>
      </ItemsRepeater.ItemTemplate>
      <ItemsRepeater.Layout>
        <WrapLayout />
      </ItemsRepeater.Layout>
    </ItemsRepeater>

  </Grid>

Expected behavior WrapLayout should display the correct items from source!

Desktop:

KaddaOK commented 9 months ago

In case anyone else comes across this issue:

<ItemsControl> with a <WrapPanel> doesn't seem to suffer from the same issue as <ItemsRepeater> with a <WrapLayout>.

I'm not sure if you lose anything like virtualization support or what not when you make the switch, but if ItemsRepeater and ItemsControl are interchangeable in your case, just change

    <ItemsRepeater>
      <ItemsRepeater.Layout>
        <WrapLayout />
      </ItemsRepeater.Layout>
    </ItemsRepeater>

to

    <ItemsControl>
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel />
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
    </ItemsControl>

and you're good. Or at least, I was, so far. 🤷‍♂️