Search code examples
c#listboxavaloniauiavalonia

Avalonia ListBox throws error when switching back and forth using ContenControl


I'm using

<ListBox ItemsSource="{Binding MyItems}"  
         Height="500" 
         HorizontalAlignment="Stretch"/>

When I populate MyItems from the view model, everything works as expected. But when I then use

<ContentControl Content="{Binding LeftPanel}"/>

Assign LeftPanel to something else and switch back, I get this error:

System.InvalidOperationException: 'An anchor control must be a visual descendent of the ScrollContentPresenter.'

This only happens when I manually fill the list, not changing it (it's blank at the beginning). But I think that's just because there is no scrolling when the list is empty.

I should also mention, the list info is read async and I use this to populate it:

void FillListBox(string[] files)
{
    Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
    {
        CreateListboxItems(files);
    });
}

private void CreateListboxItems(string[] files)
{
    MyItems.Clear();
    int length = files.Length;
    for (int i = 0; i < length; i++)
    {
        ListBoxItem item = CreateListboxItem(files[i]);
        RenderFrameItems.Add(item);
    }
}

ListBoxItem CreateListboxItem(string filePath)
{
    Avalonia.Media.FontStyle fontStyle = File.Exists(filePath) ? Avalonia.Media.FontStyle.Normal : Avalonia.Media.FontStyle.Italic;

    ListBoxItem response = new ListBoxItem()
    {
        Content = filePath,
        FontStyle = fontStyle,
    };

    return response;
}

I also tried putting the list into its own scroll view, but same result.

EDIT: Using ObservableCollection<string> rather than ObservableCollection<ListBoxItem> works, but I don't get the formatting I want


Solution

  • I solved it like this:

    Instead of using ObservableCollection<ListBoxItem> I created my own class:

    public class FileListItem
        {
            public string Filename { get; set; }
            public FontStyle FontStyle { get; set; }
            public FileListItem(string filename, FileStatus status) 
            {
                FontStyle = status == FileStatus.Existing ? FontStyle.Normal : FontStyle.Italic;
                Filename = filename;
            }
        }
    


    In the ViewModel use:

    private ObservableCollection<FileListItem> _fileListItems;
    public ObservableCollection<FileListItem> FileListItems
        {
            get => _fileListItems;
            set => SetProperty(ref _fileListItems, value);
        }
    

    And then used a DataTemplate to format the ListBoxItems.

    <ListBox ItemsSource="{Binding FileListItems}">
            <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Filename}" FontStyle="{Binding FontStyle}"/>
                    </DataTemplate>
            </ListBox.ItemTemplate>
    </ListBox>
    

    I know the original Post did not include enough information, but here is the gist:
    ObservableCollection<ListBoxItem> - which I think is the natural approach - doesn't seem to work. DataTemplateBinding using a custom class does.

    The getter, setter seems to be essential, but I haven't tested removing the private var.
    Complete description: https://docs.avaloniaui.net/docs/guides/data-binding/how-to-bind-to-a-collection