Skip to content

ListBox with Virtualization Causes InvalidOperationException and UI Freezing in MaterialDesignThemes 5.2.0 #3790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
keeleycenc opened this issue Feb 11, 2025 · 2 comments
Labels
bug evaluation required Items is pending review or evaluation by the team Stale Issues and PRs that have not had activity for a while are marked with this label. Waiting on feedback Additional information is needed. Stale items with this label may be closed.

Comments

@keeleycenc
Copy link

Bug explanation

Bug Report: Issue with MaterialDesignThemes Version 5.2.0

Environment:

  • MaterialDesignThemes Version: 5.2.0
  • WPF: .NET 8.0
  • OS: Windows 11 Professional 23H2 22631.4751
  • CPU: Intel(R) Core(TM) i7-14700KF 3.40 GHz
  • RAM: 32.0 GB

Description

After upgrading to MaterialDesignThemes version 5.2.0 from 5.1.0, the following issues have occurred:

  1. UI Freezing/Blocking: The application experiences a brief UI freezing when auto-scrolling and removing items from the ListBox that uses virtualization.
  2. System.InvalidOperationException: The following exception occurs intermittently when auto-scrolling a ListBox:
System.InvalidOperationException: "Cannot call StartAt when content generation is in progress."

Steps to Reproduce

  1. Upgrade MaterialDesignThemes to version 5.2.0.
  2. Use a ListBox with virtualization enabled (e.g., VirtualizingStackPanel.VirtualizationMode="Recycling").
  3. Bind a collection (such as InstantMessage) to the ItemsSource of the ListBox.
  4. Implement auto-scrolling when new messages are added (e.g., via ScrollIntoView in the ViewModel_OnMessageUpdated method).
  5. Remove items from the ListBox (e.g., by updating the collection).
  6. Experience brief UI freezing and occasional System.InvalidOperationException.

Code Example

<ListBox
 Height="300"                    
 x:Name="MyInstantMessageListBox"
 ItemContainerStyle="{StaticResource CustomListBoxItemStyle}"
 Template="{StaticResource CustomListBoxTemplate}"
 Grid.Row="1" Grid.ColumnSpan="2"
 Background="Transparent"
 ItemsSource="{Binding InstantMessage}"
 VirtualizingStackPanel.IsVirtualizing="True"
 VirtualizingStackPanel.VirtualizationMode="Recycling">
 <ListBox.ItemsPanel>
     <ItemsPanelTemplate>
         <VirtualizingStackPanel />
     </ItemsPanelTemplate>
 </ListBox.ItemsPanel>
 <ListBox.ItemTemplate>
     <DataTemplate>
         <StackPanel 
             Orientation="Horizontal" 
             VerticalAlignment="Center">
             <TextBlock 
                 Foreground="{DynamicResource TextFillColorPrimaryBrush}"
                 Text="{Binding Timestamp, StringFormat='HH:mm:ss.fff'}" 
                 VerticalAlignment="Center" Width="95"/>
             <md:PackIcon
                 Margin="0, 0, 5, 0"
                 Kind="{Binding Level, Converter={StaticResource InfoLevelToIconKindConverter}}" 
                 Foreground="{Binding Level, Converter={StaticResource InfoLevelToIconColorConverter}}"
                 VerticalAlignment="Center"/>
             <TextBox 
                 Text="{Binding Message}" VerticalAlignment="Center"
                 IsReadOnly="True" BorderThickness="0" Background="Transparent" TextWrapping="Wrap"
                 TextAlignment="Left" HorizontalAlignment="Stretch"/>
         </StackPanel>
     </DataTemplate>
 </ListBox.ItemTemplate>
 <ListBox.Resources>
     <ResourceDictionary>
         <ResourceDictionary.MergedDictionaries>
             <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
         </ResourceDictionary.MergedDictionaries>
         <Style TargetType="ListBox">
             <Setter Property="BorderBrush" Value="{DynamicResource MasBorderBrush}"/>
             <Setter Property="BorderThickness" Value="0,0,0,1"/>
         </Style>
     </ResourceDictionary>
 </ListBox.Resources>
</ListBox>
private void ViewModel_OnMessageUpdated(object? sender, EventArgs e) {
    try {
        if (_isMsgAutoScroll && InstantMessageListBox.Items.Count > 0) {
            InstantMessageListBox.ScrollIntoView(InstantMessageListBox.Items[^1]);
        }
    } catch {
        // BUG: ListBox virtualization mode + auto-scrolling error: System.InvalidOperationException:“Cannot call StartAt when content generation is in progress.”
    }
}

Expected Behavior

The application should smoothly auto-scroll the ListBox when new items are added, even if items are removed simultaneously, without causing UI freezing or throwing exceptions.

Actual Behavior

  • UI Freezing/Blocking: The UI experiences brief freezing when auto-scrolling and removing items from the ListBox with virtualization enabled.
  • Exception: The InvalidOperationException is thrown intermittently with the message: "Cannot call StartAt when content generation is in progress."

Additional Notes

  • The issue only occurs after upgrading to version 5.2.0.
  • The problem does not occur in version 5.1.0, which was working fine with the same code and configuration.
  • Using delay scheduling does not resolve the issue.
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
    InstantMessageListBox.ScrollIntoView(InstantMessageListBox.Items[^1]);
}), DispatcherPriority.Background);
  • After removing the MaterialDesign2 resource from ListBox.Resources, the issue disappears, Remove the following code:
<ListBox.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
        </ResourceDictionary.MergedDictionaries>
        <Style TargetType="ListBox">
            <Setter Property="BorderBrush" Value="{DynamicResource MasBorderBrush}"/>
            <Setter Property="BorderThickness" Value="0,0,0,1"/>
        </Style>
    </ResourceDictionary>
</ListBox.Resources>
  • Queue Updates Every Second: The queue updates every second, with a maximum of 15 items per cycle. The performance is not an issue, and this points to a potential issue with MaterialDesignThemes, rather than the performance of the application.

Screenshot

Image

Version

5.2.0

@keeleycenc keeleycenc added bug evaluation required Items is pending review or evaluation by the team labels Feb 11, 2025
@nicolaihenriksen
Copy link
Contributor

@keeleycenc Thank you so much for the very detailed issue description!! Awesome work!

However, even with all that in hand, I am unable to reproduce the issue 😢

I have created a standard WPF application (net8.0-windows) referencing MaterialDesignThemes.Wpf (and CommunityToolkit.Mvvm for easy access to "MVVM features"). I have more or less just dumped your ListBox code from above into the Window and that I am abusing the code-behind as a view model and background job for simplicity. The window shows the ListBox which continuously scrolls to the last added item, as well as 2 buttons to remove the first/last item respectively (as your description indicates this is needed to reproduce the issue).

I have tried 2 variations of the view model property "InstantMessages" (InstantMessages and InstantMessages2), the former being an ObservableCollection<InstantMessage> and the latter a simple List<InstantMessage>. Both seem to work in my case (the former just has the requirement that it must be modified from the UI thread only). The catch block is never hit in either case.

I had to remove the ListBox.ItemContainerStyle and ListBox.Template values you are setting, as I don't have your styles/templates. I also replaced a couple of your bindings (using converters) with hardcoded values as I don't have your converters either.

Are you able to modify the code below to make it reproduce the issue you're facing?

MainWindow.xaml

<Window x:Class="Issue3790.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Issue3790" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        mc:Ignorable="d"
        x:Name="_root"
        Title="MainWindow" Height="450" Width="800">
  <Grid>
    <ListBox Height="300"
             VerticalAlignment="Center"
             HorizontalAlignment="Center"
             x:Name="MyInstantMessageListBox"
             Grid.Row="1" Grid.ColumnSpan="2"
             Background="Transparent"
             ItemsSource="{Binding ElementName=_root, Path=InstantMessages2}"
             VirtualizingStackPanel.IsVirtualizing="True"
             VirtualizingStackPanel.VirtualizationMode="Recycling">
      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <VirtualizingStackPanel />
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal"
                      VerticalAlignment="Center">
            <TextBlock Foreground="Red"
                       Text="{Binding Timestamp, StringFormat='HH:mm:ss.fff'}"
                       VerticalAlignment="Center" Width="95"/>
            <materialDesign:PackIcon Margin="0, 0, 5, 0"
                                     Kind="Github" 
                                     Foreground="Blue"
                                     VerticalAlignment="Center"/>
            <TextBox Text="{Binding Message}"
                     VerticalAlignment="Center" Foreground="Red"
                     IsReadOnly="True" BorderThickness="0" Background="Transparent" TextWrapping="Wrap"
                     TextAlignment="Left" HorizontalAlignment="Stretch"/>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
      <ListBox.Resources>
        <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
          </ResourceDictionary.MergedDictionaries>
          <Style TargetType="ListBox">
            <Setter Property="BorderBrush" Value="{DynamicResource MasBorderBrush}"/>
            <Setter Property="BorderThickness" Value="0,0,0,1"/>
          </Style>
        </ResourceDictionary>
      </ListBox.Resources>
    </ListBox>
    <Button HorizontalAlignment="Left" VerticalAlignment="Bottom" Content="Remove First" Click="RemoveFirstButton_Click" />
    <Button HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Remove Last" Click="RemoveLastButton_Click" />
  </Grid>
</Window>

MainWindow.xaml.cs

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;

namespace Issue3790;

[ObservableObject]
public partial class MainWindow : Window
{
    // "ViewModel" property using ObservableCollection<T>
    public ObservableCollection<InstantMessage> InstantMessages { get; } = [];

    // "ViewModel" property using plain List<T>
    [ObservableProperty]
    private List<InstantMessage> _instantMessages2 = [];

    public MainWindow()
    {
        InitializeComponent();
        Task.Run(() => AddMessages());
    }

    private async Task AddMessages()
    {
        while (true)
        {
            InstantMessage msg = new() { Timestamp = DateTime.Now, Message = "This is a message" };

            // This line allocates a brand new list, and invokes INotifyPropertyChanged.PropertyChanged via the [ObservableProperty] codegen from CommunityToolkit.Mvvm
            InstantMessages2 = new List<InstantMessage>([.. (InstantMessages2 ?? []), msg]);
                
            await Dispatcher.BeginInvoke(() =>
            {
                // This line adds the message to the ObservableCollection (and thus needs to be executed on the UI thread)
                //InstantMessages.Add(msg);

                try
                {
                    MyInstantMessageListBox.ScrollIntoView(MyInstantMessageListBox.Items[^1]);
                }
                catch
                {
                    // BUG: ListBox virtualization mode + auto-scrolling error: System.InvalidOperationException:“Cannot call StartAt when content generation is in progress.”
                }
            });
                
            await Task.Delay(50);
        }
    }

    private void RemoveFirstButton_Click(object sender, RoutedEventArgs e)
    {
        if (InstantMessages.Count > 0)
            InstantMessages.RemoveAt(0);
    }

    private void RemoveLastButton_Click(object sender, RoutedEventArgs e)
    {
        if (InstantMessages.Count > 0)
            InstantMessages.RemoveAt(InstantMessages.Count - 1);
    }
}

public class InstantMessage
{
    public DateTime Timestamp { get; set; }
    public string? Message { get; set; }
}

@nicolaihenriksen nicolaihenriksen added the Waiting on feedback Additional information is needed. Stale items with this label may be closed. label Apr 14, 2025
Copy link
Contributor

This issue is marked stale because it has been open 30 days with no activity. Remove stale label or update the issue, otherwise it will be closed in 14 days.

@github-actions github-actions bot added the Stale Issues and PRs that have not had activity for a while are marked with this label. label May 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug evaluation required Items is pending review or evaluation by the team Stale Issues and PRs that have not had activity for a while are marked with this label. Waiting on feedback Additional information is needed. Stale items with this label may be closed.
Projects
None yet
Development

No branches or pull requests

2 participants