【问题标题】:WPF: OnRender and Hit TestingWPF:OnRender 和命中测试
【发布时间】:2018-03-04 02:54:14
【问题描述】:

当使用 OnRender 在屏幕上绘制东西时,有什么方法可以对绘制的图形进行 Hit Testing?

示例代码

    protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        drawingContext.DrawRectangle(Brushes.Black, null, new Rect(50, 50, 100, 100));
    }

显然没有人参考绘制的矩形,这是执行命中测试所必需的,还是我错了?我知道我可以使用 DrawingVisual,我只是好奇我的理解是否正确,使用 OnRender 绘制的东西你不能对绘制的东西进行任何命中测试?

【问题讨论】:

    标签: wpf hittest


    【解决方案1】:

    您理解正确,无法对使用 DrawingContext 方法绘制的图形进行命中测试,因为它们在可视化树中不表示为对象。

    【讨论】:

    • 对不起,除非我误解了 OP 的问题,这是不正确的。
    • OP 想知道他的鼠标是否击中了他使用DrawingContext 或其他东西绘制的矩形,例如他使用DrawingContext 绘制的圆。那么,您找到了一种方法吗?
    • 但是他没有使用DrawingContext画一个圆。他只画了一个东西——矩形。只要这是他唯一绘制的东西,那么命中测试就会告诉他他是否击中了矩形。具体来说,他问“使用 OnRender 绘制一些东西你不能对绘制的东西执行任何命中测试”是否是真的?这不是真的。正确的是,您无法区分在同一个 OnRender 中完成的两个绘图操作,但这是一个不同的问题。
    【解决方案2】:

    除非我完全误解了 OP 的问题,否则答案是肯定的,命中测试基于 OnRender 期间绘制的内容。您可以通过在您覆盖 OnRender 的同一类中覆盖 OnMouseEnter 来判断。在覆盖OnMouseEnter 试试这个:

    var htr = VisualTreeHelper.HitTest(this, e.GetPosition(this));
    if (htr != null)
         System.Diagnostics.Debug.WriteLine("It's a hit!");
    

    现在,我注意到您仍在调用base.OnRender,这表明您正在尝试在Visual 否则会呈现的内容之上绘制。那么,您将无法做的是区分对父类呈现的内容与您在覆盖中呈现的内容的命中。但是,如果您需要,只需将您的绘图包含在它自己的 Visual 中。

    【讨论】:

    • 你无法知道你是否击中了矩形,因为它不是 UIElement。命中测试只会返回他使用OnRender 绘制的控件,而不是使用DrawingContex 绘制的图形。
    【解决方案3】:

    我的快速解决方案。它在很多方面并不理想,但它对我来说是一个开始。如果有必要,可以很容易地重构性能。

        /// <summary>
        /// Provides basic hit testing
        /// </summary>
        public class RadarHitTestUtility
        {
            public Dictionary<Tuple<int,int>, HitContainer> HitStorage = new Dictionary<Tuple<int, int>, HitContainer>();
    
            public void AddEllipse(RadarObject radarObject, Point point, double x, double y)
            {
                // Geometry used for hit testing
                var elipse = new EllipseGeometry
                {
                    Center = point,
                    RadiusX = x,
                    RadiusY = y,
                };
    
                var key = new Tuple<int, int>((int)point.X, (int)point.Y);
    
                var hit = new HitContainer
                {
                    Geometry = elipse,
                    Bounds = elipse.Bounds,
                    RadarObject = radarObject
                };
    
                if(!HitStorage.ContainsKey(key))
                    HitStorage.Add(key, hit);
            }
    
            /// <summary>
            /// Gets first radar object whose center point is within distance of point.
            /// </summary>
            public HitContainer GetSimpleHit(Point point, int distance = 5)
            {
                foreach (var hit in HitStorage)
                {
                    if (Math.Abs(hit.Key.Item1 - point.X) <= distance && Math.Abs(hit.Key.Item2 - point.Y) <= distance)
                    {
                        hit.Value.Intersection = IntersectionDetail.NotCalculated;
                        return hit.Value;
                    }
                }
                return default(HitContainer);
            }
    
            /// <summary>
            /// Gets first radar object that contains point
            /// </summary>
            public HitContainer GetHit(Point point)
            {
                var PreCheckDistance = 50;
    
                foreach (var hit in HitStorage)
                {
                    if (Math.Abs(hit.Key.Item1 - point.X) <= PreCheckDistance && Math.Abs(hit.Key.Item2 - point.Y) <= PreCheckDistance)
                    {
                        if (hit.Value.Geometry.FillContains(point))
                        {
                            hit.Value.Intersection = IntersectionDetail.FullyInside;
                            return hit.Value;
                        }                    
                    }
                }
                return default(HitContainer);
            }
    
            /// <summary>
            /// Gets first radar object that intersects with geometry
            /// </summary>
            public HitContainer GetHit(Geometry geometry)
            {
                var PreCheckDistance = 50;
                var GeometryCheckTolerance = 1;
                var center = geometry.Bounds.Center();
    
                foreach (var hit in HitStorage)
                {
                    if (Math.Abs(hit.Key.Item1 - center.X) <= PreCheckDistance && Math.Abs(hit.Key.Item2 - center.Y) <= PreCheckDistance)
                    {
                        var result = hit.Value.Geometry.FillContainsWithDetail(geometry, GeometryCheckTolerance, ToleranceType.Absolute);
                        if (result != IntersectionDetail.Empty)
                        {
                            hit.Value.Intersection = result;
                            return hit.Value;
                        }
                    }
                }
                return default(HitContainer);
            }
    
            public class HitContainer
            {
                public RadarObject RadarObject { get; set; }
                public Geometry Geometry { get; set; }
                public Rect Bounds { get; set; }
                public IntersectionDetail Intersection { get; set; }
            }
    
            public void Clear()
            {
                HitStorage.Clear();
            }
        }
    

    在你的控件中声明一个实例

    public RadarHitTestUtility HitTester = new RadarHitTestUtility();
    

    您需要在某个时候调用 Clear(),以便只有当前对象存在于命中测试集合中。

    HitTester.Clear();
    

    在 OnRender() 中,当您希望记录绘制调用以用于命中测试时,您可以使用 AddEllipse() 记录它

    HitTester.AddEllipse(radarObject, radarObject.Point, actorRadius, actorRadius);
    

    这就是我要求命中检查的方式

        private void MouseDownHandler(object sender, MouseButtonEventArgs e)
        {
            IsMouseDown = true;
            Cursor = Cursors.Hand;
            DragInitialPosition = Mouse.GetPosition(this);
            DragInitialPanOffset = CanvasData.PanOffset;
    
            var hit = FindElementUnderClick(sender, e);
            if (hit != null)
            {
                SelectedItem = hit.RadarObject.Actor;
            }            
        }
    
        private RadarHitTestUtility.HitContainer FindElementUnderClick(object sender, MouseEventArgs e)
        {
            var position = e.GetPosition((UIElement)sender);            
            return HitTester.GetHit(position);
        }
    

    如果您希望能够测试椭圆以外的形状,您只需将适当的记录方法添加到 RadarHitTestUtility 并调用它,创建一种不同类型的几何来进行测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-18
      • 1970-01-01
      • 1970-01-01
      • 2018-07-29
      • 2020-06-14
      • 2013-10-16
      相关资源
      最近更新 更多