代码之家  ›  专栏  ›  技术社区  ›  serhio

WPF用户控件HitTest

  •  2
  • serhio  · 技术社区  · 14 年前

    我有以下用户控件:一个点及其名称:

    <UserControl x:Class="ShapeTester.StopPoint"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="25" d:DesignWidth="100">
    
       <StackPanel>
          <Ellipse Stroke="DarkBlue" Fill="LightBlue" Height="10" Width="10"/>
          <TextBlock Text="Eiffel Tower"/>        
      </StackPanel>
    </UserControl>
    

    这很酷。

    现在,我有一个面板,在巫婆我需要恢复我的停止,我用鼠标击中:

    public partial class StopsPanel : UserControl
    {
        private List<StopPoint> hitList = new List<StopPoint>();
        private EllipseGeometry hitArea = new EllipseGeometry();
    
        public StopsPanel()
        {
            InitializeComponent();
            Initialize();
        }
    
        private void Initialize()
        {
            foreach (StopPoint point in StopsCanvas.Children)
            {
                point.Background = Brushes.LightBlue;
            }
        }
    
        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Initialization:
            Initialize();
            // Get mouse click point:
            Point pt = e.GetPosition(StopsCanvas);
            // Define hit-testing area:
            hitArea = new EllipseGeometry(pt, 1.0, 1.0);
            hitList.Clear();
            // Call HitTest method:
            VisualTreeHelper.HitTest(StopsCanvas, null,
            new HitTestResultCallback(HitTestCallback),
            new GeometryHitTestParameters(hitArea));
            if (hitList.Count > 0)
            {
                foreach (StopPoint point in hitList)
                {
                    // Change rectangle fill color if it is hit:
                    point.Background = Brushes.LightCoral;
                }
                MessageBox.Show(string.Format(
                    "You hit {0} StopPoint(s)", hitList.Count));
            }
        }
    
        public HitTestResultBehavior HitTestCallback(HitTestResult result)
        {
            if (result.VisualHit is StopPoint)
            {
                //
                //-------- NEVER ENTER HERE!!! :(
                //
    
                // Retrieve the results of the hit test.
                IntersectionDetail intersectionDetail =
                ((GeometryHitTestResult)result).IntersectionDetail;
                switch (intersectionDetail)
                {
                    case IntersectionDetail.FullyContains:
                    // Add the hit test result to the list:
                        hitList.Add((StopPoint)result.VisualHit);
                        return HitTestResultBehavior.Continue;
                    case IntersectionDetail.Intersects:
                    // Set the behavior to return visuals at all z-order levels:
                        return HitTestResultBehavior.Continue;
                    case IntersectionDetail.FullyInside:
                    // Set the behavior to return visuals at all z-order levels:
                        return HitTestResultBehavior.Continue;
                    default:
                        return HitTestResultBehavior.Stop;
                }
            }
            else
            {
                return HitTestResultBehavior.Continue;
            }
        }
    }
    

    所以,正如你所看到的,问题是 HitTest从不将用户控件(stoppent)标识为它本身,而是标识其组件 ( 控件 , 椭圆 甚至 边界 ).
    当我将业务对象与StopPoint元素关联时,我需要在MouseHitting时获取它,而不是它的组成元素。

    有办法吗?

    编辑:

    使用filter(现在,它根本不在HitTestCallback中输入):

    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace ShapeTester
    {
        /// <summary>
        /// Interaction logic for StopsPanel.xaml
        /// </summary>
        public partial class StopsPanel : UserControl
        {
            private List<StopPoint> hitList = new List<StopPoint>();
            private EllipseGeometry hitArea = new EllipseGeometry();
    
            public StopsPanel()
            {
                InitializeComponent();
                Initialize();
            }
    
            private void Initialize()
            {
                foreach (StopPoint point in StopsCanvas.Children)
                {
                    point.Background = Brushes.LightBlue;
                }
            }
    
            private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                // Initialization:
                Initialize();
                // Get mouse click point:
                Point pt = e.GetPosition(StopsCanvas);
                // Define hit-testing area:
                hitArea = new EllipseGeometry(pt, 1.0, 1.0);
                hitList.Clear();
                // Call HitTest method:
                VisualTreeHelper.HitTest(StopsCanvas, 
                    new HitTestFilterCallback(MyHitTestFilter),
                    new HitTestResultCallback(HitTestCallback),
                    new GeometryHitTestParameters(hitArea));
    
                if (hitList.Count > 0)
                {
                    foreach (StopPoint point in hitList)
                    {
                        // Change rectangle fill color if it is hit:
                        point.Background = Brushes.LightCoral;
                    }
                    MessageBox.Show(string.Format(
                        "You hit {0} StopPoint(s)", hitList.Count));
                }
            }
    
            public HitTestResultBehavior HitTestCallback(HitTestResult result)
            {
                if (result.VisualHit is StopPoint)
                {
                    //
                    //-------- NEVER ENTER HERE!!! :(
                    //
    
                    // Retrieve the results of the hit test.
                    IntersectionDetail intersectionDetail =
                    ((GeometryHitTestResult)result).IntersectionDetail;
                    switch (intersectionDetail)
                    {
                        case IntersectionDetail.FullyContains:
                        // Add the hit test result to the list:
                            hitList.Add((StopPoint)result.VisualHit);
                            return HitTestResultBehavior.Continue;
                        case IntersectionDetail.Intersects:
                        // Set the behavior to return visuals at all z-order levels:
                            return HitTestResultBehavior.Continue;
                        case IntersectionDetail.FullyInside:
                        // Set the behavior to return visuals at all z-order levels:
                            return HitTestResultBehavior.Continue;
                        default:
                            return HitTestResultBehavior.Stop;
                    }
                }
                else
                {
                    return HitTestResultBehavior.Continue;
                }
            }
    
            // Filter the hit test values for each object in the enumeration.
            public HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
            {
                // Test for the object value you want to filter.
                if (o.GetType() == typeof(StopPoint))
                {
                    // Visual object's descendants are 
                    // NOT part of hit test results enumeration.
                    return HitTestFilterBehavior.ContinueSkipChildren;
                }
                else
                {
                    // Visual object is part of hit test results enumeration.
                    return HitTestFilterBehavior.Continue;
                }
            }
        }
    }
    
    3 回复  |  直到 12 年前
        1
  •  3
  •   Dave Clemmer manu    12 年前

    你可以用 VisualTreeHelper 要查找父停止点:

    var element = result.VisualHit;
    while(element != null && !(element is StopPoint))
        element = VisualTreeHelper.GetParent(element);
    
    if(element == null) return;
    
        2
  •  3
  •   Community datashaman    7 年前

    我想写一个解释,但我已经找到一个像样的解释:

    https://stackoverflow.com/a/7162443/717732

    重点是:

    你的 UserControl.HitTestCore() 留给可能返回空的默认实现,这将导致跳过UC,而不是传递给resultCallback。

    默认行为不是bug。这是一个不明显的,聪明的设计-总的来说,你的控制没有视觉效果,它只是一些孩子的容器谁有形状,所以一般没有点在坎特伯雷大学是可击中和混乱的步行路径。您可能会认为这是一个缺点,因为您的代码的简洁性可以从UC的可测试性中获益。然而,这里的目标不是简洁,而是速度。事实上,这是一个重要的特性,因为它确实减少了treewalker必须执行实际交集的项目数量!

    所以-要么实现HitTestCore并返回非空的内容,要么为UserControl的子项执行hittest,然后当结果正确但等于其子项时,使用VisualTreeHelper.GetParent,直到找到所需的UserControl为止。

        3
  •  0
  •   Dave Clemmer manu    12 年前

    不能将鼠标单击事件侦听器添加到点,并将发送器强制转换为 StopPoint 一切都好吗?不需要额外的命中测试代码。