Прокрутка в виртуализированном WPF TreeView очень нестабильна
Если виртуализация включена в TreeView
с элементами, имеющими различные размеры, появляются многочисленные проблемы:
Вертикальная полоса прокрутки меняет свой размер случайным образом и не запоминает размеры элементов после просмотра всего дерева. Прокручивать мышкой сложно.
После некоторой прокрутки вверх и вниз,
ArgumentNullException
выбрасывается из кода платформы.
Воспроизвести очень просто: создайте новое приложение WPF, затем поместите этот код в 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>
и этот код в 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; }
}
}
Когда приложение запущено, переместите курсор мыши в древовидную структуру, прокрутите вниз, используя колесо мыши, затем прокрутите вверх, затем снова начните прокрутку вниз. Где-то посередине выбрасывается следующее исключение:
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()
Вы также можете видеть, что исключение не единственная проблема. При прокрутке вверх и вниз полоса прокрутки постоянно меняет свой размер. (Та же проблема не появляется в ListBox
который не может предсказать размер, но запоминает общую высоту после просмотра всего списка.)
Вопрос: Как заставить полосу прокрутки вести себя правильно и избавиться от исключения? (Я не возражаю против ссылок на альтернативные элементы управления TreeView или виртуализации панелей, которые поддерживают этот сценарий.)
5 ответов
Чтобы сделать ссылку более заметной, я также публикую ее в ответе. Похоже, что ошибка находится в коде фреймворка, и пока не найдено никаких обходных путей. Я сообщил об ошибке в Microsoft Connect:
Microsoft Connect: прокрутка в виртуализированном WPF TreeView очень нестабильна
Существует также, возможно, связанная ошибка, которая была опубликована в комментариях @sixlettervariables:
Microsoft Connect: приложение WPF зависает при прокрутке TreeView при определенных условиях
Если вы можете воспроизвести ошибки, пожалуйста, оцените их.
Поздравляем с 10-летием этого жука! Он все еще существует в .NET 6.
Что вызывает это для меня, так это
Я хотел бы добавить свой обходной путь в кучу, так как ни один из других не работал у меня. Исключение составляет некоторый непубличный метод, называемый
Dispatcher.UnhandledException += ( s, e ) => {
if( e.Exception.TargetSite?.Name == "AsNonNullVisual" )
e.Handled = true;
};
Ползунок вертикальной полосы прокрутки немного дрожит, когда возникает исключение, но в остальном нет никаких других визуальных эффектов, и прокрутка продолжается как обычно.
Поместите это в
Начиная с .NET 5 эта проблема все еще существует в WPF, и Microsoft прекратила использование Microsoft Connect, поэтому неясно, находится ли она вообще на их радаре. Я столкнулся с той же проблемой и случайно наткнулся на исправление, которое сработало для меня. По сути, он просто делает то же самое, что должен делать TreeView, используя HierarchichalDataTemplate для рендеринга каждого узла, но там, где встроенный TreeView дает сбой при прокрутке, эта версия этого не делает (в дереве элементов в моем случае).
<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>
По умолчанию панель "Виртуализация стека" использует рендеринг пикселей для рендеринга дочерних элементов, а режим "Переработка" отбрасывает каждый элемент внутри контейнера древовидной структуры, который больше не нужен в пользовательском интерфейсе. Это приводит к автоматическому изменению размера полосы прокрутки. Техника рендеринга пикселей VirtualizationPanel также приведет к замедлению прокрутки. Если перейти на VirtualizingPanel.ScrollUnit="Item", это решит ваши проблемы. Ниже 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"
VirtualizingPanel.ScrollUnit="Item"
>
<TreeView.ItemTemplate>
<DataTemplate>
<Border Height="{Binding Height}"
Width="{Binding Height}"
BorderThickness="1"
Background="DarkGray"
BorderBrush="DarkBlue" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Я взял ту же ошибку в приложении wpf при загрузке окна. Visual Studio 2017 После некоторых исследований выясните что-то вроде этого поста, и я заметил, что это интересный элемент WindowStyle.
В моем случае ошибка в окне дизайна XAML в значении атрибута wpf и windows была
WindowStyle ="нет"
я изменил это значение на WindowStyle ="SingleBorderWindow"
и эта ошибка исчезла