dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.03k stars 1.16k forks source link

Performance delay when typing each letter into the ComboBox when the ItemsSource has 40,000 items #9502

Open SreemonPremkumarMuthukrishnan opened 1 month ago

SreemonPremkumarMuthukrishnan commented 1 month ago

Description

When loading 40,000 complex objects and setting the DisplayMemberPath, there is a delay between the keystroke and the update of the letter in the ComboBox control. Please find the code snippet below.

XAML codes:

<ComboBox Grid.Row="0" Grid.Column="1" x:Name="ComboBoxWithDisplayMember" 
          IsEditable="True" DisplayMemberPath="DisplayText" Width="200" Height="30" 
          BorderBrush="Green" 
          ItemsSource="{Binding ComboBoxItemSource, Source={StaticResource DataKey}}" 
          BorderThickness="3" />

C# Code:

public class ComboBoxObject
{
    public string DisplayText { get; set; }
    public int Index { get; set; }
}

Video output:

https://github.com/user-attachments/assets/c6434c23-ec2f-4344-9d76-b0ac482d187e

Tested Sample: ComboBox_Demo.zip

Reproduction Steps

  1. Run the sample.
  2. Enter letters into the second ComboBox which has no DisplayMemberPath property (no delay).
  3. Enter letters into the first ComboBox which has DisplayMemberPath property set (experiencing delay).

Expected behavior

There should not be a delay when entering a letter into the ComboBox control.

Actual behavior

There should be a delay when entering a letter into the ComboBox control.

Regression?

No response

Known Workarounds

No response

Impact

No response

Configuration

No response

Other information

No response

miloush commented 1 month ago

You can disable text search by setting IsTextSearchEnabled to false.

xlsupport commented 1 month ago

ComboBoxes get unwieldy when there are many items to display. But it gets much better with virtualizing!

Try following in Resources.

<Style TargetType="ComboBox">
     <Setter Property="ItemsPanel">
         <Setter.Value>
             <ItemsPanelTemplate>
                 <VirtualizingStackPanel/>
             </ItemsPanelTemplate>
         </Setter.Value>
     </Setter>
 </Style>
miloush commented 1 month ago

@xlsupport doesn't help with text search where you need to check each item

xlsupport commented 1 month ago

@miloush? It does! Also in this case. Check it. instant response on typing & dropdown.

h3xds1nz commented 1 month ago

It does help but the reason why it helped is actually not Virtualization itself but the fact that instead of 40k automation peers being created back and forth as you filter it's just those 20-40 for those 20-40 items you see on the screen.

Unrelated and the cause of the issue is elsewhere. If peers weren't created when no client is attached, it wouldn't be an issue in the first place.

xlsupport commented 1 month ago

@h3xds1nz Filtering? Are you confusing this with his other issue? The one with the DataTable and the awkward filtering?

“Ours is not to wonder why. Ours is just to do or die.”

h3xds1nz commented 1 month ago

No, I'm not confusing anything but I've probably chosen insufficient wording; virtualization (ItemContainerGenerator) basically feeds a filtered out portion of data that it the panel then works with (realized/unrealized blocks).

Point still stands. If you happen to display the popup or click anywhere which causes to display it, aut peers will start being created due to ForceMsaaToUiaBridge and the whole thing will become insanely laggy even after all the containers would be created.

For the issue displayed on the video, virtualization won't help because the search is done within 40k of those items (as @miloush has outlined, disabling it would help). The search func within the collection is not written in 2024 and could be improved.

miloush commented 1 month ago

The search function still needs to check each item in the worst case, optimization will only push the number of what gives a usable experience. If the application needs to do text search for a large number of items, it should come up with a way to search them faster and take over the built-in text search.

h3xds1nz commented 1 month ago

Yeah, no doubt about that.

SreemonPremkumarMuthukrishnan commented 1 month ago

Hi, Is there any possibility that this performance issue has been considered and fixed or improved in the ComboBox control itself?

mahara commented 1 month ago

... If the application needs to do text search for a large number of items, it should come up with a way to search them faster and take over the built-in text search.

It seems the internal filtering mechanism is done on UI thread, that's why it's blocking the UI rendering.

But I'm not sure WPF has mechanism to offload such task to non-UI thread, even when you're filtering using CollectionView.

mahara commented 1 month ago

Hi, Is there any possibility that this performance issue has been considered and fixed or improved in the ComboBox control itself?

This:

https://github.com/dotnet/wpf/blob/75265eed57abb4e9f00ff20770159d18d79f456a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ComboBox.cs#L708

and then this:

https://github.com/dotnet/wpf/blob/75265eed57abb4e9f00ff20770159d18d79f456a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextSearch.cs#L427

In your case, it's basically iterating over 40K items done on UI thread. And it's blocking the UI thread, hence the performance delay you noticed. If 1M items put in the ComboBox, one would be out of luck... 🤣🤣🤣

That linear search should done on non-UI thread. But first, it can be done by refactoring TextSearch to be not tightly-coupled to ItemsControl, or any other UI controls.

But overall, it seems more like design issues in WPF, such as in the case offloading the filtering on CollectionView to non-UI thread, and so on. Even I'm not so sure if WinUI would handle such case differently. If not so, then we can learn solution from WinUI to be implemented in WPF.

Symbai commented 1 month ago

That linear search should done on non-UI thread.

How often does it happen that someone has more than 40,000 entries in a combo box and not just 1-10? Creating a background task to filter 10 entries is bizarre. I would bet my life that in most scenarios the combo box only has a few entries. To be honest, I've never seen an application in my entire life where a combobox contained a list of more than 40,000 entries. And if that's the case, then I would say it's a design issue. When you have a list that large, you will normally split it up. For example, by creating groups, getting the user to select a group first and then getting a shorter list of items to choose from. But I don't mind if someone comes up with a solution for this niche case. I would just like to add that the combobox needs to deal with short lists in particular. And that should be kept in mind for a possible change.

mahara commented 1 month ago

And if that's the case, then I would say it's a design issue.

Your concern is a question that you should ask the OP yourself, for example, "Why would one add 40K items to a ComboBox?" That's a different question. You can ask him directly 😄😄😄

That linear search should be done on non-UI thread. How often does it happen that someone has more than 40,000 entries in a combo box and not just 1-10?

But my answer was for the OP, and directly relevant to the question he asked; and not answering other questions. But if you ask me directly, that would not be a majority use case, although it's not something uncommon.

To be honest, I've never seen an application in my entire life where a combobox contained a list of more than 40,000 entries.

In LOB applications I've seen, they have thousands of customers displayed in a ComboBox.

Creating a background task to filter 10 entries is bizarre.

And that's not what I exactly have in mind how it could be possibly be achieved/solved.

Let's start with a mindset first of how a good UI should be. One criterion of a good UI is that it should have a good UX with good perceived performance. The concern OP asked here because there seems to be an issue with perceived performance. And that's not good for the UX and UI in overall.

Now let's go to a searching scenario in an app. Searching data in an app is not an uncommon scenario these days. In fact, many users further ask whether they can do instant searching where the results can be shown instantly as they type any letters/characters. Now let's realize first that searching is an expensive task. And as the number of data grows, it becomes a long-running task. A long-running task that is done on UI thread could block the UI rendering, that eventually leads to a not so good, or even poor/bad, perceived performance of the UX and UI.

This case is one example of that. Another example is CollectionView filtering issue I mentioned before. https://stackoverflow.com/questions/851545/wpfs-icollectionview-filter-with-large-sets-of-data Quoting the SO OP: "But this runs in the UI thread and blocks the entire application when filtering which gives a very poor user experience."

And I believe there are still many unidentified performance issues within WPF code base itself caused by long-running, non-UI-directly-related tasks that actually done on UI thread. Along with other performance issues like UI virtualization issues, data virtualization issues, complex layout and rendering issues, and so on. Common complaints are that WPF becomes slower when displaying large sets of data.

So back to this case, how can this be possibly and ideally solved?

Continuing my earlier suggestion to refactor UI controls out of TextSearch, we can have ITextSearcher interface that only focus on searching matching items and returning matching item index in method like int FindMatchingItem(list of required parameters) { }. Current TextSearch logic can still be used by implementing the interface as ComboBoxTextSearcher, without changing principal implementation technique: searching still done in UI thread, so there's no need to create any unnecessary background thread for example if that really matters. ComboBox then can have TextSearcher property of ITextSearcher type that has ComboBoxTextSearcher set as the default implementation. But then developer can extend ITextSearcher and have custom text search implementation done on non-UI thread for better UX and UI perceived performance. One can even use text search engine libraries like Lucene.NET (https://lucenenet.apache.org/), for example. Or whatever very sophisticated custom search implementation could possibly be needed and done to make a great UI with excellent UX and perceived performance. The limit is only your imagination.

Afterall, engineering challenges and solutions should focus first on how to make a great UI with excellent UX and perceived performance.