Skip to content
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

ListView/GridView scroll performance is O(n) when automation peers are created #9181

Closed
h3xds1nz opened this issue May 30, 2024 · 7 comments · Fixed by #9182
Closed

ListView/GridView scroll performance is O(n) when automation peers are created #9181

h3xds1nz opened this issue May 30, 2024 · 7 comments · Fixed by #9182

Comments

@h3xds1nz
Copy link
Contributor

h3xds1nz commented May 30, 2024

Description

I was running some tests with ListView (using GridView) and when I loaded 1 000 000 items (yes, an obscure use case but that's why we got virtualization, no?), the scrolling performance would degrade and keyboard scrolling felt almost unusable.

During a quick rundown, I've discovered the culprit is in override of GetChildrenCore of GridViewItemAutomationPeer, which performs IndexOf (linear) search in the Items collection of the underlying ItemsSource to find the respective item's Row.

Reproduction Steps

The easiest step to reproduce is to add a ListView with GridView plus a ComboBox, after opening the ComboBox's dropdown (clicking it), Accessibility.dll gets loaded and the party gets started. You can find a demo repository on Github - here.

Behavior

The scrolling performance becomes worse to unusable with increasing number of items.

Known Workarounds

The "workaround" is to subclass GridView, overriding GetAutomationPeer to return NULL. Not really a solution though.

Other information

I have created a pull request at the same time, fixing the respective issue. #9182

@huiliuss
Copy link

huiliuss commented Jun 4, 2024

Displaying large amounts of data in WPF:

Method 1: Simple style (important) + virtualization

Method 2: Paged loading

According to my test, your virtualization does not seem to be enabled successfully. Please make sure your virtualization is correct

The following example uses ListView to load 2,000,000 pieces of data without lag when sliding.

<Grid>

    <Grid.DataContext>

        <vm:MainViewModel x:Name="MyData"></vm:MainViewModel>

    </Grid.DataContext>

    <Grid.Resources>

        <DataTemplate x:Key="MyListBoxItem">

            <StackPanel Orientation="Horizontal">

                <TextBlock Text="{Binding Path=Id}" Margin="20,0,20,0"></TextBlock>

                <TextBlock Text="{Binding Path=Name}" Margin="20,0,20,0"></TextBlock>

                <TextBlock Text="{Binding Path=Email}" Margin="20,0,20,0"></TextBlock>

            </StackPanel>

        </DataTemplate>

    </Grid.Resources>

    <StackPanel Height="400">

        <!--<ListBox VirtualizingPanel.IsVirtualizing="True"  VirtualizingPanel.VirtualizationMode="Recycling" ItemsSource="{Binding Path=Users}" ItemTemplate="{StaticResource MyListBoxItem}" Height="400">


        </ListBox>-->

        <ListView  VirtualizingPanel.IsVirtualizing="True"  VirtualizingPanel.VirtualizationMode="Recycling" ItemsSource="{Binding Path=Users}" ItemTemplate="{StaticResource MyListBoxItem}" Height="400"></ListView>

    </StackPanel>

</Grid>

ViewModel

public class MainViewModel : INotifyPropertyChanged

{

    private ObservableCollection<User> users;

 

    public ObservableCollection<User> Users {  get { return users; }  set { users = value;OnPropertyChanged("Users"); } }

 

    public MainViewModel() {

        Users = GetUsers(2000000);

        MessageBox.Show("data create success");

    }

 

    public ObservableCollection<User> GetUsers(int Count)

    {

        ObservableCollection<User> Users = new ObservableCollection<User>();

        for(int i = 0; i < Count; i++)

        {

            Users.Add(new User() { Id = i,Name="AAAA_"+i,Email="BBBB_"+i }); ; ;

        }     

        return Users;

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    public void OnPropertyChanged(string name)

    {

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    }

}

Model

public class User:INotifyPropertyChanged

{

    private int id;

 

    public int Id { get { return id; } set { id = value;OnPropertyChanged("Id"); } }

 

    private string name;

    public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } }

 

    private string email;  

    public string Email { get { return email; } set { email = value; OnPropertyChanged("Email"); } }

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    public void OnPropertyChanged(string name)

    {

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    }

}

@h3xds1nz
Copy link
Contributor Author

h3xds1nz commented Jun 4, 2024

@huiliuss Thank you for your comment, should you check the reproduction project and the steps included to reproduce the issue reported, you will notice your example is lacking necessary steps to successfully reproduce it. That's why I've included one in the first place.

  1. You do not need to enable virtualization explicitly by setting the attached property of VirtualizingPanel for ListView (GridView) and ListBox in WPF as it uses VirtualizingStackPanel and it is set to true by default, so unless you change the template to include a different ItemsControl/ItemsPanel for example, you're fine (none of those things are done by my repro project).

  2. The problem doesn't exist until automation peers are being created, which can be easily triggered in common scenarios by using a ComboBox and displaying the dropdown. Again, something your example project does not do.

  3. If I really didn't use virtualization, I'm looking at at least 16 GB of item containers in memory and wonderful amount of time to create those spend both in GC and actual creation time (over 15 minutes at least). Believe me, I didn't do that.

@hongruiyu
Copy link

The equivalent item you mentioned is not specifically used in ComboBox. Can you provide the corresponding usage code so that I can further confirm the effect you want to achieve?

@brswan
Copy link

brswan commented Oct 2, 2024

It appears I was able to solve my data grid sluggish issue by making my own DataGrid class and returning null in OnCreateAutomationPeer. I'm not sure what ramifications this may have but it appears to work,.

    public class MyDataGrid : DataGrid
    {
        public MyDataGrid()
        {
        }


        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return null;
            //return base.OnCreateAutomationPeer();
        }
    }

@h3xds1nz
Copy link
Contributor Author

h3xds1nz commented Oct 2, 2024

@brswan Basically entire UI Automation won't work for that control. Which means any accessibility tools.

@brswan
Copy link

brswan commented Oct 3, 2024

@brswan Basically entire UI Automation won't work for that control. Which means any accessibility tools.

After more testing it doesn't seem to help datagrids with any sort of complexity to them. It's still an issue for me.

@brswan
Copy link

brswan commented Oct 3, 2024

I found a solution that works for my problem. I had to override the Windows class. From there you override the OnCreateAutomationPeer() method.

 public class CustomWindowAutomationPeer : FrameworkElementAutomationPeer
 {
     public CustomWindowAutomationPeer(FrameworkElement owner) : base(owner) { }

     protected override string GetNameCore()
     {
         return "CustomWindowAutomationPeer";
     }

     protected override AutomationControlType GetAutomationControlTypeCore()
     {
         return AutomationControlType.Window;
     }

     protected override List<AutomationPeer> GetChildrenCore()
     {
         return new List<AutomationPeer>();
     }
 }

In your custom Window class add

 protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
   {
       return new CustomWindowAutomationPeer(this);
   }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants