Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scrolling in virtualized WPF TreeView is very unstable

If virtualizing is enabled in TreeView with items having various sizes, multiple problems appear:

  • Vertical scroll bar changes its size randomly and doesn't remember sizes of elements after viewing the whole tree. Scrolling with mouse is hard.

  • After some scrolling up and down, ArgumentNullException is thrown from the framework code.

Reproduciing is simple: create a new WPF application, then put this code into MainWindow.xaml

<Window x:Class="VirtualTreeView.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         Title="MainWindow" Height="800" Width="400" Left="0" Top="0"         DataContext="{Binding RelativeSource={RelativeSource Self}}">     <Grid>         <TreeView x:Name="tvwItems" ItemsSource="{Binding Items}"                 VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">             <TreeView.ItemTemplate>                 <DataTemplate>                     <Border Height="{Binding Height}" Width="{Binding Height}"                             BorderThickness="1" Background="DarkGray" BorderBrush="DarkBlue"/>                 </DataTemplate>             </TreeView.ItemTemplate>         </TreeView>     </Grid> </Window> 

and this code into MainWindow.xaml.cs

using System.Collections.ObjectModel; using System.Linq;  namespace VirtualTreeView {     public partial class MainWindow     {         public ObservableCollection<Item> Items { get; set; }          public MainWindow ()         {             Items = new ObservableCollection<Item>(Enumerable.Range(0, 20).Select(i => new Item {                 Height = i*20,             }));             InitializeComponent();         }     }      public class Item     {         public double Height { get; set; }     } } 

When application is ran, move mouse cursor into a treeview, scroll to the bottom using mouse wheel, then scroll to the top, then start scrolling down again. Somewhere in the middle the following exception is thrown:

System.ArgumentNullException was unhandled   HResult=-2147467261   Message=Value cannot be null. Parameter name: element   Source=PresentationCore   ParamName=element   StackTrace:        at MS.Internal.Media.VisualTreeUtils.AsNonNullVisual(DependencyObject element, Visual& visual, Visual3D& visual3D)        at System.Windows.Media.VisualTreeHelper.GetParent(DependencyObject reference)        at System.Windows.Controls.VirtualizingStackPanel.FindScrollOffset(Visual v)        at System.Windows.Controls.VirtualizingStackPanel.OnAnchorOperation(Boolean isAnchorOperationPending)        at System.Windows.Controls.VirtualizingStackPanel.OnAnchorOperation()        at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)        at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)        at System.Windows.Threading.DispatcherOperation.InvokeImpl()        at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)        at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)        at System.Windows.Threading.DispatcherOperation.Invoke()        at System.Windows.Threading.Dispatcher.ProcessQueue()        at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)        at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)        at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)        at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)        at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)        at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)        at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)        at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)        at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)        at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)        at System.Windows.Threading.Dispatcher.Run()        at System.Windows.Application.RunDispatcher(Object ignore)        at System.Windows.Application.RunInternal(Window window)        at System.Windows.Application.Run(Window window)        at System.Windows.Application.Run()        at VirtualTreeView.App.Main() in d:\Docs\Projects\_Try\VirtualTreeView\obj\Debug\App.g.cs:line 0        at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)        at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)        at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()        at System.Threading.ThreadHelper.ThreadStart_Context(Object state)        at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)        at System.Threading.ThreadHelper.ThreadStart() 

You can also see that exception is not the only problem. When scrolling up and down, scroll bar constantly changes its size. (The same problem doesn't appear in ListBox which can't predict size, but remembers total height after viewing the whole list.)

Question: How to make the scroll bar behave properly and get rid of the exception? (I don't mind links to alternative TreeView controls or maybe virtualizing panels which support this scenario.)

like image 857
Athari Avatar asked Dec 30 '12 17:12

Athari


2 Answers

To make the link more prominent, I am posting it in an answer too. It looks like a bug is within the framework code and there are no workarounds found yet. I have reported the bug on Microsoft Connect:

Microsoft Connect: Scrolling in virtualized WPF TreeView is very unstable

There is also a maybe related bug which was posted in the comments by @sixlettervariables:

Microsoft Connect: WPF application freezes while scrolling the TreeView under specific conditions

If you can reproduce the bugs, please vote them up.

like image 72
Athari Avatar answered Oct 03 '22 16:10

Athari


As of .NET 5 this issue still exists in WPF, and Microsoft has retired Microsoft Connect so it's unclear if this is even on their radar anymore. I encountered the same issue and stumbled across a fix that worked for me purely by accident. Essentially it's just doing the same thing the TreeView should be doing, using a HierarchichalDataTemplate to render each node, but where the built-in TreeView crashes while scrolling, this version doesn't (on the tree of items in my case).

<DockPanel>    <DockPanel.Resources>       <HierarchicalDataTemplate DataType="{x:Type src:Item}" ItemsSource="{Binding Path=Children}">          <TextBlock Text"{Binding}"/>       </HierarchicalDataTemplate>    </DockPanel.Resources>     <TreeView x:Name="tvwItems" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding Items}">    </TreeView> </DockPanel> 
like image 32
Framnk Avatar answered Oct 03 '22 15:10

Framnk