对自定义控件的WPF命中测试不起作用

本文关键字:测试 不起作用 WPF 自定义控件 | 更新日期: 2023-09-27 18:12:13

以下自定义控件

public class DummyControl : FrameworkElement
{
    private Visual visual;
    protected override Visual GetVisualChild(int index)
    {
        return visual;
    }
    protected override int VisualChildrenCount { get; } = 1;
    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
    {
        var pt = hitTestParameters.HitPoint;
        return new PointHitTestResult(visual, pt);
    }
    public DummyControl()
    {
        var dv = new DrawingVisual();
        using (var ctx = dv.RenderOpen())
        {
            var penTransparent = new Pen(Brushes.Transparent, 0);
            ctx.DrawRectangle(Brushes.Green, penTransparent, new Rect(0, 0, 1000, 1000));
            ctx.DrawLine(new Pen(Brushes.Red, 3), new Point(0, 500), new Point(1000, 500));
            ctx.DrawLine(new Pen(Brushes.Red, 3), new Point(500, 0), new Point(500, 1000));
        }
        var m = new Matrix();
        m.Scale(0.5, 0.5);
        RenderTransform = new MatrixTransform(m);
        //Does work; but only the left top quater enters hit test
        //var hv = new HostVisual();
        //var vt = new VisualTarget(hv);
        //vt.RootVisual = dv;
        //visual = hv;
        //Never enters hit test
        visual = dv;
    }
}
在xaml

<Window x:Class="MyNamespace.TestWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MyNamespace"
    mc:Ignorable="d">
    <Border Width="500" Height="500">
        <local:DummyControl />
    </Border>
</Window>

显示一个绿色区域,中间有两条红色坐标线。但是它的命中测试行为对我来说是不可理解的。

  • 我在方法HitTestCore中放置了一个断点,但它从未命中。

  • 如果我取消注释代码,使用HostVisualVisualTarget代替,它击中,但只有当鼠标在左上角时(由上面给出的红线表示)

如何解释上述问题,以及如何使其按预期工作(进入hit test full range)?

(最初,我只想处理自定义控件上的鼠标事件。一些现有的解决方案建议我重写HitTestCore方法。所以,如果你能提供任何想法,可以让我处理鼠标事件,我不需要使HitTestCore方法工作。

Clemen的答案是好的,如果我决定使用DrawingVisual。但是,当我使用HostVisualVisualTarget时,如果不重写HitTestCore,它就无法工作,即使我这样做,仍然只有左上角的四分之一将接收鼠标事件。

原来的问题还包括解释。此外,使用HostVisual允许我在另一个线程中运行渲染(在我的实际情况下很耗时)。

(让我用上面的HostVisual突出显示代码)

    //Does work; but only the left top quater enters hit test
    //var hv = new HostVisual();
    //var vt = new VisualTarget(hv);
    //vt.RootVisual = dv;
    //visual = hv;

任何想法?

更新# 2

Clemen的新答案仍然不适合我的目的。是的,所有可视区域都接受命中测试。然而,我想要的是拥有完整的视口来接受命中测试。在他的例子中,这是空白区域,因为他将完整的视觉区域缩放到视觉区域。

对自定义控件的WPF命中测试不起作用

为了建立一个可视树(从而使命中测试在默认情况下工作),您还必须调用AddVisualChild。从MSDN:

AddVisualChild方法设置父子关系在两个可视对象之间。这种方法必须在需要时使用对底层存储实现进行更强的低级控制可视子对象的。VisualCollection可以作为默认值使用存储子对象的实现。

除此之外,当控件的大小发生变化时,您的控件应该重新呈现:

public class DummyControl : FrameworkElement
{
    private readonly DrawingVisual visual = new DrawingVisual();
    public DummyControl()
    {
        AddVisualChild(visual);
    }
    protected override int VisualChildrenCount
    {
        get { return 1; }
    }
    protected override Visual GetVisualChild(int index)
    {
        return visual;
    }
    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        using (var dc = visual.RenderOpen())
        {
            var width = sizeInfo.NewSize.Width;
            var height = sizeInfo.NewSize.Height;
            var linePen = new Pen(Brushes.Red, 3);
            dc.DrawRectangle(Brushes.Green, null, new Rect(0, 0, width, height));
            dc.DrawLine(linePen, new Point(0, height / 2), new Point(width, height / 2));
            dc.DrawLine(linePen, new Point(width / 2, 0), new Point(width / 2, height));
        }
        base.OnRenderSizeChanged(sizeInfo);
    }
}

当你的控件使用HostVisual和VisualTarget时,当它的大小改变时,它仍然需要重新渲染自己,并且还调用AddVisualChild来建立一个视觉树。

public class DummyControl : FrameworkElement
{
    private readonly DrawingVisual drawingVisual = new DrawingVisual();
    private readonly HostVisual hostVisual = new HostVisual();
    public DummyControl()
    {
        var visualTarget = new VisualTarget(hostVisual);
        visualTarget.RootVisual = drawingVisual;
        AddVisualChild(hostVisual);
    }
    protected override int VisualChildrenCount
    {
        get { return 1; }
    }
    protected override Visual GetVisualChild(int index)
    {
        return hostVisual;
    }
    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParams)
    {
        return new PointHitTestResult(hostVisual, hitTestParams.HitPoint);
    }
    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        using (var dc = drawingVisual.RenderOpen())
        {
            var width = sizeInfo.NewSize.Width;
            var height = sizeInfo.NewSize.Height;
            var linePen = new Pen(Brushes.Red, 3);
            dc.DrawRectangle(Brushes.Green, null, new Rect(0, 0, width, height));
            dc.DrawLine(linePen, new Point(0, height / 2), new Point(width, height / 2));
            dc.DrawLine(linePen, new Point(width / 2, 0), new Point(width / 2, height));
        }
        base.OnRenderSizeChanged(sizeInfo);
    }
}

你现在可以设置RenderTransform并且仍然得到正确的命中测试:

<Border>
    <local:DummyControl MouseDown="DummyControl_MouseDown">
        <local:DummyControl.RenderTransform>
            <ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
        </local:DummyControl.RenderTransform>
    </local:DummyControl>
</Border>

这将为您工作。

public class DummyControl : FrameworkElement
    {                   
        protected override void OnRender(DrawingContext ctx)
        {          
            Pen penTransparent = new Pen(Brushes.Transparent, 0);
            ctx.DrawGeometry(Brushes.Green, null, rectGeo);
            ctx.DrawGeometry(Brushes.Red, new Pen(Brushes.Red, 3), line1Geo);
            ctx.DrawGeometry(Brushes.Red, new Pen(Brushes.Red, 3), line2Geo);
            base.OnRender(ctx);
        }
        RectangleGeometry rectGeo;
        LineGeometry line1Geo, line2Geo;
        public DummyControl()
        {
            rectGeo = new RectangleGeometry(new Rect(0, 0, 1000, 1000));
            line1Geo = new LineGeometry(new Point(0, 500), new Point(1000, 500));
            line2Geo = new LineGeometry(new Point(500, 0), new Point(500, 1000));
            this.MouseDown += DummyControl_MouseDown;
        }
        void DummyControl_MouseDown(object sender, MouseButtonEventArgs e)
        {
        }
    }