在虚拟的WPF树视图中滚动是非常不稳定的
本文关键字:滚动 非常 不稳定 视图 虚拟 WPF | 更新日期: 2023-09-27 17:50:23
如果在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控件或可能虚拟化面板支持这种情况)
为了使链接更加突出,我也在回答中发布了它。看起来框架代码中存在bug,而且还没有找到解决方法。我已经报告了Microsoft Connect的错误:
Microsoft Connect:在虚拟WPF TreeView中滚动是非常不稳定的
还有一个可能相关的错误,是@sixlettervariables:
在评论中发布的。Microsoft Connect: WPF应用程序在特定条件下滚动TreeView时冻结
如果你能重现这些错误,请投票给他们。
祝这个bug诞生10周年快乐!它仍然存在于。net 6。
对我来说触发它的是TreeView
,其中每个TreeViewItem
都有一些自定义格式的文本(通过TextBlock
内联),其宽度足以需要一个水平滚动条。如果我把水平滚动条的拇指向右拖动,然后把垂直滚动条的拇指向上或向下拖动,就会出现这个异常
我想把我的变通方法添加到这一堆中,因为其他方法都不适合我。该异常来自某个名为AsNonNullVisual
的非公共方法。我只捕获那个特殊的异常并忽略它:
Dispatcher.UnhandledException += ( s, e ) => {
if( e.Exception.TargetSite?.Name == "AsNonNullVisual" )
e.Handled = true;
};
当抛出异常时,垂直滚动条的拇指抖动一点,但没有其他视觉效果,滚动像往常一样继续。
把它放在App.xaml.cs
,你的主窗口构造函数,或其他任何地方,它将运行早期和只一次。
在。net 5中,这个问题仍然存在于WPF中,而且微软已经退出了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>
默认情况下,虚拟化堆栈面板使用像素渲染来呈现子元素,回收模式将丢弃UI中不再需要的树视图容器中的每个元素。这将导致滚动条的大小自动更改。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应用程序中遇到了同样的错误。经过一些研究发现类似这篇文章的东西,我注意到它是一个有趣的WindowStyle元素。
在我的情况下,XAML设计窗口在wpf和windows属性值的错误是
WindowStyle = "没有"
我已将其值更改为WindowStyle ="SingleBorderWindow"
,此错误已消失