【问题标题】:Scrolling in virtualized WPF TreeView is very unstable在虚拟化 WPF TreeView 中滚动非常不稳定
【发布时间】:2012-12-15 02:05:32
【问题描述】:

如果在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 控件或支持这种情况的虚拟化面板。)

【问题讨论】:

  • 您使用的是 .NET 4 还是 4.5?
  • @Sisyphe .NET 4.5,Windows 7(航空主题),VS 2012
  • @Athari 在同一个盒子(32 位)上测试无一例外,滚动条的大小始终相同。
  • 我能够使用 .NET 4.0、Win7 64 位重现该问题。为了重现,有必要按照字母的说明进行操作:使用鼠标滚轮一直向下滚动,然后使用鼠标滚轮一直向上滚动。在某些时候,它会抛出。以任何其他方式滚动都不会触发问题。

标签: c# .net wpf treeview virtualizingstackpanel


【解决方案1】:

为了使链接更加突出,我也将其发布在答案中。看起来框架代码中有一个错误,还没有找到解决方法。我已经报告了 Microsoft Connect 上的错误:

Microsoft Connect: Scrolling in virtualized WPF TreeView is very unstable

@sixlettervariables 在 cmets 中发布了一个可能相关的错误:

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

如果您可以重现错误,请投票。

【讨论】:

  • 问题已被微软关闭? .Net4.5 中是否解决了任何更新?
  • @RohitVats 考虑到他们的评论,“更新的确切性质和日期待定。”
  • 此问题现已在 .NET 4.5.2 中修复。
  • 给未来读者的说明,从 4.6.1 开始,如果您绑定的项目(mvvm 样式)是结构,那么它将崩溃。我不确定它是否与这个问题有关(虽然我有类似的堆栈跟踪)。切换到课堂解决了我的问题。
  • 这真的是这个问题的答案吗?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    默认情况下,虚拟化堆栈面板使用像素渲染来渲染子元素,而回收模式将丢弃 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>
    

    【讨论】:

    • TreeView 虚拟化在ScrollUnit=Item 模式下毫无意义,因为每个分支 都被视为一个项目。它适用于这个例子只是因为没有子项目。在引入ScrollUnit=Pixel 之前,TreeView 中的虚拟化是完全不可能的。
    【解决方案4】:

    我在加载窗口时在 wpf 应用程序中遇到了同样的错误。 Visual Studio 2017 经过一些研究,发现了类似这篇文章的内容,我注意到它是有趣的 WindowStyle 元素。

    在我的情况下,wpf 和 windows 属性值中的 XAML 设计窗口中的错误是

    WindowStyle =“无”

    我已将其值更改为 WindowStyle ="SingleBorderWindow",此错误已消失

    【讨论】:

      猜你喜欢
      • 2017-06-27
      • 2018-05-07
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 2017-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多