WPF HitTest 获取 ListBoxItem

WPF HitTest to get ListBoxItem

第一件事:我在 Visual Studio 2010 年有一个使用 C# 和 MVVM (MVVM Light) 的 WPF 项目。

我有一个 Canvas 控件,里面是一个 ListBox - 每个 ListBoxItem 都可以通过鼠标拖动来回移动(每个 ListBoxItem 都有一个 DataTemplate,其中包含一个允许拖动每个项目的 Thumb 控件)。

看起来如下:

<DataTemplate>
                    <Grid Background="Transparent">
                        <Thumb Name="myThumb" Template="{StaticResource NodeVisualTemplateRegular}">

                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="DragDelta">
                                    <cmd:EventToCommand Command="{Binding ChatNodeListViewModel.DragDeltaCommand, Source={StaticResource Locator}}" PassEventArgsToCommand="True"/>
                                </i:EventTrigger>

                            </i:Interaction.Triggers>
                        </Thumb>
                    </Grid> </DataTemplate>

如您所见,有一个处理拖动部分的命令 (DeltaDragCommand)。

None 这是个问题,但后来我想添加通过单击和拖动动作在 Canvas 控件周围平移的功能。这是与上述拖动 ListBoxItems 发生冲突的地方。

Canvas pan 内容在后面的代码中处理,事件的订阅如下所示:

NodeDragScrollViewer.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
        NodeDragScrollViewer.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
        NodeDragScrollViewer.MouseMove += OnMouseMove;
        NodeDragScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;

        NodeDragScrollViewer.ScrollChanged += OnScrollViewerScrollChanged;

我们在这里看到的事件是 运行 在 ScrollViewer(Canvas 控件所在的控件)上。问题是这些事件位于 'on top of' ListBoxItems - canvas 的平移点击将在点击拖动 ListBoxItem 之前激活。

现在我看到的一个解决方案是使用 VisualTreeHelper HitTest 方法找出被点击的内容。如果我可以查明是否单击了 ListBoxItem,那么我可以忽略 Canvas 平移操作并让事情像以前一样进行。

我面临的问题是我无法让它工作。我似乎有人解释了这应该如何工作并为此操作借用了代码。我在这里展示该代码:

Point pt = new Point();
        VisualTreeHelper.HitTest(NodeDragCanvas, null,
                     new HitTestResultCallback(MyHitTestResult),
                     new PointHitTestParameters(pt));

private HitTestResultBehavior MyHitTestResult(HitTestResult result)
    {
        var p = FindParent<ListBoxItem>(result.VisualHit);


        //Set the behavior to return visuals at all z-order levels. 
        return HitTestResultBehavior.Continue;
    }

public static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        //get parent item
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        T parent = parentObject as T;
        if (parent != null)
            return parent;
        else
            return FindParent<T>(parentObject);
    }

如果您查看 HitTestResultBehaviour 的内容,我会在此处检查我单击的控件。我希望它是一个 ListBoxItem...但总是 returns 'null'。我知道该方法可以多次触发,但我从未见过 var p(在本例中)不是 null 值。但是,如果我尝试使用 FindParent<ListBox>,我将返回 ListBox。它有适量的物品,似乎符合我的预期。但这对我不起作用,因为我的列表框是 Canvas.

的大小

这似乎是关键点,但我不知道还能尝试什么,所有的研究途径似乎都回到了这种方法。

任何人都可以提供任何指导吗? 谢谢

是的,显然您需要在 Canvas 上使用 PreviewXXX 事件,否则您将无法获得 Canvas 中 ListBoxItems 的鼠标事件,因为 ListBoxItem 已经吃掉了它们。

您可能需要考虑重组您的代码,这样您就可以使用 MyListBox(或您想要的任何名称)来代替使​​用 ListBox,并在其中捕获 ListBoxItem 鼠标事件并仅处理 Canvas拖入Canvas。你现在拥有它的方式,当它们真正完全分开时,你将这两种情况混合在一起。 canvas 应该处理它的事情,列表框应该处理它的事情。

如果您想保留代码的现有状态,请查看 MouseEventArgs、原始来源、发件人等。其中之一可能有原始发件人,但它可能是 TextBlock 而不是然后是 ListBoxItem,因为这是顶级项目,所以...

Google 周围有一个众所周知的 class VisualTreeExtensions 并使用它。当您获得 TextBlock 或其他内容时,您可以执行类似 ((DependencyObject)e.OriginalSource).GetVisualAncestor<ListBoxItem>() 的操作,如果它不为空,则您知道您在 ListBox 中。