为什么点击树会抛出“System.Windows.Documents.Run”不是Visual或Visual3D的InvalidOperationException异常?

18
有时在树形视图中右键单击某个节点会导致未处理的 InvalidOperationException 异常。在代码后台,我选择了被右键单击的行:

有时在树形视图中右键单击某个节点会导致未处理的 InvalidOperationException 异常。在代码后台,我选择了被右键单击的行:

    static TreeViewItem VisualUpwardSearch(DependencyObject source)
    {
        while (source != null && !(source is TreeViewItem))
            source = VisualTreeHelper.GetParent(source);

        return source as TreeViewItem;
    }

    private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
            TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);

            if (treeViewItem != null)
            {
                treeViewItem.Focus();
                e.Handled = true;
            }
    }
根据上面的堆栈跟踪,这是问题的源头。
xaml:
<UserControl.Resources>
   <HierarchicalDataTemplate ItemsSource="{Binding ClassesItemsSource}" DataType="{x:Type pnls:FavoriteObjectTableViewModel}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding Converter={StaticResource nameToBitmapSource}}" DataContext="{Binding Bitmap}" />
            <Label Content="{Binding TableName}"/>
        </StackPanel>
    </HierarchicalDataTemplate>
    <DataTemplate DataType="{x:Type pnls:FavoriteObjectClassViewModel}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding Bitmap, Converter={StaticResource UriToCachedImageConverter}}"/>
            <Label Content="{Binding ClassName}"/>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

<TreeView Name="Insert_ObjectTreeIE" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding TablesItemsSource}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="TreeViewItem">
                <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <EventSetter Event="PreviewMouseRightButtonDown" Handler="OnPreviewMouseRightButtonDown"></EventSetter>
                <EventSetter Event="MouseDoubleClick" Handler="OnMouseDoubleClick" />
            </Style>
        </TreeView.ItemContainerStyle>
</TreeView>

堆栈跟踪:

e.StackTrace    "   at MS.Internal.Media.VisualTreeUtils.AsVisual(DependencyObject element, Visual& visual, Visual3D& visual3D)\r\n   
at MS.Internal.Media.VisualTreeUtils.AsNonNullVisual(DependencyObject element, Visual& visual, Visual3D& visual3D)\r\n
at System.Windows.Media.VisualTreeHelper.GetParent(DependencyObject reference)\r\n   
at Tekla.Nis.Application.Shared.UI.Panels.FavoriteObjectsView.VisualUpwardSearch(DependencyObject source) in c:\\XXX\\161wpf\\src\\SharedAppFeature\\Panels\\FavoriteObjectsView.xaml.cs:line 45\r\n   
at Application.Shared.UI.Panels.FavoriteObjectsView.OnPreviewMouseRightButtonDown(Object sender, MouseButtonEventArgs e) in c:\\XXX\\161wpf\\src\\NisSharedAppFeature\\Panels\\FavoriteObjectsView.xaml.cs:line 52\r\n   
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)\r\n   
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)\r\n   
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)\r\n   
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)\r\n   
at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)\r\n   
at System.Windows.UIElement.OnPreviewMouseDownThunk(Object sender, MouseButtonEventArgs e)\r\n   
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)\r\n   
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)\r\n   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)\r\n   
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)\r\n  
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)\r\n   at System.Windows.ContentElement.RaiseTrustedEvent(RoutedEventArgs args)\r\n   
at System.Windows.Input.InputManager.ProcessStagingArea()\r\n   
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)\r\n   
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)\r\n   
at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)\r\n   
at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)\r\n   
at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)\r\n   
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)\r\n   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)\r\n   
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)\r\n   
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)\r\n   
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)\r\n   
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)\r\n   
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)\r\n   
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)\r\n  
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)\r\n   
at System.Windows.Application.RunDispatcher(Object ignore)\r\n   
at System.Windows.Application.RunInternal(Window window)\r\n   
at System.Windows.Application.Run(Window window)\r\n   
at System.Windows.Application.Run()\r\n   at "my application start location"

有时我可以复现这个问题。我的同事说,在某些树中,左键单击项目1并右键单击项目2会每次都产生这种情况。


“Run”不是可视元素,需要单独处理。VisualUpwardSearch(e.Source as DependencyObject);已经为您解决了吗?此外:必须在标签上而不是图片上重现点击! - Markus Hütter
我点击的是文本块,而不是图像,但现在我已经点击了几分钟,无法重现它。因此,我不知道这个更改是否有所不同。 - char m
将 e.Source 更改为后,选择项的父级将被选中。 - char m
2个回答

36
当您在标签文本中的某个位置单击时,将出现问题。在这种情况下,e.OriginalSource将是一个Run对象,它是LabelTextBox的内部组成部分。 Run元素没有继承Visual类,因此不能成为可视树的一部分,在这种情况下,VisualTreeHelper.GetParent(source);将抛出InvalidOperationException
最简单的解决方案是将每个文本控件(在您的情况下是Label)设置为IsHitTestVisible="False",这将排除这些控件的命中测试逻辑,这意味着它永远不会是事件的e.OriginalSource,取而代之的是将选择其父级,而父级很可能是一个Visual元素。

1
感谢!彻底的测试将会告诉我们这是否修复了它。对我来说,几乎不可能重现。在点击了1小时后,我只成功地得到了2次异常。再次感谢你的解释。你能告诉我“最有可能的部分”是什么吗?即使我设置了IsHitTestVisible="False",这种情况也可能发生吗? - char m
所以,为了安全起见,是否可以添加额外的检查“if(source is Visual)”呢? - char m
1
不要进行检查,它可能掩盖未来的问题。我提到过它可能是“最有可能的视觉”是因为如果您在模板中添加其他文本元素而没有将IsHitTestVisible设置为false,则可能存在一些情况。在您的情况下是100%安全的,您可以只将IsHitTestVisible放在模板的根StackPanel上,以避免出现模板中其他文本元素的问题。 - Novitchi S
谢谢。我的同事测试过了,无法再次复现。这是一个奇怪的错误/特性,因为它很难再现。 - char m
3
我遇到了和在TextBlock中运行时相同的问题,我将整个TextBlock的IsHitTestVisible属性设为“False”,问题就消失了。谢谢!!! - Gabriel

5
我发现以父项作为起点开始搜索适用于我的情况。我的TreeViewItem文本包含多个运行,因此我无法禁用命中测试。
    private void TextBlock_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        TreeViewItem treeViewItem;
        if (e.OriginalSource is System.Windows.Documents.Run)
            treeViewItem = VisualUpwardSearch(((System.Windows.Documents.Run)e.OriginalSource).Parent as DependencyObject);
        else treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);

        if (treeViewItem != null)
        {
            treeViewItem.IsSelected = true;
            e.Handled = true;
        }
    }

这个答案对我帮助很大。IsHitTestVisible="False"的解决方法并没有解决我的问题=>右键单击后,上下文菜单不再弹出。 - IamJose
这比被接受的答案更好。不需要修复XAML中可能发生这种情况的所有地方,只需修复VisualUpwardSearch(或者你的等效物)- 它应该首先检查DependencyObject是否为Run,如果是,则从其父级开始。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接