.net 在虚拟化WPF树视图中滚动非常不稳定

wvyml7n5  于 2023-02-17  发布在  .NET
关注(0)|答案(5)|浏览(235)

如果在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控件或虚拟化支持此场景的面板。

p5fdfcr1

p5fdfcr11#

为了让这个链接更加突出,我也在回复中发布了它。看起来框架代码中有一个bug,目前还没有找到解决方法。我已经在Microsoft Connect上报告了这个bug:
Microsoft Connect: Scrolling in virtualized WPF TreeView is very unstable
还有一个可能相关的bug,由@sixlettervariables发布在评论中:
Microsoft Connect: WPF application freezes while scrolling the TreeView under specific conditions
如果你能重现这些错误,请投赞成票。

k7fdbhmy

k7fdbhmy2#

从.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>
pdkcd3nj

pdkcd3nj3#

默认情况下,虚拟化堆栈面板使用像素渲染来渲染子元素,回收模式将丢弃UI中不再需要的树视图容器内的每个元素。这会导致滚动条大小自动更改。VirtualizingPanel像素渲染技术也会导致滚动选项变慢。通过更改为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>
tyky79it

tyky79it4#

我在加载窗口时在wpf应用程序中出现了同样的错误。Visual Studio 2017经过一些研究,发现了类似这篇文章的东西,我注意到它是有趣的WindowStyle元素。
在我的例子中,XAML设计窗口中wpf和windows属性值的错误是
窗口样式=“无”
我已将其值更改为WindowStyle ="SingleBorderWindow",此错误已消失

ru9i0ody

ru9i0ody5#

祝这个bug 10周年快乐!它仍然存在于.NET 6中。
触发这个异常的是一个TreeView,每个TreeViewItem都有一些自定义格式的文本(通过TextBlock inlines),这些文本足够宽,需要一个水平滚动条,如果我一直向右拖动水平滚动条滑块,然后向上或向下拖动垂直滚动条滑块,就会遇到这个异常。
我想把我的解决方法添加到这堆方法中,因为其他方法都不适用于我。异常来自一个名为AsNonNullVisual的非公共方法。我只是捕获这个特定的异常并忽略它:

Dispatcher.UnhandledException += ( s, e ) => {
    if( e.Exception.TargetSite?.Name == "AsNonNullVisual" )
        e.Handled = true;
};

当抛出异常时,垂直滚动条的拇指会有点抖动,但除此之外没有其他视觉效果,滚动照常继续。
把它放在App.xaml.cs中,你的主窗口构造函数,或者其他任何地方,在那里它会提前运行并且只运行一次。

相关问题