【问题标题】:WPF hit test on custom control does not work自定义控件上的 WPF 命中测试不起作用
【发布时间】:2016-10-10 03:45:05
【问题描述】:

如下自定义控件

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 中设置了一个断点,但它从未命中。

  • 1234563

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

(最初,我只是想在自定义控件上处理鼠标事件。一些现有的解决方案指向我重写 HitTestCore 方法。因此,如果您能提供任何可以让我处理鼠标事件的想法,我不会必须使HitTestCore 方法工作。)

更新

如果我决定使用DrawingVisual,Clemen 的回答很好。但是,当我使用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

克莱门的新答案仍然不符合我的目的。是的,所有的视觉区域都接受了命中测试。但是,我想要的是拥有完整的视口来接收命中测试。在他的例子中,当他将整个视觉缩放到视觉区域时,这是空白区域。

【问题讨论】:

    标签: c# wpf hittest


    【解决方案1】:

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

    【讨论】:

    • 这个答案没有命中测试的代码,这就是这个问题的内容。为了显示,我的代码已经足够好了,至少对我来说是这样。那么你能展示你的代码如何与HitTestCore一起工作吗?
    • 你不需要任何代码来进行命中测试,因为它默认工作。只需致电AddVisualChild
    • 您的问题没有显示任何使用 HostVisual 的代码。现在,调用 AddVisualChild 可以解决您的 actual 问题中描述的问题。使用 HostVisual 是一个不同的问题,应该单独提出。
    • 我的问题包含使用HostVisual 的(注释)代码,在原文中我说我观察到使用HostVisual 时的行为。我要求解释......
    • 您是否尝试摆脱(无法解释的,顺便说一句)RenderTransform?
    【解决方案2】:

    这对你有用。

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

    【讨论】:

      猜你喜欢
      • 2021-05-09
      • 2016-09-14
      • 1970-01-01
      • 1970-01-01
      • 2018-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多