0

I have a ListView based control. It's a file explorer. When Path property is set, first all file system items are loaded by asynchronous method (using Win32 API). This is super fast even for large number of files. Anyway, for C:\Windows\System32 I don't see any lag. The items are then added to my ListView in batches of 64, after each 64 Dispatcher.Yield() is awaited to show what was added before the operation finishes. This again makes the whole thing way faster and more responsive.

There is however one not very fast operation: when I need an icon for a file for the first time, I have to make a system call. This call is made in getter, it's smart, it tries to use cache if available.

The loading of the view is a blink.

However when I load a huge directory (no problem, fast) - and then resize or maximize the window - I see some black flash before the control resizes.

It's like first the window resizes, the added space is painted black, then the larger ListView appears. It's supper annoying and like devastates my smooth experience. Is there any way to avoid this black space?

I tried to set Window background to white, didn't help. I tried to set my UserControl containing ListView background to white. No joy.

When there are not many items in the ListView there is no black flash, but it's still not smooth enough.

So - how to prevent seeing a black background while the control is being resized?

H.B.
  • 166,899
  • 29
  • 327
  • 400
Harry
  • 4,524
  • 4
  • 42
  • 81
  • It's impossible to know for sure without a good [mcve]. But it sounds like you are doing the WPF equivalent of executing long-running code in the UI thread and calling `Application.DoEvents()` periodically. This is a bad idea in Winforms and the WPF equivalent is also a bad idea. The long-running code should be executed in a background task, notifying the UI thread periodically when there's an update to make. There are lots of Q&A on Stack Overflow already addressing this basic technique. Please improve the question if you think your scenario is unique and you need more help. – Peter Duniho Aug 08 '16 at 22:24
  • 2
    Are you programmatically adding a `ListViewItem` for each file system item? If so, you're creating a rather large visual tree. Have you tried binding your `ListView` using an MVVM approach? If you enable item container virtualization, you'll never have (many) more elements than will actually fit in the viewport, and the elements will be recycled each time an item is scrolled out of view. Resizing should then be quite snappy, though you may need to rig up asynchronous, lazy icon loading to keep things fluid. – Mike Strobel Aug 08 '16 at 22:29
  • @PeterDuniho: I run all long running code outside UI thread - that's how async methods work. My `OnPropertyChanged` override is marked async and it runs outside UI thread. I'm not sure if minimal example would help, because it could not exhibit discussed behavior, and pasting my full code makes no sense. All right, I'll test it on way simpler example and I'll paste it here if the behavior could be observed. – Harry Aug 08 '16 at 22:49
  • @MikeStrobel: Sounds tempting. I'll give it a try. Then I could first allow the control to be drawn, then load the icons. Now when the view needs extra icons, the getter waits until the icons are loaded - maybe this is the cause. – Harry Aug 08 '16 at 22:58
  • 1
    @MikeStrobel: WOW - THANK YOU! It works as charm! `VirtualizingPanel` made all the difference. It still flashes a little when the debugger is attached, but without debugger all flashing disappeared. I'll tweak the icon loading to fully optimize the speed. – Harry Aug 08 '16 at 23:10
  • _"I'm not sure if minimal example would help, because it could not exhibit discussed behavior"_ -- a good [mcve] would, by definition, exhibit the discussed behavior. As you can see from the comments, it's not clear from your question what code is being run in what thread, and this is in large part due to the lack of an MCVE. The question text is particularly confusing, as calling `Dispatcher.Yield()` would have no practical benefit outside the UI thread, so if all the pertinent code is already outside the UI thread, why bother calling it? Anyway, thankfully Mike's guess was correct & helped – Peter Duniho Aug 08 '16 at 23:16
  • @PeterDuniho: Well - adding the items to the control MUST be done in UI thread. When you add like 4K items, it's to big. So I divided it into smaller chunks. After each chunk `Dispatcher.Yield()` is called to show updated items. This is the only place when UI thread is unavoidable. My minimal example would be like 1000 LOC, anything less could not trigger the problem. BTW, adding `VirtualizingPanel` helped a lot. I didn't know WPF has such nice features out of the box, I thought I would have to code it myself from scratch. – Harry Aug 08 '16 at 23:44
  • If you add items to the control manually (which as you've found, isn't necessary or desirable), WPF will normally update the visual as soon as the dispatcher thread returns back to the event loop. Presumably you call `Dispatcher.Yield()` just before the invoked method returns. Again, lacking a good [mcve] it's impossible to comment specifically. But calling `Yield()` simply should not be necessary. – Peter Duniho Aug 08 '16 at 23:46

1 Answers1

0

If anyone has performance issues with ListView - here's what should be done about it:

  1. Do not execute any expensive operations on UI thread. In my case it was not exactly it, but similar. The offending code was hidden in item constructor, it tried to make some string conversions on start. Very bad idea, all expensive operations were moved to getters which solved half of the problem. The item constructor should be empty. No initial values. No initialization at all. All properties should be read via getters. This will result in executing 20 such operations per view instead of like 20000, but only if UI virtualization is used.

  2. Use MVVM pattern, so do not add items directly, create an item source and add items to the source. This would allow built-in UI virtualization which is essential where you have a large number of items. Search "UI virtualization ListView" in Google for more details. This solved the problem definitely.

  3. Think of data virtualization if populating the item source is too slow. In my case this was unnecessary. When getting ALL the data is relatively cheap - we're good. If not - we have to get only a chunk of data which is currently needed to be shown.

  4. A simpler control template could increase application performance a little bit on some low end mobile devices.

  5. If you use unmanaged code anywhere in your application, double check it, I forgot calling DestroyIcon once, and it resulted in very weird, unexpected behavior. So dispose anything disposable, destroy unused handles and such. Make triple sure it's done, I wasted countless hours on debugging such things. To be clear: unreleased resources didn't cause any performance issues, it caused other unexpected behavior like weird exceptions, disappearing icons and such. The issues seemed to come from ListView though.

Harry
  • 4,524
  • 4
  • 42
  • 81