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

时间:2016-10-10 03:45:05

标签: c# wpf hittest

以下自定义控件

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代替它,它只会在鼠标位于左上方的四分之一(由上面给出的红线表示)时命中

如何解释上述内容以及如何使其按预期工作(在全范围内进行命中测试)?

(最初,我只是想在自定义控件上处理鼠标事件。一些现有的解决方案指出我要覆盖HitTestCore方法。所以,如果你能提供任何可以让我处理鼠标事件的想法,我就不要不必使HitTestCore方法有效。)

更新

如果我决定使用DrawingVisual,克莱门的回答是好的。但是,当我使用HostVisualVisualTarget时,如果没有覆盖HitTestCore则不能正常工作,即使我这样做,仍然只有左上角的quater会接收鼠标事件。

最初的问题还包括解释。此外,使用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

克莱门的新答案仍不适用于我的目的。是的,所有视觉区域都接受了热门测试。但是,我想要的是让完整的视口接收命中测试。在他的情况下,这是空白区域,因为他将视觉区域的整个视觉比例缩放。

2 个答案:

答案 0 :(得分:3)

为了建立可视树(默认情况下使命中测试工作),您还必须调用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>

答案 1 :(得分:0)

这对你有用。

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)
        {

        }
    }